Merge "Add null check in the notifyForegroundUserAboutSoundIfNecessary" into main
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 31157ca..0653839 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -17,7 +17,9 @@
package android.companion.virtual;
import android.app.PendingIntent;
+import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.sensor.VirtualSensor;
@@ -296,4 +298,15 @@
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
String getVirtualCameraId(in VirtualCameraConfig camera);
+
+ /**
+ * Setter for listeners that live in the client process, namely in
+ * {@link android.companion.virtual.VirtualDeviceInternal}.
+ *
+ * This is needed for virtual devices that are created by the system, as the VirtualDeviceImpl
+ * object is created before the returned VirtualDeviceInternal one.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void setListeners(in IVirtualDeviceActivityListener activityListener,
+ in IVirtualDeviceSoundEffectListener soundEffectListener);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index af86c97..4cbcb68 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -164,6 +164,20 @@
mSoundEffectListener);
}
+ VirtualDeviceInternal(
+ IVirtualDeviceManager service,
+ Context context,
+ IVirtualDevice virtualDevice) {
+ mService = service;
+ mContext = context.getApplicationContext();
+ mVirtualDevice = virtualDevice;
+ try {
+ mVirtualDevice.setListeners(mActivityListenerBinder, mSoundEffectListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
int getDeviceId() {
try {
return mVirtualDevice.getDeviceId();
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index c68014d..88c3d38 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -575,6 +575,12 @@
new VirtualDeviceInternal(service, context, associationId, params);
}
+ /** @hide */
+ public VirtualDevice(IVirtualDeviceManager service, Context context,
+ IVirtualDevice virtualDevice) {
+ mVirtualDeviceInternal = new VirtualDeviceInternal(service, context, virtualDevice);
+ }
+
/**
* Returns the unique ID of this virtual device.
*/
diff --git a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
index b8f2c00..3be911abe7 100644
--- a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
+++ b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
@@ -69,8 +69,13 @@
}
/**
- * Returns the scroll amount, normalized from -1.0 to 1.0, inclusive. Positive values
- * indicate scrolling forward (e.g. down in a vertical list); negative values, backward.
+ * Returns the scroll amount, normalized from -1.0 to 1.0, inclusive.
+ * <p>
+ * Positive values indicate scrolling forward (e.g. down in a vertical list); negative values,
+ * backward.
+ * <p>
+ * Values of 1.0 or -1.0 represent the maximum supported scroll.
+ * </p>
*/
public @FloatRange(from = -1.0f, to = 1.0f) float getScrollAmount() {
return mScrollAmount;
@@ -91,7 +96,7 @@
*/
public static final class Builder {
- private float mScrollAmount;
+ @FloatRange(from = -1.0f, to = 1.0f) private float mScrollAmount = 0.0f;
private long mEventTimeNanos = 0L;
/**
@@ -102,9 +107,13 @@
}
/**
- * Sets the scroll amount, normalized from -1.0 to 1.0, inclusive. Positive values
- * indicate scrolling forward (e.g. down in a vertical list); negative values, backward.
- *
+ * Sets the scroll amount, normalized from -1.0 to 1.0, inclusive.
+ * <p>
+ * Positive values indicate scrolling forward (e.g. down in a vertical list); negative
+ * values, backward.
+ * <p>
+ * Values of 1.0 or -1.0 represent the maximum supported scroll.
+ * </p>
* @return this builder, to allow for chaining of calls
*/
public @NonNull Builder setScrollAmount(
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a0cf203..c0bd535 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -22,6 +22,8 @@
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
import static android.os.Trace.TRACE_TAG_VIEW;
+import static android.util.SequenceUtils.getInitSeq;
+import static android.util.SequenceUtils.isIncomingSeqNewer;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.DragEvent.ACTION_DRAG_LOCATION;
@@ -128,6 +130,7 @@
import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
import static com.android.window.flags.Flags.insetsControlChangedItem;
+import static com.android.window.flags.Flags.insetsControlSeq;
import static com.android.window.flags.Flags.setScPropertiesInClient;
import android.Manifest;
@@ -892,6 +895,12 @@
/** Non-{@code null} if {@link #mActivityConfigCallback} is not {@code null}. */
@Nullable
private ActivityWindowInfo mLastReportedActivityWindowInfo;
+ @Nullable
+ private final ClientWindowFrames mLastReportedFrames = insetsControlSeq()
+ ? new ClientWindowFrames()
+ : null;
+ private int mLastReportedInsetsStateSeq = getInitSeq();
+ private int mLastReportedActiveControlsSeq = getInitSeq();
boolean mScrollMayChange;
@SoftInputModeFlags
@@ -1596,8 +1605,6 @@
attachedFrame = null;
}
if (mTranslator != null) {
- mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
- mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls.get());
mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
mTmpFrames.attachedFrame = attachedFrame;
@@ -1620,8 +1627,7 @@
mAttachInfo.mAlwaysConsumeSystemBars =
(res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0;
mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
- mInsetsController.onStateChanged(mTempInsets);
- mInsetsController.onControlsChanged(mTempControls.get());
+ handleInsetsControlChanged(mTempInsets, mTempControls);
final InsetsState state = mInsetsController.getState();
final Rect displayCutoutSafe = mTempRect;
state.getDisplayCutoutSafe(displayCutoutSafe);
@@ -2219,17 +2225,18 @@
return;
}
+ onClientWindowFramesChanged(frames);
+
CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration);
final Rect frame = frames.frame;
final Rect displayFrame = frames.displayFrame;
final Rect attachedFrame = frames.attachedFrame;
if (mTranslator != null) {
- mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
mTranslator.translateRectInScreenToAppWindow(frame);
mTranslator.translateRectInScreenToAppWindow(displayFrame);
mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
- mInsetsController.onStateChanged(insetsState);
+ onInsetsStateChanged(insetsState);
final float compatScale = frames.compatScale;
final boolean frameChanged = !mWinFrame.equals(frame);
final boolean shouldReportActivityWindowInfoChanged =
@@ -2294,26 +2301,69 @@
}
/** Handles messages {@link #MSG_INSETS_CONTROL_CHANGED}. */
- private void handleInsetsControlChanged(@NonNull InsetsState insetsState,
+ @VisibleForTesting
+ public void handleInsetsControlChanged(@NonNull InsetsState insetsState,
@NonNull InsetsSourceControl.Array activeControls) {
- final InsetsSourceControl[] controls = activeControls.get();
-
- if (mTranslator != null) {
- mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
- mTranslator.translateSourceControlsInScreenToAppWindow(controls);
- }
-
// Deliver state change before control change, such that:
// a) When gaining control, controller can compare with server state to evaluate
// whether it needs to run animation.
// b) When loosing control, controller can restore server state by taking last
// dispatched state as truth.
- mInsetsController.onStateChanged(insetsState);
- if (mAdded) {
- mInsetsController.onControlsChanged(controls);
- } else {
- activeControls.release();
+ onInsetsStateChanged(insetsState);
+ onActiveControlsChanged(activeControls);
+ }
+
+ private void onClientWindowFramesChanged(@NonNull ClientWindowFrames inOutFrames) {
+ if (mLastReportedFrames == null) {
+ return;
}
+ if (isIncomingSeqNewer(mLastReportedFrames.seq, inOutFrames.seq)) {
+ // Keep track of the latest.
+ mLastReportedFrames.setTo(inOutFrames);
+ } else {
+ // If the last reported frames is newer, use the last reported instead.
+ inOutFrames.setTo(mLastReportedFrames);
+ }
+ }
+
+ private void onInsetsStateChanged(@NonNull InsetsState insetsState) {
+ if (insetsControlSeq()) {
+ if (isIncomingSeqNewer(mLastReportedInsetsStateSeq, insetsState.getSeq())) {
+ mLastReportedInsetsStateSeq = insetsState.getSeq();
+ } else {
+ // The last reported InsetsState is newer. Skip.
+ return;
+ }
+ }
+
+ if (mTranslator != null) {
+ mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
+ }
+ mInsetsController.onStateChanged(insetsState);
+ }
+
+ private void onActiveControlsChanged(@NonNull InsetsSourceControl.Array activeControls) {
+ if (!mAdded) {
+ // Do not update the last report if window is not added yet.
+ activeControls.release();
+ return;
+ }
+
+ if (insetsControlSeq()) {
+ if (isIncomingSeqNewer(mLastReportedActiveControlsSeq, activeControls.getSeq())) {
+ mLastReportedActiveControlsSeq = activeControls.getSeq();
+ } else {
+ // The last reported controls is newer. Skip.
+ activeControls.release();
+ return;
+ }
+ }
+
+ final InsetsSourceControl[] controls = activeControls.get();
+ if (mTranslator != null) {
+ mTranslator.translateSourceControlsInScreenToAppWindow(controls);
+ }
+ mInsetsController.onControlsChanged(controls);
}
private final DisplayListener mDisplayListener = new DisplayListener() {
@@ -9268,6 +9318,8 @@
mRelayoutSeq, mLastSyncSeqId, mRelayoutResult);
mRelayoutRequested = true;
+ onClientWindowFramesChanged(mTmpFrames);
+
if (activityWindowInfoFlag() && mPendingActivityWindowInfo != null) {
final ActivityWindowInfo outInfo = mRelayoutResult.activityWindowInfo;
if (outInfo != null) {
@@ -9284,13 +9336,10 @@
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
- mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
- mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls.get());
}
mInvCompatScale = 1f / mTmpFrames.compatScale;
CompatibilityInfo.applyOverrideScaleIfNeeded(mPendingMergedConfiguration);
- mInsetsController.onStateChanged(mTempInsets);
- mInsetsController.onControlsChanged(mTempControls.get());
+ handleInsetsControlChanged(mTempInsets, mTempControls);
mPendingAlwaysConsumeSystemBars =
(relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 618f622..ab04851 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -160,8 +160,20 @@
*/
public static final int CUJ_DESKTOP_MODE_RESIZE_WINDOW = 106;
+ /** Track entering desktop mode interaction. */
+ public static final int CUJ_DESKTOP_MODE_ENTER_MODE = 107;
+
+ /** Track exiting desktop mode interaction. */
+ public static final int CUJ_DESKTOP_MODE_EXIT_MODE = 108;
+
+ /** Track minimize window interaction in desktop mode. */
+ public static final int CUJ_DESKTOP_MODE_MINIMIZE_WINDOW = 109;
+
+ /** Track window drag interaction in desktop mode. */
+ public static final int CUJ_DESKTOP_MODE_DRAG_WINDOW = 110;
+
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_RESIZE_WINDOW;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_DRAG_WINDOW;
/** @hide */
@IntDef({
@@ -259,7 +271,11 @@
CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK,
CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW,
CUJ_FOLD_ANIM,
- CUJ_DESKTOP_MODE_RESIZE_WINDOW
+ CUJ_DESKTOP_MODE_RESIZE_WINDOW,
+ CUJ_DESKTOP_MODE_ENTER_MODE,
+ CUJ_DESKTOP_MODE_EXIT_MODE,
+ CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
+ CUJ_DESKTOP_MODE_DRAG_WINDOW
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -368,6 +384,10 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MAXIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_FOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__FOLD_ANIM;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_RESIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_RESIZE_WINDOW;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MINIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MINIMIZE_WINDOW;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_DRAG_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_DRAG_WINDOW;
}
private Cuj() {
@@ -576,6 +596,14 @@
return "FOLD_ANIM";
case CUJ_DESKTOP_MODE_RESIZE_WINDOW:
return "DESKTOP_MODE_RESIZE_WINDOW";
+ case CUJ_DESKTOP_MODE_ENTER_MODE:
+ return "DESKTOP_MODE_ENTER_MODE";
+ case CUJ_DESKTOP_MODE_EXIT_MODE:
+ return "DESKTOP_MODE_EXIT_MODE";
+ case CUJ_DESKTOP_MODE_MINIMIZE_WINDOW:
+ return "DESKTOP_MODE_MINIMIZE_WINDOW";
+ case CUJ_DESKTOP_MODE_DRAG_WINDOW:
+ return "DESKTOP_MODE_DRAG_WINDOW";
}
return "UNKNOWN";
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index b153700..9337bf6 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -16,13 +16,6 @@
package android.view;
-import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
-import static android.view.flags.Flags.FLAG_ADD_SCHANDLE_TO_VRI_SURFACE;
-import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
-import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
-import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY;
-import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY;
-import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
@@ -44,6 +37,13 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
+import static android.view.flags.Flags.FLAG_ADD_SCHANDLE_TO_VRI_SURFACE;
+import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY;
+import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY;
+import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
import static android.view.flags.Flags.toolkitFrameRateBySizeReadOnly;
import static android.view.flags.Flags.toolkitFrameRateDefaultNormalReadOnly;
import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly;
@@ -53,6 +53,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -63,9 +64,11 @@
import android.app.UiModeManager;
import android.content.Context;
import android.graphics.ForceDarkType;
+import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.SystemProperties;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -1540,6 +1543,37 @@
nativeCreateASurfaceControlFromSurface(mViewRootImpl.mSurface));
}
+ @EnableFlags(Flags.FLAG_INSETS_CONTROL_SEQ)
+ @Test
+ public void testHandleInsetsControlChanged() {
+ mView = new View(sContext);
+ attachViewToWindow(mView);
+
+ mViewRootImpl = mView.getViewRootImpl();
+ final InsetsController controller = mViewRootImpl.getInsetsController();
+
+ final InsetsState state0 = new InsetsState();
+ final InsetsState state1 = new InsetsState();
+ state0.setDisplayFrame(new Rect(0, 0, 500, 1000));
+ state0.setSeq(10000);
+ state1.setDisplayFrame(new Rect(0, 0, 1500, 2000));
+ state1.setSeq(10001);
+ final InsetsSourceControl.Array array = new InsetsSourceControl.Array();
+
+ sInstrumentation.runOnMainSync(() -> {
+ mViewRootImpl.handleInsetsControlChanged(state0, array);
+ assertEquals(state0, controller.getLastDispatchedState());
+
+ mViewRootImpl.handleInsetsControlChanged(state1, array);
+ assertEquals(state1, controller.getLastDispatchedState());
+
+ // Skip the stale value.
+ mViewRootImpl.handleInsetsControlChanged(state0, array);
+ assertEquals(state1, controller.getLastDispatchedState());
+ assertNotEquals(state0, controller.getLastDispatchedState());
+ });
+ }
+
private boolean setForceDarkSysProp(boolean isForceDarkEnabled) {
try {
SystemProperties.set(
diff --git a/data/keyboards/Vendor_18d1_Product_4f60.idc b/data/keyboards/Vendor_18d1_Product_4f60.idc
new file mode 100644
index 0000000..b9fd406
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_4f60.idc
@@ -0,0 +1,18 @@
+# Copyright 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.
+
+# Increase palm thresholds, since this touchpad has a tendency to overstate
+# touch sizes.
+gestureProp.Palm_Width = 40.0
+gestureProp.Multiple_Palm_Width = 40.0
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 1fcfa7f..43cdcca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.dagger;
import android.annotation.Nullable;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.os.Handler;
@@ -514,6 +515,7 @@
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
DragAndDropController dragAndDropController,
Transitions transitions,
+ KeyguardManager keyguardManager,
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
@@ -528,7 +530,7 @@
Optional<RecentTasksController> recentTasksController) {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
- dragAndDropController, transitions, enterDesktopTransitionHandler,
+ dragAndDropController, transitions, keyguardManager, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler, desktopModeTaskRepository,
desktopModeLoggerTransitionObserver, launchAdjacentController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 1965382..5813f85 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -18,6 +18,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions
+import android.app.KeyguardManager
import android.app.PendingIntent
import android.app.TaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
@@ -108,6 +109,7 @@
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
private val dragAndDropController: DragAndDropController,
private val transitions: Transitions,
+ private val keyguardManager: KeyguardManager,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
@@ -972,6 +974,12 @@
transition: IBinder
): WindowContainerTransaction? {
KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
+ if (keyguardManager.isKeyguardLocked) {
+ // Do NOT handle freeform task launch when locked.
+ // It will be launched in fullscreen windowing mode (Details: b/160925539)
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked")
+ return null
+ }
if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 7c5f10a..8ee72b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -76,21 +76,40 @@
continue
}
+ // Filter out changes that we care about
if (change.mode == WindowManager.TRANSIT_OPEN) {
change.taskInfo?.let { taskInfoList.add(it) }
transitionTypeList.add(change.mode)
}
}
- transitionToTransitionChanges.put(
- transition,
- TransitionChanges(taskInfoList, transitionTypeList)
- )
+ // Only add the transition to map if it has a change we care about
+ if (taskInfoList.isNotEmpty()) {
+ transitionToTransitionChanges.put(
+ transition,
+ TransitionChanges(taskInfoList, transitionTypeList)
+ )
+ }
}
}
override fun onTransitionStarting(transition: IBinder) {}
- override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
+ override fun onTransitionMerged(merged: IBinder, playing: IBinder) {
+ val mergedTransitionChanges =
+ transitionToTransitionChanges.get(merged)
+ ?:
+ // We are adding changes of the merged transition to changes of the playing
+ // transition so if there is no changes nothing to do.
+ return
+
+ transitionToTransitionChanges.remove(merged)
+ val playingTransitionChanges = transitionToTransitionChanges.get(playing)
+ if (playingTransitionChanges != null) {
+ playingTransitionChanges.merge(mergedTransitionChanges)
+ } else {
+ transitionToTransitionChanges.put(playing, mergedTransitionChanges)
+ }
+ }
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
val taskInfoList =
@@ -138,6 +157,11 @@
private data class TransitionChanges(
val taskInfoList: MutableList<RunningTaskInfo> = ArrayList(),
- val transitionTypeList: MutableList<Int> = ArrayList()
- )
+ val transitionTypeList: MutableList<Int> = ArrayList(),
+ ) {
+ fun merge(transitionChanges: TransitionChanges) {
+ taskInfoList.addAll(transitionChanges.taskInfoList)
+ transitionTypeList.addAll(transitionChanges.transitionTypeList)
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 14fa0f1..0e53e10 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -18,6 +18,7 @@
import android.app.ActivityManager.RecentTaskInfo
import android.app.ActivityManager.RunningTaskInfo
+import android.app.KeyguardManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -149,6 +150,7 @@
@Mock lateinit var syncQueue: SyncTransactionQueue
@Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock lateinit var transitions: Transitions
+ @Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
@Mock
@@ -233,6 +235,7 @@
rootTaskDisplayAreaOrganizer,
dragAndDropController,
transitions,
+ keyguardManager,
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
toggleResizeDesktopTaskTransitionHandler,
@@ -1301,6 +1304,17 @@
}
@Test
+ fun handleRequest_freeformTask_keyguardLocked_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+ val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNull(result, "Should NOT handle request")
+ }
+
+ @Test
fun handleRequest_notOpenOrToFrontTransition_returnNull() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
index f959970..0e5efa6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
@@ -48,7 +48,6 @@
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-
/**
* Test class for {@link TaskStackTransitionObserver}
*
@@ -168,6 +167,80 @@
.isEqualTo(freeformOpenChange.taskInfo?.windowingMode)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun transitionMerged_withChange_onlyOpenChangeIsNotified() {
+ val listener = TestListener()
+ val executor = TestShellExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Create open transition
+ val change =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build()
+
+ // create change transition to be merged to above transition
+ val mergedChange =
+ createChange(
+ WindowManager.TRANSIT_CHANGE,
+ createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val mergedTransitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0).addChange(mergedChange).build()
+ val mergedTransition = Mockito.mock(IBinder::class.java)
+
+ callOnTransitionReady(transitionInfo)
+ callOnTransitionReady(mergedTransitionInfo, mergedTransition)
+ callOnTransitionMerged(mergedTransition)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(change.taskInfo?.taskId)
+ assertThat(listener.taskInfoToBeNotified.windowingMode)
+ .isEqualTo(change.taskInfo?.windowingMode)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun transitionMerged_withOpen_lastOpenChangeIsNotified() {
+ val listener = TestListener()
+ val executor = TestShellExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Create open transition
+ val change =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build()
+
+ // create change transition to be merged to above transition
+ val mergedChange =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val mergedTransitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(mergedChange).build()
+ val mergedTransition = Mockito.mock(IBinder::class.java)
+
+ callOnTransitionReady(transitionInfo)
+ callOnTransitionReady(mergedTransitionInfo, mergedTransition)
+ callOnTransitionMerged(mergedTransition)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(mergedChange.taskInfo?.taskId)
+ assertThat(listener.taskInfoToBeNotified.windowingMode)
+ .isEqualTo(mergedChange.taskInfo?.windowingMode)
+ }
+
class TestListener : TaskStackTransitionObserver.TaskStackTransitionObserverListener {
var taskInfoToBeNotified = ActivityManager.RunningTaskInfo()
@@ -179,11 +252,14 @@
}
/** Simulate calling the onTransitionReady() method */
- private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
+ private fun callOnTransitionReady(
+ transitionInfo: TransitionInfo,
+ transition: IBinder = mockTransitionBinder
+ ) {
val startT = Mockito.mock(SurfaceControl.Transaction::class.java)
val finishT = Mockito.mock(SurfaceControl.Transaction::class.java)
- transitionObserver.onTransitionReady(mockTransitionBinder, transitionInfo, startT, finishT)
+ transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
}
/** Simulate calling the onTransitionFinished() method */
@@ -191,6 +267,11 @@
transitionObserver.onTransitionFinished(mockTransitionBinder, false)
}
+ /** Simulate calling the onTransitionMerged() method */
+ private fun callOnTransitionMerged(merged: IBinder, playing: IBinder = mockTransitionBinder) {
+ transitionObserver.onTransitionMerged(merged, playing)
+ }
+
companion object {
fun createTaskInfo(taskId: Int, windowingMode: Int): ActivityManager.RunningTaskInfo {
val taskInfo = ActivityManager.RunningTaskInfo()
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 3ba0d59..8acaf3b 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5143,9 +5143,9 @@
* of negative QP and positive QP are chosen wisely, the overall viewing experience can be
* improved.
* <p>
- * If byte array size is too small than the expected size, components may ignore the
- * configuration silently. If the byte array exceeds the expected size, components shall use
- * the initial portion and ignore the rest.
+ * If byte array size is smaller than the expected size, components will ignore the
+ * configuration and print an error message. If the byte array exceeds the expected size,
+ * components will use the initial portion and ignore the rest.
* <p>
* The scope of this key is throughout the encoding session until it is reconfigured during
* running state.
@@ -5159,7 +5159,8 @@
* Set the region of interest as QpOffset-Rects on the next queued input frame.
* <p>
* The associated value is a String in the format "Top1,Left1-Bottom1,Right1=Offset1;Top2,
- * Left2-Bottom2,Right2=Offset2;...". Co-ordinates (Top, Left), (Top, Right), (Bottom, Left)
+ * Left2-Bottom2,Right2=Offset2;...". If the configuration doesn't follow this pattern,
+ * it will be ignored. Co-ordinates (Top, Left), (Top, Right), (Bottom, Left)
* and (Bottom, Right) form the vertices of bounding box of region of interest in pixels.
* Pixel (0, 0) points to the top-left corner of the frame. Offset is the suggested
* quantization parameter (QP) offset of the blocks in the bounding box. The bounding box
@@ -5171,9 +5172,10 @@
* negative QP and positive QP are chosen wisely, the overall viewing experience can be
* improved.
* <p>
- * If Roi rect is not valid that is bounding box width is < 0 or bounding box height is < 0,
- * components may ignore the configuration silently. If Roi rect extends outside frame
- * boundaries, then rect shall be clamped to the frame boundaries.
+ * If roi (region of interest) rect is outside the frame boundaries, that is, left < 0 or
+ * top < 0 or right > width or bottom > height, then rect shall be clamped to the frame
+ * boundaries. If roi rect is not valid, that is left > right or top > bottom, then the
+ * parameter setting is ignored.
* <p>
* The scope of this key is throughout the encoding session until it is reconfigured during
* running state.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
index 7669e79b..f8c3a93 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
@@ -1,9 +1,4 @@
# Default reviewers for this and subdirectories.
-siyuanh@google.com
-hughchen@google.com
-timhypeng@google.com
-robertluo@google.com
-songferngwang@google.com
yqian@google.com
chelseahao@google.com
yiyishen@google.com
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index c5e86b4..4f2329b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -327,4 +327,12 @@
return sInstance;
}
}
+
+ /** Testing only. Reset the instance to avoid tests affecting each other. */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ public static void resetInstance() {
+ synchronized (PowerAllowlistBackend.class) {
+ sInstance = null;
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 8ec5ba1..837c682 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -46,6 +46,7 @@
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -98,6 +99,7 @@
private val contentResolver: ContentResolver,
private val backgroundCoroutineContext: CoroutineContext,
private val coroutineScope: CoroutineScope,
+ private val logger: Logger,
) : AudioRepository {
private val streamSettingNames: Map<AudioStream, String> =
@@ -170,6 +172,7 @@
.conflate()
.map { getCurrentAudioStream(audioStream) }
.onStart { emit(getCurrentAudioStream(audioStream)) }
+ .onEach { logger.onVolumeUpdateReceived(audioStream, it) }
.flowOn(backgroundCoroutineContext)
}
@@ -193,6 +196,7 @@
override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
withContext(backgroundCoroutineContext) {
+ logger.onSetVolumeRequested(audioStream, volume)
audioManager.setStreamVolume(audioStream.value, volume, 0)
}
}
@@ -247,4 +251,11 @@
awaitClose { contentResolver.unregisterContentObserver(observer) }
}
}
+
+ interface Logger {
+
+ fun onSetVolumeRequested(audioStream: AudioStream, volume: Int)
+
+ fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel)
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
index 9c48299..c8e4d71 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.volume.shared.model
import android.media.AudioManager
+import android.media.AudioSystem
/** Type-safe wrapper for [AudioManager] audio stream. */
@JvmInline
@@ -25,6 +26,8 @@
require(value in supportedStreamTypes) { "Unsupported stream=$value" }
}
+ override fun toString(): String = AudioSystem.streamToString(value)
+
companion object {
val supportedStreamTypes =
setOf(
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 844dc12..0e43acb 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -64,6 +64,7 @@
@Mock private lateinit var communicationDevice: AudioDeviceInfo
@Mock private lateinit var contentResolver: ContentResolver
+ private val logger = FakeAudioRepositoryLogger()
private val eventsReceiver = FakeAudioManagerEventsReceiver()
private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
@@ -109,6 +110,7 @@
contentResolver,
testScope.testScheduler,
testScope.backgroundScope,
+ logger,
)
}
@@ -173,6 +175,15 @@
underTest.setVolume(audioStream, 50)
runCurrent()
+ assertThat(logger.logs)
+ .isEqualTo(
+ listOf(
+ "onVolumeUpdateReceived audioStream=STREAM_SYSTEM",
+ "onSetVolumeRequested audioStream=STREAM_SYSTEM",
+ "onVolumeUpdateReceived audioStream=STREAM_SYSTEM",
+ "onVolumeUpdateReceived audioStream=STREAM_SYSTEM",
+ )
+ )
assertThat(streamModel)
.isEqualTo(
AudioStreamModel(
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt
new file mode 100644
index 0000000..389bf53
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.settingslib.volume.data.repository
+
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+
+class FakeAudioRepositoryLogger : AudioRepositoryImpl.Logger {
+
+ private val mutableLogs: MutableList<String> = mutableListOf()
+ val logs: List<String>
+ get() = mutableLogs
+
+ override fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) {
+ synchronized(mutableLogs) {
+ mutableLogs.add("onSetVolumeRequested audioStream=$audioStream")
+ }
+ }
+
+ override fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel) {
+ synchronized(mutableLogs) {
+ mutableLogs.add("onVolumeUpdateReceived audioStream=$audioStream")
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index c7fde48..52b0b87 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -674,4 +674,11 @@
return factory.create("DeviceEntryIconLog", 100);
}
+ /** Provides a {@link LogBuffer} for use by the volume loggers. */
+ @Provides
+ @SysUISingleton
+ @VolumeLog
+ public static LogBuffer provideVolumeLogBuffer(LogBufferFactory factory) {
+ return factory.create("VolumeLog", 50);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/VolumeLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/VolumeLog.kt
new file mode 100644
index 0000000..bc3858a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/VolumeLog.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for volume. */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class VolumeLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 96b1cf2..646d0b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1477,7 +1477,7 @@
}
if (hasRemoteInput) {
result.mView.setWrapper(wrapper);
- result.mView.addOnVisibilityChangedListener(this::setRemoteInputVisible);
+ result.mView.setOnVisibilityChangedListener(this::setRemoteInputVisible);
if (existingPendingIntent != null || result.mView.isActive()) {
// The current action could be gone, or the pending intent no longer valid.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 1fc7bf4..31776cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -115,7 +115,7 @@
private final SendButtonTextWatcher mTextWatcher;
private final TextView.OnEditorActionListener mEditorActionHandler;
private final ArrayList<Runnable> mOnSendListeners = new ArrayList<>();
- private final ArrayList<Consumer<Boolean>> mOnVisibilityChangedListeners = new ArrayList<>();
+ private Consumer<Boolean> mOnVisibilityChangedListener = null;
private final ArrayList<OnFocusChangeListener> mEditTextFocusChangeListeners =
new ArrayList<>();
@@ -733,24 +733,17 @@
* {@link #getVisibility()} would return {@link View#VISIBLE}, and {@code false} it would return
* any other value.
*/
- public void addOnVisibilityChangedListener(Consumer<Boolean> listener) {
- mOnVisibilityChangedListeners.add(listener);
- }
-
- /**
- * Unregister a listener previously registered via
- * {@link #addOnVisibilityChangedListener(Consumer)}.
- */
- public void removeOnVisibilityChangedListener(Consumer<Boolean> listener) {
- mOnVisibilityChangedListeners.remove(listener);
+ public void setOnVisibilityChangedListener(Consumer<Boolean> listener) {
+ mOnVisibilityChangedListener = listener;
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (changedView == this) {
- for (Consumer<Boolean> listener : new ArrayList<>(mOnVisibilityChangedListeners)) {
- listener.accept(visibility == VISIBLE);
+ final Consumer<Boolean> visibilityChangedListener = mOnVisibilityChangedListener;
+ if (visibilityChangedListener != null) {
+ visibilityChangedListener.accept(visibility == VISIBLE);
}
// Hide soft-keyboard when the input view became invisible
// (i.e. The notification shade collapsed by pressing the home key)
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
index 2797b8d..e9f4374 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -36,10 +36,10 @@
import com.android.internal.logging.InstanceId
import com.android.internal.logging.InstanceIdSequence
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.DebugLogger.debugLog
+import com.android.systemui.res.R
import com.android.systemui.shared.hardware.hasInputDevice
import com.android.systemui.shared.hardware.isAnyStylusSource
import com.android.systemui.util.NotificationChannels
@@ -65,8 +65,10 @@
private var batteryCapacity = 1.0f
private var suppressed = false
private var instanceId: InstanceId? = null
- @VisibleForTesting var inputDeviceId: Int? = null
- private set
+ @VisibleForTesting
+ var inputDeviceId: Int? = null
+ private set
+
@VisibleForTesting var instanceIdSequence = InstanceIdSequence(1 shl 13)
fun init() {
@@ -113,7 +115,7 @@
inputDeviceId = deviceId
if (batteryState.capacity == batteryCapacity || batteryState.capacity <= 0f)
return@updateBattery
-
+ // Note that batteryState.capacity == NaN will fall through to here
batteryCapacity = batteryState.capacity
debugLog {
"Updating notification battery state to $batteryCapacity " +
@@ -172,7 +174,7 @@
}
private fun isBatteryBelowThreshold(): Boolean {
- return batteryCapacity <= LOW_BATTERY_THRESHOLD
+ return !batteryCapacity.isNaN() && batteryCapacity <= LOW_BATTERY_THRESHOLD
}
private fun hasConnectedBluetoothStylus(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 1ae5614..2e7b05a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -32,6 +32,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.volume.shared.VolumeLogger
import dagger.Module
import dagger.Provides
import kotlin.coroutines.CoroutineContext
@@ -58,6 +59,7 @@
contentResolver: ContentResolver,
@Background coroutineContext: CoroutineContext,
@Application coroutineScope: CoroutineScope,
+ volumeLogger: VolumeLogger,
): AudioRepository =
AudioRepositoryImpl(
intentsReceiver,
@@ -65,6 +67,7 @@
contentResolver,
coroutineContext,
coroutineScope,
+ volumeLogger,
)
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index c18573e..521f608 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -26,6 +26,7 @@
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
+import com.android.systemui.volume.panel.shared.VolumePanelLogger
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -51,6 +52,7 @@
private val context: Context,
private val audioVolumeInteractor: AudioVolumeInteractor,
private val uiEventLogger: UiEventLogger,
+ private val volumePanelLogger: VolumePanelLogger,
) : SliderViewModel {
private val volumeChanges = MutableStateFlow<Int?>(null)
@@ -105,6 +107,7 @@
audioVolumeInteractor.canChangeVolume(audioStream),
audioVolumeInteractor.ringerMode,
) { model, isEnabled, ringerMode ->
+ volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume)
model.toState(isEnabled, ringerMode)
}
.stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
@@ -112,7 +115,10 @@
init {
volumeChanges
.filterNotNull()
- .onEach { audioVolumeInteractor.setVolume(audioStream, it) }
+ .onEach {
+ volumePanelLogger.onSetVolumeRequested(audioStream, it)
+ audioVolumeInteractor.setVolume(audioStream, it)
+ }
.launchIn(coroutineScope)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt
new file mode 100644
index 0000000..cc513b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.panel.shared
+
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.VolumeLog
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+
+private const val TAG = "SysUI_VolumePanel"
+
+/** Logs events related to the Volume Panel. */
+@VolumePanelScope
+class VolumePanelLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) {
+
+ fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = audioStream.toString()
+ int1 = volume
+ },
+ { "Set volume: stream=$str1 volume=$int1" }
+ )
+ }
+
+ fun onVolumeUpdateReceived(audioStream: AudioStream, volume: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = audioStream.toString()
+ int1 = volume
+ },
+ { "Volume update received: stream=$str1 volume=$int1" }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt
new file mode 100644
index 0000000..869a82a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.shared
+
+import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.VolumeLog
+import javax.inject.Inject
+
+private const val TAG = "SysUI_Volume"
+
+/** Logs general System UI volume events. */
+@SysUISingleton
+class VolumeLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) :
+ AudioRepositoryImpl.Logger {
+
+ override fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = audioStream.toString()
+ int1 = volume
+ },
+ { "Set volume: stream=$str1 volume=$int1" }
+ )
+ }
+
+ override fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = audioStream.toString()
+ int1 = model.volume
+ },
+ { "Volume update received: stream=$str1 volume=$int1" }
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 70afbd8..ffe7750 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -247,7 +247,7 @@
ExpandableNotificationRow row = helper.createRow();
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
- view.addOnVisibilityChangedListener(null);
+ view.setOnVisibilityChangedListener(null);
view.setVisibility(View.INVISIBLE);
view.setVisibility(View.VISIBLE);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
index 5b9db4b..5603ff0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
@@ -31,8 +31,8 @@
import com.android.internal.logging.InstanceId
import com.android.internal.logging.UiEventLogger
import com.android.systemui.InstanceIdSequenceFake
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
@@ -109,6 +109,14 @@
}
@Test
+ fun updateBatteryState_capacityNaN_cancelsNotification() {
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(Float.NaN))
+
+ verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
+ verifyNoMoreInteractions(notificationManager)
+ }
+
+ @Test
fun updateBatteryState_capacityBelowThreshold_notifies() {
stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index 97688d5..ef2d4ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -29,10 +29,16 @@
import com.android.systemui.unfold.util.TestFoldStateProvider
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+/**
+ * This test class tests [PhysicsBasedUnfoldTransitionProgressProvider] in a more E2E
+ * fashion, it uses real handler thread and timings, so it might be perceptible to more flakiness
+ * compared to the other unit tests that do not perform real multithreaded interactions.
+ */
@RunWith(AndroidJUnit4::class)
@SmallTest
class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
@@ -44,8 +50,8 @@
mock<UnfoldFrameCallbackScheduler.Factory>().apply {
whenever(create()).then { UnfoldFrameCallbackScheduler() }
}
- private val mockBgHandler = mock<Handler>()
- private val fakeHandler = Handler(HandlerThread("UnfoldBg").apply { start() }.looper)
+ private val handlerThread = HandlerThread("UnfoldBg").apply { start() }
+ private val bgHandler = Handler(handlerThread.looper)
@Before
fun setUp() {
@@ -54,20 +60,26 @@
context,
schedulerFactory,
foldStateProvider = foldStateProvider,
- progressHandler = fakeHandler
+ progressHandler = bgHandler
)
progressProvider.addCallback(listener)
}
+ @After
+ fun after() {
+ handlerThread.quit()
+ }
+
@Test
fun testUnfold_emitsIncreasingTransitionEvents() {
runOnProgressThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
{ foldStateProvider.sendHingeAngleUpdate(10f) },
- { foldStateProvider.sendUnfoldedScreenAvailable() },
- { foldStateProvider.sendHingeAngleUpdate(90f) },
- { foldStateProvider.sendHingeAngleUpdate(180f) },
- { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
+ { foldStateProvider.sendUnfoldedScreenAvailable() }
+ )
+ sendHingeAngleAndEnsureAnimationUpdate(90f, 120f, 180f)
+ runOnProgressThreadWithInterval(
+ { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
)
with(listener.ensureTransitionFinished()) {
@@ -91,7 +103,7 @@
}
@Test
- fun testUnfold_screenAvailableOnlyAfterFullUnfold_emitsIncreasingTransitionEvents() {
+ fun testUnfold_screenAvailableOnlyAfterFullUnfold_finishesWithUnfoldEvent() {
runOnProgressThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
{ foldStateProvider.sendHingeAngleUpdate(10f) },
@@ -102,7 +114,6 @@
)
with(listener.ensureTransitionFinished()) {
- assertIncreasingProgress()
assertFinishedWithUnfold()
}
}
@@ -111,9 +122,9 @@
fun testFold_emitsDecreasingTransitionEvents() {
runOnProgressThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING) },
- { foldStateProvider.sendHingeAngleUpdate(170f) },
- { foldStateProvider.sendHingeAngleUpdate(90f) },
- { foldStateProvider.sendHingeAngleUpdate(10f) },
+ )
+ sendHingeAngleAndEnsureAnimationUpdate(170f, 90f, 10f)
+ runOnProgressThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) },
)
@@ -127,9 +138,9 @@
fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() {
runOnProgressThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
- { foldStateProvider.sendUnfoldedScreenAvailable() },
- { foldStateProvider.sendHingeAngleUpdate(10f) },
- { foldStateProvider.sendHingeAngleUpdate(90f) },
+ { foldStateProvider.sendUnfoldedScreenAvailable() })
+ sendHingeAngleAndEnsureAnimationUpdate(10f, 50f, 90f)
+ runOnProgressThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) },
)
@@ -159,12 +170,22 @@
with(listener.ensureTransitionFinished()) { assertHasFoldAnimationAtTheEnd() }
}
+ private fun sendHingeAngleAndEnsureAnimationUpdate(vararg angles: Float) {
+ angles.forEach { angle ->
+ listener.waitForProgressChangeAfter {
+ bgHandler.post {
+ foldStateProvider.sendHingeAngleUpdate(angle)
+ }
+ }
+ }
+ }
+
private fun runOnProgressThreadWithInterval(
vararg blocks: () -> Unit,
intervalMillis: Long = 60,
) {
blocks.forEach {
- fakeHandler.post(it)
+ bgHandler.post(it)
Thread.sleep(intervalMillis)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
index bbc96f70..6e8bf85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
@@ -68,6 +68,24 @@
return recordings.first()
}
+ /**
+ * Number of progress event for the currently running transition
+ * Returns null if there is no currently running transition
+ */
+ val currentTransitionProgressEventCount: Int?
+ get() = currentRecording?.progressHistory?.size
+
+ /**
+ * Runs [block] and ensures that there was at least once onTransitionProgress event after that
+ */
+ fun waitForProgressChangeAfter(block: () -> Unit) {
+ val eventCount = currentTransitionProgressEventCount
+ block()
+ waitForCondition {
+ currentTransitionProgressEventCount != eventCount
+ }
+ }
+
fun assertStarted() {
assertWithMessage("Transition didn't start").that(currentRecording).isNotNull()
}
@@ -86,7 +104,7 @@
}
class UnfoldTransitionRecording {
- private val progressHistory: MutableList<Float> = arrayListOf()
+ val progressHistory: MutableList<Float> = arrayListOf()
private var finishingInvocations: Int = 0
fun addProgress(progress: Float) {
@@ -142,6 +160,6 @@
}
private companion object {
- private const val MIN_ANIMATION_EVENTS = 5
+ private const val MIN_ANIMATION_EVENTS = 3
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
index b2b19de..e6b52f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
@@ -20,6 +20,7 @@
import com.android.internal.logging.uiEventLogger
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
+import com.android.systemui.volume.shared.volumePanelLogger
import kotlinx.coroutines.CoroutineScope
val Kosmos.audioStreamSliderViewModelFactory by
@@ -36,6 +37,7 @@
applicationContext,
audioVolumeInteractor,
uiEventLogger,
+ volumePanelLogger,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/shared/VolumePanelLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/shared/VolumePanelLoggerKosmos.kt
new file mode 100644
index 0000000..3a7574d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/shared/VolumePanelLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.shared
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.volume.panel.shared.VolumePanelLogger
+
+val Kosmos.volumePanelLogger by Kosmos.Fixture { VolumePanelLogger(logcatLogBuffer()) }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 04c4284..a72259e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -185,8 +185,8 @@
private final SparseIntArray mDevicePolicies;
@GuardedBy("mVirtualDeviceLock")
private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>();
- private final IVirtualDeviceActivityListener mActivityListener;
- private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
+ private IVirtualDeviceActivityListener mActivityListener;
+ private IVirtualDeviceSoundEffectListener mSoundEffectListener;
private final DisplayManagerGlobal mDisplayManager;
private final DisplayManagerInternal mDisplayManagerInternal;
@GuardedBy("mVirtualDeviceLock")
@@ -303,7 +303,9 @@
UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(attributionSource.getUid());
mContext = context.createContextAsUser(ownerUserHandle, 0);
mAssociationInfo = associationInfo;
- mPersistentDeviceId = createPersistentDeviceId(associationInfo.getId());
+ mPersistentDeviceId = associationInfo == null
+ ? null
+ : createPersistentDeviceId(associationInfo.getId());
mService = service;
mPendingTrampolineCallback = pendingTrampolineCallback;
mActivityListener = activityListener;
@@ -405,7 +407,7 @@
/** Returns the device display name. */
CharSequence getDisplayName() {
- return mAssociationInfo.getDisplayName();
+ return mAssociationInfo == null ? mParams.getName() : mAssociationInfo.getDisplayName();
}
/** Returns the public representation of the device. */
@@ -420,6 +422,22 @@
}
}
+ /**
+ * Setter for listeners that live in the client process, namely in
+ * {@link android.companion.virtual.VirtualDeviceInternal}.
+ *
+ * This is needed for virtual devices that are created by the system, as the VirtualDeviceImpl
+ * object is created before the returned VirtualDeviceInternal one.
+ */
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void setListeners(@NonNull IVirtualDeviceActivityListener activityListener,
+ @NonNull IVirtualDeviceSoundEffectListener soundEffectListener) {
+ super.setListeners_enforcePermission();
+ mActivityListener = Objects.requireNonNull(activityListener);
+ mSoundEffectListener = Objects.requireNonNull(soundEffectListener);
+ }
+
@Override // Binder call
public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
@VirtualDeviceParams.PolicyType int policyType) {
@@ -456,7 +474,9 @@
@Override // Binder call
public int getAssociationId() {
- return mAssociationInfo.getId();
+ return mAssociationInfo == null
+ ? VirtualDeviceManagerService.CDM_ASSOCIATION_ID_NONE
+ : mAssociationInfo.getId();
}
@Override // Binder call
@@ -1140,7 +1160,7 @@
String indent = " ";
fout.println(" VirtualDevice: ");
fout.println(indent + "mDeviceId: " + mDeviceId);
- fout.println(indent + "mAssociationId: " + mAssociationInfo.getId());
+ fout.println(indent + "mAssociationId: " + getAssociationId());
fout.println(indent + "mOwnerPackageName: " + mOwnerPackageName);
fout.println(indent + "mParams: ");
mParams.dump(fout, indent + indent);
@@ -1286,8 +1306,7 @@
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
private void onActivityBlocked(int displayId, ActivityInfo activityInfo) {
- Intent intent = BlockedAppStreamingActivity.createIntent(
- activityInfo, mAssociationInfo.getDisplayName());
+ Intent intent = BlockedAppStreamingActivity.createIntent(activityInfo, getDisplayName());
mContext.startActivityAsUser(
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
@@ -1374,7 +1393,7 @@
@SuppressWarnings("AndroidFrameworkRequiresPermission")
private void checkVirtualInputDeviceDisplayIdAssociation(int displayId) {
- if (mContext.checkCallingPermission(android.Manifest.permission.INJECT_EVENTS)
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.INJECT_EVENTS)
== PackageManager.PERMISSION_GRANTED) {
// The INJECT_EVENTS permission allows for injecting input to any window / display.
return;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java
index c65aa5b..b0bacfd 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Process;
import android.util.SparseArray;
import java.io.PrintWriter;
@@ -35,6 +36,8 @@
"MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
private static final int MAX_ENTRIES = 16;
+ private static final String VIRTUAL_DEVICE_OWNER_SYSTEM = "system";
+
private final Context mContext;
private final ArrayDeque<LogEntry> mLogEntries = new ArrayDeque<>();
@@ -132,6 +135,8 @@
String[] packages;
if (mUidToPackagesCache.contains(ownerUid)) {
return mUidToPackagesCache.get(ownerUid);
+ } else if (ownerUid == Process.SYSTEM_UID) {
+ return VIRTUAL_DEVICE_OWNER_SYSTEM;
} else {
packages = mPackageManager.getPackagesForUid(ownerUid);
String packageName = "";
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 9ad73ca..1be1d2b 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -101,6 +101,11 @@
AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING);
+ /**
+ * A virtual device association id corresponding to no CDM association.
+ */
+ static final int CDM_ASSOCIATION_ID_NONE = 0;
+
private final Object mVirtualDeviceManagerLock = new Object();
private final VirtualDeviceManagerImpl mImpl;
private final VirtualDeviceManagerNativeImpl mNativeImpl;
@@ -316,7 +321,9 @@
for (int i = 0; i < mVirtualDevices.size(); i++) {
VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
- if (!activeAssociationIds.contains(virtualDevice.getAssociationId())) {
+ int deviceAssociationId = virtualDevice.getAssociationId();
+ if (deviceAssociationId != CDM_ASSOCIATION_ID_NONE
+ && !activeAssociationIds.contains(deviceAssociationId)) {
virtualDevicesToRemove.add(virtualDevice);
}
}
@@ -422,28 +429,39 @@
@NonNull IVirtualDeviceActivityListener activityListener,
@NonNull IVirtualDeviceSoundEffectListener soundEffectListener) {
createVirtualDevice_enforcePermission();
- attributionSource.enforceCallingUid();
-
- final int callingUid = getCallingUid();
+ Objects.requireNonNull(activityListener);
+ Objects.requireNonNull(soundEffectListener);
final String packageName = attributionSource.getPackageName();
- if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) {
- throw new SecurityException(
- "Package name " + packageName + " does not belong to calling uid "
- + callingUid);
- }
AssociationInfo associationInfo = getAssociationInfo(packageName, associationId);
if (associationInfo == null) {
throw new IllegalArgumentException("No association with ID " + associationId);
- }
- if (!VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES
+ } else if (!VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES
.contains(associationInfo.getDeviceProfile())
&& Flags.persistentDeviceIdApi()) {
throw new IllegalArgumentException("Unsupported CDM Association device profile "
+ associationInfo.getDeviceProfile() + " for virtual device creation.");
}
+ return createVirtualDevice(token, attributionSource, associationInfo, params,
+ activityListener, soundEffectListener);
+ }
+
+ private IVirtualDevice createVirtualDevice(
+ IBinder token,
+ AttributionSource attributionSource,
+ AssociationInfo associationInfo,
+ @NonNull VirtualDeviceParams params,
+ @Nullable IVirtualDeviceActivityListener activityListener,
+ @Nullable IVirtualDeviceSoundEffectListener soundEffectListener) {
+ createVirtualDevice_enforcePermission();
+ attributionSource.enforceCallingUid();
+
+ final String packageName = attributionSource.getPackageName();
+ if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) {
+ throw new SecurityException(
+ "Package name " + packageName + " does not belong to calling uid "
+ + getCallingUid());
+ }
Objects.requireNonNull(params);
- Objects.requireNonNull(activityListener);
- Objects.requireNonNull(soundEffectListener);
final UserHandle userHandle = getCallingUserHandle();
final CameraAccessController cameraAccessController =
@@ -724,6 +742,21 @@
private final ArraySet<Integer> mAllUidsOnVirtualDevice = new ArraySet<>();
@Override
+ public @NonNull VirtualDeviceManager.VirtualDevice createVirtualDevice(
+ @NonNull VirtualDeviceParams params) {
+ Objects.requireNonNull(params, "params must not be null");
+ Objects.requireNonNull(params.getName(), "virtual device name must not be null");
+ IVirtualDevice virtualDevice = mImpl.createVirtualDevice(
+ new Binder(),
+ getContext().getAttributionSource(),
+ /* associationInfo= */ null,
+ params,
+ /* activityListener= */ null,
+ /* soundEffectListener= */ null);
+ return new VirtualDeviceManager.VirtualDevice(mImpl, getContext(), virtualDevice);
+ }
+
+ @Override
public int getDeviceOwnerUid(int deviceId) {
VirtualDeviceImpl virtualDevice;
synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 27fda15..a8c269d 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2793,12 +2793,13 @@
return mDeviceInventory.getImmutableDeviceInventory();
}
- void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) {
- mDeviceInventory.addOrUpdateDeviceSAStateInInventory(deviceState);
+ void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState, boolean syncInventory) {
+ mDeviceInventory.addOrUpdateDeviceSAStateInInventory(deviceState, syncInventory);
}
- void addOrUpdateBtAudioDeviceCategoryInInventory(AdiDeviceState deviceState) {
- mDeviceInventory.addOrUpdateAudioDeviceCategoryInInventory(deviceState);
+ void addOrUpdateBtAudioDeviceCategoryInInventory(
+ AdiDeviceState deviceState, boolean syncInventory) {
+ mDeviceInventory.addOrUpdateAudioDeviceCategoryInInventory(deviceState, syncInventory);
}
@Nullable
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ba7aee0..6ff4a61 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -135,9 +135,10 @@
* AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list.
* @param deviceState the device to update
*/
- void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) {
+ void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState, boolean syncInventory) {
synchronized (mDeviceInventoryLock) {
- mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> {
+ mDeviceInventory.merge(deviceState.getDeviceId(), deviceState,
+ (oldState, newState) -> {
oldState.setHasHeadTracker(newState.hasHeadTracker());
oldState.setHeadTrackerEnabled(newState.isHeadTrackerEnabled());
oldState.setSAEnabled(newState.isSAEnabled());
@@ -145,7 +146,9 @@
});
checkDeviceInventorySize_l();
}
- mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
+ if (syncInventory) {
+ mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
+ }
}
/**
@@ -196,7 +199,8 @@
* AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list.
* @param deviceState the device to update
*/
- void addOrUpdateAudioDeviceCategoryInInventory(AdiDeviceState deviceState) {
+ void addOrUpdateAudioDeviceCategoryInInventory(
+ AdiDeviceState deviceState, boolean syncInventory) {
AtomicBoolean updatedCategory = new AtomicBoolean(false);
synchronized (mDeviceInventoryLock) {
if (automaticBtDeviceType()) {
@@ -218,7 +222,9 @@
if (updatedCategory.get()) {
mDeviceBroker.postUpdatedAdiDeviceState(deviceState, false /*initSA*/);
}
- mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
+ if (syncInventory) {
+ mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
+ }
}
void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address,
@@ -235,14 +241,14 @@
boolean bleCategoryFound = false;
AdiDeviceState deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLE_HEADSET);
if (deviceState != null) {
- addOrUpdateAudioDeviceCategoryInInventory(deviceState);
+ addOrUpdateAudioDeviceCategoryInInventory(deviceState, true /*syncInventory*/);
btCategory = deviceState.getAudioDeviceCategory();
bleCategoryFound = true;
}
deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLUETOOTH_A2DP);
if (deviceState != null) {
- addOrUpdateAudioDeviceCategoryInInventory(deviceState);
+ addOrUpdateAudioDeviceCategoryInInventory(deviceState, true /*syncInventory*/);
int a2dpCategory = deviceState.getAudioDeviceCategory();
if (bleCategoryFound && a2dpCategory != btCategory) {
Log.w(TAG, "Found different audio device category for A2DP and BLE profiles with "
@@ -269,23 +275,43 @@
}
/**
- * synchronize AdiDeviceState for LE devices in the same group
+ * Synchronize AdiDeviceState for LE devices in the same group
+ * or BT classic devices with the same address.
+ * @param updatedDevice the device state to synchronize or null.
+ * Called with null once after the device inventory and spatializer helper
+ * have been initialized to resync all devices.
*/
void onSynchronizeAdiDevicesInInventory(AdiDeviceState updatedDevice) {
synchronized (mDevicesLock) {
synchronized (mDeviceInventoryLock) {
- boolean found = false;
- found |= synchronizeBleDeviceInInventory(updatedDevice);
- if (automaticBtDeviceType()) {
- found |= synchronizeDeviceProfilesInInventory(updatedDevice);
- }
- if (found) {
- mDeviceBroker.postPersistAudioDeviceSettings();
+ if (updatedDevice != null) {
+ onSynchronizeAdiDeviceInInventory_l(updatedDevice);
+ } else {
+ for (AdiDeviceState ads : mDeviceInventory.values()) {
+ onSynchronizeAdiDeviceInInventory_l(ads);
+ }
}
}
}
}
+ /**
+ * Synchronize AdiDeviceState for LE devices in the same group
+ * or BT classic devices with the same address.
+ * @param updatedDevice the device state to synchronize.
+ */
+ @GuardedBy({"mDevicesLock", "mDeviceInventoryLock"})
+ void onSynchronizeAdiDeviceInInventory_l(AdiDeviceState updatedDevice) {
+ boolean found = false;
+ found |= synchronizeBleDeviceInInventory(updatedDevice);
+ if (automaticBtDeviceType()) {
+ found |= synchronizeDeviceProfilesInInventory(updatedDevice);
+ }
+ if (found) {
+ mDeviceBroker.postPersistAudioDeviceSettings();
+ }
+ }
+
@GuardedBy("mDeviceInventoryLock")
private void checkDeviceInventorySize_l() {
if (mDeviceInventory.size() > MAX_DEVICE_INVENTORY_ENTRIES) {
@@ -595,6 +621,9 @@
mDeviceName = TextUtils.emptyIfNull(deviceName);
mDeviceAddress = TextUtils.emptyIfNull(address);
mDeviceIdentityAddress = TextUtils.emptyIfNull(identityAddress);
+ if (mDeviceIdentityAddress.isEmpty()) {
+ mDeviceIdentityAddress = mDeviceAddress;
+ }
mDeviceCodecFormat = codecFormat;
mGroupId = groupId;
mPeerDeviceAddress = TextUtils.emptyIfNull(peerAddress);
@@ -2951,8 +2980,8 @@
// Note if the device is not compatible with spatialization mode or the device
// type is not canonical, it will be ignored in {@link SpatializerHelper}.
if (devState != null) {
- addOrUpdateDeviceSAStateInInventory(devState);
- addOrUpdateAudioDeviceCategoryInInventory(devState);
+ addOrUpdateDeviceSAStateInInventory(devState, false /*syncInventory*/);
+ addOrUpdateAudioDeviceCategoryInInventory(devState, false /*syncInventory*/);
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c89992d..dcce96b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9639,6 +9639,9 @@
case MSG_INIT_SPATIALIZER:
onInitSpatializer();
+ // the device inventory can only be synchronized after the
+ // spatializer has been initialized
+ mDeviceBroker.postSynchronizeAdiDevicesInInventory(null);
mAudioEventWakeLock.release();
break;
@@ -11394,7 +11397,8 @@
deviceState.setAudioDeviceCategory(btAudioDeviceCategory);
- mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState);
+ mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(
+ deviceState, true /*syncInventory*/);
mDeviceBroker.postPersistAudioDeviceSettings();
mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes(),
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index cae1695..9265ff2 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -568,7 +568,8 @@
updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(),
ada.getAddress());
initSAState(updatedDevice);
- mDeviceBroker.addOrUpdateDeviceSAStateInInventory(updatedDevice);
+ mDeviceBroker.addOrUpdateDeviceSAStateInInventory(
+ updatedDevice, true /*syncInventory*/);
}
if (updatedDevice != null) {
onRoutingUpdated();
@@ -723,7 +724,7 @@
new AdiDeviceState(canonicalDeviceType, ada.getInternalType(),
ada.getAddress());
initSAState(deviceState);
- mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState);
+ mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState, true /*syncInventory*/);
mDeviceBroker.postPersistAudioDeviceSettings();
logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later.
}
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index b179783..6e38733 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -20,6 +20,8 @@
import android.annotation.Nullable;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.VirtualDevice;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.sensor.VirtualSensor;
import android.content.Context;
import android.os.LocaleList;
@@ -180,4 +182,14 @@
* exists, as long as one may have existed or can be created.
*/
public abstract @NonNull Set<String> getAllPersistentDeviceIds();
+
+ /**
+ * Creates a virtual device where applications can launch and receive input events injected by
+ * the creator.
+ *
+ * <p>A Companion Device Manager association is not required. Only the system may create such
+ * virtual devices.</p>
+ */
+ public abstract @NonNull VirtualDeviceManager.VirtualDevice createVirtualDevice(
+ @NonNull VirtualDeviceParams params);
}
diff --git a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
new file mode 100644
index 0000000..4a66bac
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "postsubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.RescuePartyTest"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f72e82a..d845968 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3695,7 +3695,7 @@
void fillInsetsSourceControls(@NonNull InsetsSourceControl.Array outArray,
boolean copyControls) {
- final int lastSeq = mLastReportedInsetsState.getSeq();
+ final int lastSeq = mLastReportedActiveControls.getSeq();
final InsetsSourceControl[] controls =
getDisplayContent().getInsetsStateController().getControlsForDispatch(this);
outArray.set(controls, copyControls);