diff options
38 files changed, 928 insertions, 85 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b9d61cd334e3..35720fd17769 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3643,11 +3643,26 @@ package android.companion.virtual.sensor { method public int getDeviceId(); method @NonNull public String getName(); method public int getType(); + method @FlaggedApi("android.companion.virtualdevice.flags.virtual_sensor_additional_info") public void sendAdditionalInfo(@NonNull android.companion.virtual.sensor.VirtualSensorAdditionalInfo); method public void sendEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensor> CREATOR; } + @FlaggedApi("android.companion.virtualdevice.flags.virtual_sensor_additional_info") public final class VirtualSensorAdditionalInfo implements android.os.Parcelable { + method public int describeContents(); + method public int getType(); + method @NonNull public java.util.List<float[]> getValues(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorAdditionalInfo> CREATOR; + } + + public static final class VirtualSensorAdditionalInfo.Builder { + ctor public VirtualSensorAdditionalInfo.Builder(int); + method @NonNull public android.companion.virtual.sensor.VirtualSensorAdditionalInfo.Builder addValues(@NonNull float[]); + method @NonNull public android.companion.virtual.sensor.VirtualSensorAdditionalInfo build(); + } + public interface VirtualSensorCallback { method public void onConfigurationChanged(@NonNull android.companion.virtual.sensor.VirtualSensor, boolean, @NonNull java.time.Duration, @NonNull java.time.Duration); } @@ -3665,6 +3680,7 @@ package android.companion.virtual.sensor { method public float getResolution(); method public int getType(); method @Nullable public String getVendor(); + method @FlaggedApi("android.companion.virtualdevice.flags.virtual_sensor_additional_info") public boolean isAdditionalInfoSupported(); method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public boolean isWakeUpSensor(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR; @@ -3673,6 +3689,7 @@ package android.companion.virtual.sensor { public static final class VirtualSensorConfig.Builder { ctor public VirtualSensorConfig.Builder(@IntRange(from=1) int, @NonNull String); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build(); + method @FlaggedApi("android.companion.virtualdevice.flags.virtual_sensor_additional_info") @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setAdditionalInfoSupported(boolean); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setDirectChannelTypesSupported(int); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setHighestDirectReportRateLevel(int); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setMaxDelay(int); diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 5891bddfbbe6..6f0eafe487af 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -269,7 +269,7 @@ flag { namespace: "systemui" description: "enables metrics when redacting notifications on the lockscreen" bug: "343631648" - metadata { + metadata { purpose: PURPOSE_BUGFIX } } @@ -279,7 +279,16 @@ flag { namespace: "systemui" description: "enables user expanding the public view of a notification" bug: "398853084" - metadata { + metadata { + purpose: PURPOSE_BUGFIX + } + } +flag { + name: "notif_entry_creation_time_use_elapsed_realtime" + namespace: "systemui" + description: "makes the notification entry expect its creation time to be elapsedRealtime, not uptimeMillis" + bug: "343631648" + metadata { purpose: PURPOSE_BUGFIX } } diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index f8ac27de1754..db77adeb5a3d 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -24,6 +24,7 @@ import android.companion.virtual.IVirtualDeviceSoundEffectListener; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; import android.companion.virtual.sensor.VirtualSensor; +import android.companion.virtual.sensor.VirtualSensorAdditionalInfo; import android.companion.virtual.sensor.VirtualSensorConfig; import android.companion.virtual.sensor.VirtualSensorEvent; import android.companion.virtual.camera.VirtualCameraConfig; @@ -251,6 +252,11 @@ interface IVirtualDevice { boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event); /** + * Sends additional information about the virtual sensor corresponding to the given token. + */ + boolean sendSensorAdditionalInfo(IBinder token, in VirtualSensorAdditionalInfo info); + + /** * Launches a pending intent on the given display that is owned by this virtual device. */ void launchPendingIntent(int displayId, in PendingIntent pendingIntent, diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 4fb3982c3754..615a6dffdf99 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -158,3 +158,11 @@ flag { bug: "370720522" is_exported: true } + +flag { + name: "virtual_sensor_additional_info" + namespace: "virtual_devices" + description: "API for injecting SensorAdditionalInfo for VirtualSensor" + bug: "393517834" + is_exported: true +} diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java index 934a1a8ffcbd..8d4acfcb30d7 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensor.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java @@ -16,12 +16,15 @@ package android.companion.virtual.sensor; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.companion.virtual.IVirtualDevice; +import android.companion.virtualdevice.flags.Flags; import android.hardware.Sensor; +import android.hardware.SensorAdditionalInfo; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -37,20 +40,33 @@ import android.os.RemoteException; */ @SystemApi public final class VirtualSensor implements Parcelable { + private final int mHandle; private final int mType; private final String mName; + private final int mFlags; private final IVirtualDevice mVirtualDevice; private final IBinder mToken; + // Only one additional info frame set at a time. + private final Object mAdditionalInfoLock = new Object(); /** * @hide */ public VirtualSensor(int handle, int type, String name, IVirtualDevice virtualDevice, IBinder token) { + this(handle, type, name, /*flags=*/0, virtualDevice, token); + } + + /** + * @hide + */ + public VirtualSensor(int handle, int type, String name, int flags, IVirtualDevice virtualDevice, + IBinder token) { mHandle = handle; mType = type; mName = name; + mFlags = flags; mVirtualDevice = virtualDevice; mToken = token; } @@ -61,13 +77,14 @@ public final class VirtualSensor implements Parcelable { @SuppressLint("UnflaggedApi") // @TestApi without associated feature. @TestApi public VirtualSensor(int handle, int type, @NonNull String name) { - this(handle, type, name, /*virtualDevice=*/null, /*token=*/null); + this(handle, type, name, /*flags=*/0, /*virtualDevice=*/null, /*token=*/null); } private VirtualSensor(Parcel parcel) { mHandle = parcel.readInt(); mType = parcel.readInt(); mName = parcel.readString8(); + mFlags = parcel.readInt(); mVirtualDevice = IVirtualDevice.Stub.asInterface(parcel.readStrongBinder()); mToken = parcel.readStrongBinder(); } @@ -123,6 +140,7 @@ public final class VirtualSensor implements Parcelable { parcel.writeInt(mHandle); parcel.writeInt(mType); parcel.writeString8(mName); + parcel.writeInt(mFlags); parcel.writeStrongBinder(mVirtualDevice.asBinder()); parcel.writeStrongBinder(mToken); } @@ -143,6 +161,33 @@ public final class VirtualSensor implements Parcelable { } } + /** + * Send additional information about the sensor to the system. + * + * @param info the additional sensor information to send. + * @throws UnsupportedOperationException if the sensor does not support sending additional info. + * @see Sensor#isAdditionalInfoSupported() + * @see VirtualSensorConfig.Builder#setAdditionalInfoSupported(boolean) + * @see SensorAdditionalInfo + * @see VirtualSensorAdditionalInfo + */ + @FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO) + public void sendAdditionalInfo(@NonNull VirtualSensorAdditionalInfo info) { + if (!Flags.virtualSensorAdditionalInfo()) { + return; + } + if ((mFlags & VirtualSensorConfig.ADDITIONAL_INFO_MASK) == 0) { + throw new UnsupportedOperationException("Sensor additional info not supported."); + } + try { + synchronized (mAdditionalInfoLock) { + mVirtualDevice.sendSensorAdditionalInfo(mToken, info); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @NonNull public static final Parcelable.Creator<VirtualSensor> CREATOR = new Parcelable.Creator<VirtualSensor>() { diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.aidl new file mode 100644 index 000000000000..7267be88ca75 --- /dev/null +++ b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2025 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 android.companion.virtual.sensor; + +parcelable VirtualSensorAdditionalInfo;
\ No newline at end of file diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java new file mode 100644 index 000000000000..a4fca507b1d5 --- /dev/null +++ b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2025 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 android.companion.virtual.sensor; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.companion.virtualdevice.flags.Flags; +import android.hardware.SensorAdditionalInfo; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +/** + * An additional information frame for a {@link VirtualSensor}, which is reported through listener + * callback {@link android.hardware.SensorEventCallback#onSensorAdditionalInfo}. + * + * @see SensorAdditionalInfo + * @see VirtualSensorConfig.Builder#setAdditionalInfoSupported(boolean) + * @hide + */ +@FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO) +@SystemApi +public final class VirtualSensorAdditionalInfo implements Parcelable { + + private final int mType; + @NonNull + private final List<float[]> mValues; + + /** @hide */ + @IntDef(prefix = "TYPE_", value = { + SensorAdditionalInfo.TYPE_UNTRACKED_DELAY, + SensorAdditionalInfo.TYPE_INTERNAL_TEMPERATURE, + SensorAdditionalInfo.TYPE_VEC3_CALIBRATION, + SensorAdditionalInfo.TYPE_SENSOR_PLACEMENT, + SensorAdditionalInfo.TYPE_SAMPLING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + private VirtualSensorAdditionalInfo(int type, @NonNull List<float[]> values) { + mType = type; + mValues = values; + } + + private VirtualSensorAdditionalInfo(@NonNull Parcel parcel) { + mType = parcel.readInt(); + final int valuesLength = parcel.readInt(); + mValues = new ArrayList<>(valuesLength); + for (int i = 0; i < valuesLength; ++i) { + mValues.add(parcel.createFloatArray()); + } + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) { + parcel.writeInt(mType); + parcel.writeInt(mValues.size()); + for (int i = 0; i < mValues.size(); ++i) { + parcel.writeFloatArray(mValues.get(i)); + } + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the type of this information frame. + * + * @see SensorAdditionalInfo#type + */ + public int getType() { + return mType; + } + + /** + * Returns the float values of this information frame, if any. + * + * @see SensorAdditionalInfo#floatValues + */ + @NonNull + public List<float[]> getValues() { + return mValues; + } + + /** + * Builder for {@link VirtualSensorAdditionalInfo}. + */ + public static final class Builder { + + @VirtualSensorAdditionalInfo.Type + private final int mType; + @NonNull + private final ArrayList<float[]> mValues = new ArrayList<>(); + + /** + * Creates a new builder. + * + * @param type type of this additional info frame. + * @see SensorAdditionalInfo + */ + public Builder(@VirtualSensorAdditionalInfo.Type int type) { + switch (type) { + case SensorAdditionalInfo.TYPE_UNTRACKED_DELAY: + case SensorAdditionalInfo.TYPE_SAMPLING: + case SensorAdditionalInfo.TYPE_INTERNAL_TEMPERATURE: + case SensorAdditionalInfo.TYPE_VEC3_CALIBRATION: + case SensorAdditionalInfo.TYPE_SENSOR_PLACEMENT: + break; + default: + throw new IllegalArgumentException("Unsupported type " + type); + } + mType = type; + } + + /** + * Additional info payload data represented in float values. Depending on the type of + * information, this may be null. + * + * @see SensorAdditionalInfo#floatValues + */ + @NonNull + public Builder addValues(@NonNull float[] values) { + if (values.length > 14) { + throw new IllegalArgumentException("Maximum payload value size is 14."); + } + if (mValues.isEmpty()) { + switch (mType) { + case SensorAdditionalInfo.TYPE_UNTRACKED_DELAY: + case SensorAdditionalInfo.TYPE_SAMPLING: + assertValuesLength(values, 2); + break; + case SensorAdditionalInfo.TYPE_INTERNAL_TEMPERATURE: + assertValuesLength(values, 1); + break; + case SensorAdditionalInfo.TYPE_VEC3_CALIBRATION: + case SensorAdditionalInfo.TYPE_SENSOR_PLACEMENT: + assertValuesLength(values, 11); + break; + } + } else if (values.length != mValues.getFirst().length) { + throw new IllegalArgumentException("All payload values must have the same length"); + } + + mValues.add(values); + return this; + } + + private void assertValuesLength(float[] values, int expected) { + if (values.length != expected) { + throw new IllegalArgumentException( + "Payload values must have size " + expected + " for type " + mType); + } + } + + /** + * Creates a new {@link VirtualSensorAdditionalInfo}. + * + * @throws IllegalArgumentException if the payload doesn't match the expectation for the + * given type, as documented in {@link SensorAdditionalInfo}. + */ + @NonNull + public VirtualSensorAdditionalInfo build() { + if (mValues.isEmpty()) { + throw new IllegalArgumentException("Payload is required"); + } + return new VirtualSensorAdditionalInfo(mType, mValues); + } + } + + public static final @NonNull Creator<VirtualSensorAdditionalInfo> CREATOR = + new Creator<>() { + public VirtualSensorAdditionalInfo createFromParcel(Parcel source) { + return new VirtualSensorAdditionalInfo(source); + } + + public VirtualSensorAdditionalInfo[] newArray(int size) { + return new VirtualSensorAdditionalInfo[size]; + } + }; +} diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java index 68bc9bce28d2..be8974ec29ad 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java @@ -59,10 +59,14 @@ public final class VirtualSensorConfig implements Parcelable { private static final int REPORTING_MODE_MASK = 0xE; private static final int REPORTING_MODE_SHIFT = 1; + // Mask for indication bit of sensor additional information support, bit 6. + static final int ADDITIONAL_INFO_MASK = 0x40; + // Mask for direct mode highest rate level, bit 7, 8, 9. private static final int DIRECT_REPORT_MASK = 0x380; private static final int DIRECT_REPORT_SHIFT = 7; + // Mask for supported direct channel, bit 10, 11 private static final int DIRECT_CHANNEL_SHIFT = 10; @@ -253,6 +257,18 @@ public final class VirtualSensorConfig implements Parcelable { } /** + * Returns whether the sensor supports additional information. + * + * @see Builder#setAdditionalInfoSupported(boolean) + * @see Sensor#isAdditionalInfoSupported() + * @see android.hardware.SensorAdditionalInfo + */ + @FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO) + public boolean isAdditionalInfoSupported() { + return (mFlags & ADDITIONAL_INFO_MASK) > 0; + } + + /** * Returns the reporting mode of this sensor. * * @see Builder#setReportingMode(int) @@ -450,6 +466,25 @@ public final class VirtualSensorConfig implements Parcelable { } /** + * Sets whether this sensor supports sensor additional information. + * + * @see Sensor#isAdditionalInfoSupported() + * @see android.hardware.SensorAdditionalInfo + * @see VirtualSensorAdditionalInfo + */ + @FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO) + @NonNull + public VirtualSensorConfig.Builder setAdditionalInfoSupported( + boolean additionalInfoSupported) { + if (additionalInfoSupported) { + mFlags |= ADDITIONAL_INFO_MASK; + } else { + mFlags &= ~ADDITIONAL_INFO_MASK; + } + return this; + } + + /** * Sets the reporting mode of this sensor. * * @throws IllegalArgumentException if the reporting mode is not one of diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt index 6dcc0deb1da1..489e4f0aed01 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt @@ -114,21 +114,36 @@ class DesktopDisplayModeController( transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) } + // Do not directly use this method to check the state of desktop-first mode. Check the display + // windowing mode instead. + private fun canDesktopFirstModeBeEnabledOnDefaultDisplay(): Boolean { + if (isDefaultDisplayDesktopEligible()) { + if (isExtendedDisplayEnabled() && hasExternalDisplay()) { + return true + } + if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { + if (isInClamshellMode()) { + return true + } + } + } + return false + } + @VisibleForTesting fun getTargetWindowingModeForDefaultDisplay(): Int { - if (isExtendedDisplayEnabled() && hasExternalDisplay()) { + if (canDesktopFirstModeBeEnabledOnDefaultDisplay()) { return WINDOWING_MODE_FREEFORM } - if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { - if (isInClamshellMode()) { - return WINDOWING_MODE_FREEFORM - } - return WINDOWING_MODE_FULLSCREEN - } - // If form factor-based desktop first switch is disabled, use the default display windowing - // mode here to keep the freeform mode for some form factors (e.g., FEATURE_PC). - return windowManager.getWindowingMode(DEFAULT_DISPLAY) + return if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { + WINDOWING_MODE_FULLSCREEN + } else { + // If form factor-based desktop first switch is disabled, use the default display + // windowing mode here to keep the freeform mode for some form factors (e.g., + // FEATURE_PC). + windowManager.getWindowingMode(DEFAULT_DISPLAY) + } } private fun isExtendedDisplayEnabled(): Boolean { @@ -156,6 +171,13 @@ class DesktopDisplayModeController( private fun isInClamshellMode() = inputManager.isInTabletMode() == InputManager.SWITCH_STATE_OFF + private fun isDefaultDisplayDesktopEligible(): Boolean { + val display = requireNotNull(displayController.getDisplay(DEFAULT_DISPLAY)) { + "Display object of DEFAULT_DISPLAY must be non-null." + } + return DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display) + } + private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 7a4a834e9dc2..40737120f364 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -23,12 +23,12 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERL import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; +import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP; -import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEdgeResizePermitted; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEventFromTouchscreen; @@ -127,7 +127,9 @@ class DragResizeInputListener implements AutoCloseable { Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, DisplayController displayController, - DesktopModeEventLogger desktopModeEventLogger) { + DesktopModeEventLogger desktopModeEventLogger, + InputChannel inputChannel, + InputChannel sinkInputChannel) { mContext = context; mWindowSession = windowSession; mBgExecutor = bgExecutor; @@ -154,9 +156,13 @@ class DragResizeInputListener implements AutoCloseable { final InputSetUpResult result = setUpInputChannels(mDisplayId, mWindowSession, mDecorationSurface, mClientToken, mSinkClientToken, mSurfaceControlBuilderSupplier, - mSurfaceControlTransactionSupplier); + mSurfaceControlTransactionSupplier, inputChannel, sinkInputChannel); mainExecutor.execute(() -> { if (mClosed) { + result.mInputChannel.dispose(); + result.mSinkInputChannel.dispose(); + mSurfaceControlTransactionSupplier.get().remove( + result.mInputSinkSurface).apply(); return; } mInputSinkSurface = result.mInputSinkSurface; @@ -208,7 +214,7 @@ class DragResizeInputListener implements AutoCloseable { new DefaultTaskResizeInputEventReceiverFactory(), taskInfo, handler, choreographer, displayId, decorationSurface, callback, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, - displayController, desktopModeEventLogger); + displayController, desktopModeEventLogger, new InputChannel(), new InputChannel()); } DragResizeInputListener( @@ -251,11 +257,11 @@ class DragResizeInputListener implements AutoCloseable { @NonNull IBinder clientToken, @NonNull IBinder sinkClientToken, @NonNull Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, - @NonNull Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) { + @NonNull Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, + @NonNull InputChannel inputChannel, + @NonNull InputChannel sinkInputChannel) { Trace.beginSection("DragResizeInputListener#setUpInputChannels"); final InputTransferToken inputTransferToken = new InputTransferToken(); - final InputChannel inputChannel = new InputChannel(); - final InputChannel sinkInputChannel = new InputChannel(); try { windowSession.grantInputChannel( displayId, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt index 105941079095..da6a67c679ff 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt @@ -37,7 +37,6 @@ import android.view.WindowManager.TRANSIT_CHANGE import android.window.DisplayAreaInfo import android.window.WindowContainerTransaction import androidx.test.filters.SmallTest -import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession @@ -102,6 +101,7 @@ class DesktopDisplayModeControllerTest( TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build() private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) private val wallpaperToken = MockToken().token() + private val defaultDisplay = mock<Display>() private val externalDisplay = mock<Display>() private lateinit var extendedDisplaySettingsRestoreSession: @@ -118,7 +118,7 @@ class DesktopDisplayModeControllerTest( mockitoSession = mockitoSession() .strictness(Strictness.LENIENT) - .spyStatic(DesktopModeStatus::class.java) + .mockStatic(DesktopModeStatus::class.java) .startMocking() extendedDisplaySettingsRestoreSession = ExtendedDisplaySettingsRestoreSession(context.contentResolver) @@ -141,8 +141,15 @@ class DesktopDisplayModeControllerTest( runningTasks.add(fullscreenTask) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks)) whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken) + whenever(displayController.getDisplay(DEFAULT_DISPLAY)).thenReturn(defaultDisplay) whenever(displayController.getDisplay(EXTERNAL_DISPLAY_ID)).thenReturn(externalDisplay) setTabletModeStatus(SwitchState.UNKNOWN) + whenever( + DesktopModeStatus.isDesktopModeSupportedOnDisplay( + context, + defaultDisplay + ) + ).thenReturn(true) } @After @@ -210,6 +217,12 @@ class DesktopDisplayModeControllerTest( } setTabletModeStatus(tabletModeStatus) setExtendedMode(param.extendedDisplayEnabled) + whenever( + DesktopModeStatus.isDesktopModeSupportedOnDisplay( + context, + defaultDisplay + ) + ).thenReturn(param.isDefaultDisplayDesktopEligible) assertThat(controller.getTargetWindowingModeForDefaultDisplay()) .isEqualTo(param.expectedWindowingMode) @@ -228,6 +241,12 @@ class DesktopDisplayModeControllerTest( } setTabletModeStatus(param.tabletModeStatus) setExtendedMode(param.extendedDisplayEnabled) + whenever( + DesktopModeStatus.isDesktopModeSupportedOnDisplay( + context, + defaultDisplay + ) + ).thenReturn(param.isDefaultDisplayDesktopEligible) assertThat(controller.getTargetWindowingModeForDefaultDisplay()) .isEqualTo(param.expectedWindowingMode) @@ -287,9 +306,12 @@ class DesktopDisplayModeControllerTest( private fun setExtendedMode(enabled: Boolean) { if (DisplayFlags.enableDisplayContentModeManagement()) { - doReturn(enabled).`when` { - DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, externalDisplay) - } + whenever( + DesktopModeStatus.isDesktopModeSupportedOnDisplay( + context, + externalDisplay + ) + ).thenReturn(enabled) } else { Settings.Global.putInt( context.contentResolver, @@ -334,54 +356,119 @@ class DesktopDisplayModeControllerTest( val defaultWindowingMode: Int, val hasExternalDisplay: Boolean, val extendedDisplayEnabled: Boolean, + val isDefaultDisplayDesktopEligible: Boolean, val expectedWindowingMode: Int, ) { - FREEFORM_EXTERNAL_EXTENDED( + FREEFORM_EXTERNAL_EXTENDED_NO_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = true, extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - FULLSCREEN_EXTERNAL_EXTENDED( + FULLSCREEN_EXTERNAL_EXTENDED_NO_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = true, extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - FREEFORM_NO_EXTERNAL_EXTENDED( + FREEFORM_NO_EXTERNAL_EXTENDED_NO_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = false, extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - FULLSCREEN_NO_EXTERNAL_EXTENDED( + FULLSCREEN_NO_EXTERNAL_EXTENDED_NO_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = false, extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - FREEFORM_EXTERNAL_MIRROR( + FREEFORM_EXTERNAL_MIRROR_NO_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = true, extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - FULLSCREEN_EXTERNAL_MIRROR( + FULLSCREEN_EXTERNAL_MIRROR_NO_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = true, extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - FREEFORM_NO_EXTERNAL_MIRROR( + FREEFORM_NO_EXTERNAL_MIRROR_NO_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = false, extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - FULLSCREEN_NO_EXTERNAL_MIRROR( + FULLSCREEN_NO_EXTERNAL_MIRROR_NO_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = false, extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + FREEFORM_EXTERNAL_EXTENDED_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + hasExternalDisplay = true, + extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + FULLSCREEN_EXTERNAL_EXTENDED_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = true, + extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + FREEFORM_NO_EXTERNAL_EXTENDED_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + hasExternalDisplay = false, + extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + FULLSCREEN_NO_EXTERNAL_EXTENDED_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = false, + extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + FREEFORM_EXTERNAL_MIRROR_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + hasExternalDisplay = true, + extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + FULLSCREEN_EXTERNAL_MIRROR_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = true, + extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + FREEFORM_NO_EXTERNAL_MIRROR_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + hasExternalDisplay = false, + extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + FULLSCREEN_NO_EXTERNAL_MIRROR_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = false, + extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), } @@ -390,78 +477,175 @@ class DesktopDisplayModeControllerTest( val hasExternalDisplay: Boolean, val extendedDisplayEnabled: Boolean, val tabletModeStatus: SwitchState, + val isDefaultDisplayDesktopEligible: Boolean, val expectedWindowingMode: Int, ) { - EXTERNAL_EXTENDED_TABLET( + EXTERNAL_EXTENDED_TABLET_NO_PROJECTED( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - NO_EXTERNAL_EXTENDED_TABLET( + NO_EXTERNAL_EXTENDED_TABLET_NO_PROJECTED( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_MIRROR_TABLET( + EXTERNAL_MIRROR_TABLET_NO_PROJECTED( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - NO_EXTERNAL_MIRROR_TABLET( + NO_EXTERNAL_MIRROR_TABLET_NO_PROJECTED( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_EXTENDED_CLAMSHELL( + EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - NO_EXTERNAL_EXTENDED_CLAMSHELL( + NO_EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - EXTERNAL_MIRROR_CLAMSHELL( + EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - NO_EXTERNAL_MIRROR_CLAMSHELL( + NO_EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - EXTERNAL_EXTENDED_UNKNOWN( + EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - NO_EXTERNAL_EXTENDED_UNKNOWN( + NO_EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_EXTENDED_TABLET_PROJECTED( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_EXTENDED_TABLET_PROJECTED( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_MIRROR_TABLET_PROJECTED( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_MIRROR_TABLET_PROJECTED( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_MIRROR_CLAMSHELL_PROJECTED( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_MIRROR_CLAMSHELL_PROJECTED( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_EXTENDED_UNKNOWN_PROJECTED( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_EXTENDED_UNKNOWN_PROJECTED( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_MIRROR_UNKNOWN( + EXTERNAL_MIRROR_UNKNOWN_PROJECTED( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - NO_EXTERNAL_MIRROR_UNKNOWN( + NO_EXTERNAL_MIRROR_UNKNOWN_PROJECTED( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt index 7341e098add5..e23d0ad55b04 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt @@ -63,6 +63,8 @@ class DragResizeInputListenerTest : ShellTestCase() { private val testBgExecutor = TestShellExecutor() private val mockWindowSession = mock<IWindowSession>() private val mockInputEventReceiver = mock<TaskResizeInputEventReceiver>() + private val inputChannel = mock<InputChannel>() + private val sinkInputChannel = mock<InputChannel>() @Test fun testGrantInputChannelOffMainThread() { @@ -143,6 +145,16 @@ class DragResizeInputListenerTest : ShellTestCase() { verify(mockWindowSession).remove(inputListener.mSinkClientToken) } + @Test + fun testClose_afterBgSetup_disposesOfInputChannels() { + val inputListener = create() + testBgExecutor.flushAll() + inputListener.close() + testMainExecutor.flushAll() + verify(inputChannel).dispose() + verify(sinkInputChannel).dispose() + } + private fun verifyNoInputChannelGrantRequests() { verify(mockWindowSession, never()) .grantInputChannel( @@ -178,6 +190,8 @@ class DragResizeInputListenerTest : ShellTestCase() { { StubTransaction() }, mock<DisplayController>(), mock<DesktopModeEventLogger>(), + inputChannel, + sinkInputChannel, ) private class TestInitializationCallback : Runnable { diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java index ddd9d2acdab3..9d4c5c2735fc 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java @@ -112,6 +112,22 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen if (mSwitch.getVisibility() == VISIBLE) { mSwitch.setOnCheckedChangeListener(this); } + + if (attrs != null) { + final TypedArray a = context.obtainStyledAttributes(attrs, + androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/, + 0 /*defStyleRes*/); + final CharSequence title = a.getText( + androidx.preference.R.styleable.Preference_android_title); + setTitle(title); + //TODO(b/369470034): update to next version + if (isExpressive && Build.VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) { + CharSequence summary = a.getText( + androidx.preference.R.styleable.Preference_android_summary); + setSummary(summary); + } + a.recycle(); + } } @Override diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 790b2c343a11..bfd700dcc302 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -58,6 +58,7 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; +import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi; import com.android.systemui.util.time.FakeSystemClock; @@ -151,7 +152,8 @@ public class NotificationEntryTest extends SysuiTestCase { .build(); NotificationEntry entry = - new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); + new NotificationEntry(sbn, ranking, + UseElapsedRealtimeForCreationTime.getCurrentTime(mClock)); assertFalse(entry.isBlockable()); } @@ -251,7 +253,8 @@ public class NotificationEntryTest extends SysuiTestCase { .build(); NotificationEntry entry = - new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); + new NotificationEntry(sbn, ranking, + UseElapsedRealtimeForCreationTime.getCurrentTime(mClock)); assertEquals(systemGeneratedSmartActions, entry.getSmartActions()); assertEquals(NOTIFICATION_CHANNEL, entry.getChannel()); @@ -365,7 +368,8 @@ public class NotificationEntryTest extends SysuiTestCase { .setKey(sbn.getKey()) .build(); NotificationEntry entry = - new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); + new NotificationEntry(sbn, ranking, + UseElapsedRealtimeForCreationTime.getCurrentTime(mClock)); assertFalse(entry.isChannelVisibilityPrivate()); } @@ -378,7 +382,8 @@ public class NotificationEntryTest extends SysuiTestCase { .setKey(sbn.getKey()) .build(); NotificationEntry entry = - new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); + new NotificationEntry(sbn, ranking, + UseElapsedRealtimeForCreationTime.getCurrentTime(mClock)); assertFalse(entry.isChannelVisibilityPrivate()); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.kt index ef0a4169d98e..d532010f4c55 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.kt @@ -50,6 +50,7 @@ import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable.PluggableListener import com.android.systemui.statusbar.notification.collection.notifPipeline @@ -323,7 +324,10 @@ class VisualStabilityCoordinatorTest(flags: FlagsParameterization) : SysuiTestCa setPulsing(true) // WHEN we temporarily allow section changes for this notification entry - underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.currentTimeMillis()) + underTest.temporarilyAllowSectionChanges( + entry, + UseElapsedRealtimeForCreationTime.getCurrentTime(fakeSystemClock), + ) // THEN group changes aren't allowed assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isFalse() @@ -349,7 +353,10 @@ class VisualStabilityCoordinatorTest(flags: FlagsParameterization) : SysuiTestCa setPulsing(false) // WHEN we temporarily allow section changes for this notification entry - underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.uptimeMillis()) + underTest.temporarilyAllowSectionChanges( + entry, + UseElapsedRealtimeForCreationTime.getCurrentTime(fakeSystemClock), + ) // THEN the notification list is invalidated verifyStabilityManagerWasInvalidated(times(1)) @@ -365,7 +372,10 @@ class VisualStabilityCoordinatorTest(flags: FlagsParameterization) : SysuiTestCa setPulsing(false) // WHEN we temporarily allow section changes for this notification entry - underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.currentTimeMillis()) + underTest.temporarilyAllowSectionChanges( + entry, + UseElapsedRealtimeForCreationTime.getCurrentTime(fakeSystemClock), + ) // THEN invalidate is not called because this entry was never suppressed from reordering verifyStabilityManagerWasInvalidated(never()) @@ -382,7 +392,10 @@ class VisualStabilityCoordinatorTest(flags: FlagsParameterization) : SysuiTestCa assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isTrue() // WHEN we temporarily allow section changes for this notification entry - underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.currentTimeMillis()) + underTest.temporarilyAllowSectionChanges( + entry, + UseElapsedRealtimeForCreationTime.getCurrentTime(fakeSystemClock), + ) // THEN invalidate is not called because this entry was never suppressed from // reordering; @@ -415,7 +428,10 @@ class VisualStabilityCoordinatorTest(flags: FlagsParameterization) : SysuiTestCa setPulsing(true) // WHEN we temporarily allow section changes for this notification entry - underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.currentTimeMillis()) + underTest.temporarilyAllowSectionChanges( + entry, + UseElapsedRealtimeForCreationTime.getCurrentTime(fakeSystemClock), + ) // can now reorder, so invalidates verifyStabilityManagerWasInvalidated(times(1)) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 339f898be251..9bf3d5dfe4cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -47,7 +47,6 @@ import android.database.ExecutorContentObserver; import android.net.Uri; import android.os.Looper; import android.os.Process; -import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -78,6 +77,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction; @@ -920,7 +920,9 @@ public class NotificationLockscreenUserManagerImpl implements // notification's "when" time, or the notification entry creation time private long getEarliestNotificationTime(NotificationEntry notif) { long notifWhenWallClock = notif.getSbn().getNotification().getWhen(); - long creationTimeDelta = SystemClock.uptimeMillis() - notif.getCreationTime(); + long creationTimeDelta = UseElapsedRealtimeForCreationTime.getCurrentTime() + - notif.getCreationTime(); + long creationTimeWallClock = System.currentTimeMillis() - creationTimeDelta; return Math.min(notifWhenWallClock, creationTimeWallClock); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index caa7abb1aa7a..6b6ac69733cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -34,7 +34,7 @@ public abstract class ListEntry extends PipelineEntry { } /** - * The SystemClock.uptimeMillis() when this object was created. In general, this means the + * The SystemClock.elapsedRealtime() when this object was created. In general, this means the * moment when NotificationManager notifies our listener about the existence of this entry. * * This value will not change if the notification is updated, although it will change if the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 9795edf3313c..b7fe39e9c757 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -522,7 +522,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { } private void onNotificationsInitialized() { - mInitializedTimestamp = mClock.uptimeMillis(); + mInitializedTimestamp = UseElapsedRealtimeForCreationTime.getCurrentTime(mClock); } private void postNotification( @@ -532,7 +532,8 @@ public class NotifCollection implements Dumpable, PipelineDumpable { if (entry == null) { // A new notification! - entry = new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); + entry = new NotificationEntry(sbn, ranking, + UseElapsedRealtimeForCreationTime.getCurrentTime(mClock)); mEventQueue.add(new InitEntryEvent(entry)); mEventQueue.add(new BindEntryEvent(entry, sbn)); mNotificationSet.put(sbn.getKey(), entry); @@ -861,7 +862,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { // messages from system server. private void crashIfNotInitializing(RuntimeException exception) { final boolean isRecentlyInitialized = mInitializedTimestamp == 0 - || mClock.uptimeMillis() - mInitializedTimestamp + || UseElapsedRealtimeForCreationTime.getCurrentTime(mClock) - mInitializedTimestamp < INITIALIZATION_FORGIVENESS_WINDOW; if (isRecentlyInitialized) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt index 1f8d365cfdad..698fed33a408 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt @@ -89,9 +89,9 @@ class NotifCollectionCache<V>( return true } - // Using uptimeMillis since it's guaranteed to be monotonic, as we don't want a + // Using elapsedRealtime since it's guaranteed to be monotonic, as we don't want a // timezone/clock change to break us - val now = systemClock.uptimeMillis() + val now = UseElapsedRealtimeForCreationTime.getCurrentTime(systemClock) // Cannot purge the same entry from two threads simultaneously synchronized(key) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index d031d831bf5a..765d444a5c95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -251,7 +251,7 @@ public final class NotificationEntry extends ListEntry { /** * @param sbn the StatusBarNotification from system server * @param ranking also from system server - * @param creationTime SystemClock.uptimeMillis of when we were created + * @param creationTime SystemClock.elapsedRealtime of when we were created */ public NotificationEntry( @NonNull StatusBarNotification sbn, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 780e8f47a7fe..3110db65ca3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -573,7 +573,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { List<PipelineEntry> out, List<NotifFilter> filters) { Trace.beginSection("ShadeListBuilder.filterNotifs"); - final long now = mSystemClock.uptimeMillis(); + final long now = UseElapsedRealtimeForCreationTime.getCurrentTime(mSystemClock); for (PipelineEntry entry : entries) { if (entry instanceof GroupEntry) { final GroupEntry groupEntry = (GroupEntry) entry; @@ -617,7 +617,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { GroupEntry group = mGroups.get(topLevelKey); if (group == null) { - group = new GroupEntry(topLevelKey, mSystemClock.uptimeMillis()); + group = new GroupEntry(topLevelKey, + UseElapsedRealtimeForCreationTime.getCurrentTime(mSystemClock)); mGroups.put(topLevelKey, group); } if (group.getParent() == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/UseElapsedRealtimeForCreationTime.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/UseElapsedRealtimeForCreationTime.kt new file mode 100644 index 000000000000..23f90f3694a8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/UseElapsedRealtimeForCreationTime.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection + +import android.app.Flags +import com.android.systemui.util.time.SystemClock + +/** A helper class for replacing uptimeMillis with elapsedRealtime for entry creation times */ +public object UseElapsedRealtimeForCreationTime { + @JvmStatic + fun getCurrentTime(clock: SystemClock): Long { + if (Flags.notifEntryCreationTimeUseElapsedRealtime()) { + return clock.elapsedRealtime() + } + return clock.uptimeMillis() + } + + @JvmStatic + fun getCurrentTime(): Long { + if (Flags.notifEntryCreationTimeUseElapsedRealtime()) { + return android.os.SystemClock.elapsedRealtime() + } + return android.os.SystemClock.uptimeMillis() + + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java index 2eec68b26347..fb7772e26240 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java @@ -25,7 +25,7 @@ import java.util.List; * Represents a set of notification post events for a particular notification group. */ public class EventBatch { - /** SystemClock.uptimeMillis() */ + /** SystemClock.elapsedRealtime() */ final long mCreatedTimestamp; /** SBN.getGroupKey -- same for all members */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java index 96b35428b3ce..944e313d795a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java @@ -34,6 +34,7 @@ import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; import com.android.systemui.statusbar.notification.collection.PipelineDumpable; import com.android.systemui.statusbar.notification.collection.PipelineDumper; +import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.time.SystemClock; @@ -182,11 +183,12 @@ public class GroupCoalescer implements Dumpable, PipelineDumpable { private void maybeEmitBatch(StatusBarNotification sbn) { final CoalescedEvent event = mCoalescedEvents.get(sbn.getKey()); final EventBatch batch = mBatches.get(sbn.getGroupKey()); + long now = UseElapsedRealtimeForCreationTime.getCurrentTime(mClock); if (event != null) { mLogger.logEarlyEmit(sbn.getKey(), requireNonNull(event.getBatch()).mGroupKey); emitBatch(requireNonNull(event.getBatch())); } else if (batch != null - && mClock.uptimeMillis() - batch.mCreatedTimestamp >= mMaxGroupLingerDuration) { + && now - batch.mCreatedTimestamp >= mMaxGroupLingerDuration) { mLogger.logMaxBatchTimeout(sbn.getKey(), batch.mGroupKey); emitBatch(batch); } @@ -228,7 +230,8 @@ public class GroupCoalescer implements Dumpable, PipelineDumpable { private EventBatch getOrBuildBatch(final String groupKey) { EventBatch batch = mBatches.get(groupKey); if (batch == null) { - batch = new EventBatch(mClock.uptimeMillis(), groupKey); + batch = new EventBatch(UseElapsedRealtimeForCreationTime.getCurrentTime(mClock), + groupKey); mBatches.put(groupKey, batch); } return batch; @@ -268,7 +271,8 @@ public class GroupCoalescer implements Dumpable, PipelineDumpable { } events.sort(mEventComparator); - long batchAge = mClock.uptimeMillis() - batch.mCreatedTimestamp; + long batchAge = UseElapsedRealtimeForCreationTime.getCurrentTime(mClock) + - batch.mCreatedTimestamp; mLogger.logEmitBatch(batch.mGroupKey, batch.mMembers.size(), batchAge); mHandler.onNotificationBatchPosted(events); @@ -298,7 +302,7 @@ public class GroupCoalescer implements Dumpable, PipelineDumpable { @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - long now = mClock.uptimeMillis(); + long now = UseElapsedRealtimeForCreationTime.getCurrentTime(mClock); int eventCount = 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index 27765635edcb..0466c0359710 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -501,7 +501,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { * notification and we are reordering based on the user's change. * * @param entry notification entry that can change sections even if isReorderingAllowed is false - * @param now current time SystemClock.uptimeMillis + * @param now current time SystemClock.elapsedRealtime */ public void temporarilyAllowSectionChanges(@NonNull NotificationEntry entry, long now) { final String entryKey = entry.getKey(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java index 07fa6aeb7900..03b4076ba6fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java @@ -20,7 +20,6 @@ import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_N import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; -import android.os.SystemClock; import android.service.notification.NotificationStats; import androidx.annotation.NonNull; @@ -30,6 +29,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime; import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; @@ -85,7 +85,7 @@ public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback public void onImportanceChanged(NotificationEntry entry) { mVisualStabilityCoordinator.temporarilyAllowSectionChanges( entry, - SystemClock.uptimeMillis()); + UseElapsedRealtimeForCreationTime.getCurrentTime()); } @NonNull diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java index 776c7d5eb7f6..389bb3129c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java @@ -41,8 +41,8 @@ public abstract class NotifFilter extends Pluggable<NotifFilter> { * this entry will not have any grouping nor sorting information. * If this filter is registered via {@link NotifPipeline#addFinalizeFilter}, * this entry will have grouping and sorting information. - * @param now A timestamp in SystemClock.uptimeMillis that represents "now" for the purposes of - * pipeline execution. This value will be the same for all pluggable calls made + * @param now A timestamp in SystemClock.elapsedRealtime that represents "now" for the purposes + * of pipeline execution. This value will be the same for all pluggable calls made * during this pipeline run, giving pluggables a stable concept of "now" to compare * various entries against. * @return True if the notif should be removed from the list diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index ec8fbc08de7a..5bdd769dfa03 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -19,7 +19,6 @@ import android.content.Context; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemClock; import android.service.notification.NotificationListenerService; import android.util.ArrayMap; import android.util.ArraySet; @@ -44,6 +43,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.UpdateSource; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; @@ -112,7 +112,7 @@ public class NotificationLogger implements StateListener, CoreStartable, @Override public void run() { - mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis(); + mLastVisibilityReportUptimeMs = UseElapsedRealtimeForCreationTime.getCurrentTime(); // 1. Loop over active entries: // A. Keep list of visible notifications. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 74a42ef3ff7d..f3d72027238f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -29,6 +29,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.content.Context; import android.graphics.Color; import android.os.Handler; import android.util.Log; @@ -226,6 +227,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private ScrimState mState = ScrimState.UNINITIALIZED; + private Context mContext; + private ScrimView mScrimInFront; private ScrimView mNotificationsScrim; private ScrimView mScrimBehind; @@ -365,7 +368,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump @Main CoroutineDispatcher mainDispatcher, LargeScreenShadeInterpolator largeScreenShadeInterpolator, BlurConfig blurConfig, + @Main Context context, Lazy<WindowRootViewBlurInteractor> windowRootViewBlurInteractor) { + mContext = context; mScrimStateListener = lightBarController::setScrimState; mLargeScreenShadeInterpolator = largeScreenShadeInterpolator; mBlurConfig = blurConfig; @@ -1627,16 +1632,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private void updateThemeColors() { if (mScrimBehind == null) return; - int background = mScrimBehind.getContext().getColor( + int background = mContext.getColor( com.android.internal.R.color.materialColorSurfaceDim); - int accent = mScrimBehind.getContext().getColor( + int accent = mContext.getColor( com.android.internal.R.color.materialColorPrimary); mColors.setMainColor(background); mColors.setSecondaryColor(accent); final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background); mColors.setSupportsDarkText(isBackgroundLight); - int surface = mScrimBehind.getContext().getColor( + int surface = mContext.getColor( com.android.internal.R.color.materialColorSurface); for (ScrimState state : ScrimState.values()) { state.setSurfaceColor(surface); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index ffb861db182c..063b546cbae9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -296,6 +296,7 @@ public class ScrimControllerTest extends SysuiTestCase { mKosmos.getTestDispatcher(), mLinearLargeScreenShadeInterpolator, new BlurConfig(0.0f, 0.0f), + mContext, mKosmos::getWindowRootViewBlurInteractor); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); @@ -1247,6 +1248,7 @@ public class ScrimControllerTest extends SysuiTestCase { mKosmos.getTestDispatcher(), mLinearLargeScreenShadeInterpolator, new BlurConfig(0.0f, 0.0f), + mContext, mKosmos::getWindowRootViewBlurInteractor); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java index 4efcada96a14..215df9d59ec9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java @@ -58,7 +58,7 @@ public class GroupEntryBuilder { return this; } - /** Sets the creation time. */ + /** Sets the creation time. Should be SystemClock.elapsedRealtime */ public GroupEntryBuilder setCreationTime(long creationTime) { mCreationTime = creationTime; return this; diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java index 88b791046c87..6e098d014ee3 100644 --- a/services/companion/java/com/android/server/companion/virtual/SensorController.java +++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java @@ -21,15 +21,18 @@ import android.annotation.Nullable; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.sensor.IVirtualSensorCallback; import android.companion.virtual.sensor.VirtualSensor; +import android.companion.virtual.sensor.VirtualSensorAdditionalInfo; import android.companion.virtual.sensor.VirtualSensorConfig; import android.companion.virtual.sensor.VirtualSensorEvent; import android.content.AttributionSource; +import android.hardware.SensorAdditionalInfo; import android.hardware.SensorDirectChannel; import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.SharedMemory; +import android.os.SystemClock; import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; @@ -140,7 +143,7 @@ public class SensorController { final IBinder sensorToken = new Binder("android.hardware.sensor.VirtualSensor:" + config.getName()); VirtualSensor sensor = new VirtualSensor(handle, config.getType(), config.getName(), - virtualDevice, sensorToken); + config.getFlags(), virtualDevice, sensorToken); synchronized (mLock) { mSensorDescriptors.put(sensorToken, sensorDescriptor); mVirtualSensors.put(handle, sensor); @@ -164,6 +167,37 @@ public class SensorController { } } + boolean sendSensorAdditionalInfo(@NonNull IBinder token, + @NonNull VirtualSensorAdditionalInfo info) { + Objects.requireNonNull(token); + Objects.requireNonNull(info); + synchronized (mLock) { + final SensorDescriptor sensorDescriptor = mSensorDescriptors.get(token); + long timestamp = SystemClock.elapsedRealtimeNanos(); + if (sensorDescriptor == null) { + throw new IllegalArgumentException("Could not send sensor event for given token"); + } + if (!mSensorManagerInternal.sendSensorAdditionalInfo( + sensorDescriptor.getHandle(), SensorAdditionalInfo.TYPE_FRAME_BEGIN, + /* serial= */ 0, timestamp++, /* values= */ null)) { + return false; + } + for (int i = 0; i < info.getValues().size(); ++i) { + if (!mSensorManagerInternal.sendSensorAdditionalInfo( + sensorDescriptor.getHandle(), info.getType(), /* serial= */ i, + timestamp++, info.getValues().get(i))) { + return false; + } + } + if (!mSensorManagerInternal.sendSensorAdditionalInfo( + sensorDescriptor.getHandle(), SensorAdditionalInfo.TYPE_FRAME_END, + /* serial= */ 0, timestamp, /* values= */ null)) { + return false; + } + } + return true; + } + @Nullable VirtualSensor getSensorByHandle(int handle) { synchronized (mLock) { 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 28efdfc01ee2..0023b6d53837 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -55,6 +55,7 @@ import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; import android.companion.virtual.camera.VirtualCameraConfig; import android.companion.virtual.sensor.VirtualSensor; +import android.companion.virtual.sensor.VirtualSensorAdditionalInfo; import android.companion.virtual.sensor.VirtualSensorEvent; import android.companion.virtualdevice.flags.Flags; import android.compat.annotation.ChangeId; @@ -1294,6 +1295,18 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } @Override // Binder call + public boolean sendSensorAdditionalInfo(@NonNull IBinder token, + @NonNull VirtualSensorAdditionalInfo info) { + checkCallerIsDeviceOwner(); + final long ident = Binder.clearCallingIdentity(); + try { + return mSensorController.sendSensorAdditionalInfo(token, info); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call public void registerIntentInterceptor(IVirtualDeviceIntentInterceptor intentInterceptor, IntentFilter filter) { checkCallerIsDeviceOwner(); diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java index 7ff4ade1101c..9636cc6c77a7 100644 --- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java +++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java @@ -17,6 +17,7 @@ package com.android.server.sensors; import android.annotation.NonNull; +import android.annotation.Nullable; import android.hardware.SensorDirectChannel; import android.os.ParcelFileDescriptor; @@ -71,7 +72,7 @@ public abstract class SensorManagerInternal { /** * Sends an event for the runtime sensor with the given handle to the framework. * - * Only relevant for sending runtime sensor events. @see #createRuntimeSensor. + * <p>Only relevant for sending runtime sensor events. @see #createRuntimeSensor.</p> * * @param handle The sensor handle. * @param type The type of the sensor. @@ -83,6 +84,21 @@ public abstract class SensorManagerInternal { @NonNull float[] values); /** + * Sends an additional info event for the runtime sensor with the given handle to the framework. + * + * <p>Only relevant for runtime sensors. @see #createRuntimeSensor.</p> + * + * @param handle The sensor handle. + * @param type The type of payload data. + * @param serial The sequence number of this frame for this type. + * @param timestampNanos Timestamp of the event. + * @param values The payload data represented in float values. + * @return Whether the event injection was successful. + */ + public abstract boolean sendSensorAdditionalInfo(int handle, int type, int serial, + long timestampNanos, @Nullable float[] values); + + /** * Listener for proximity sensor state changes. */ public interface ProximityActiveListener { diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java index 3de191030d71..0d31b22e2020 100644 --- a/services/core/java/com/android/server/sensors/SensorService.java +++ b/services/core/java/com/android/server/sensors/SensorService.java @@ -19,6 +19,7 @@ package com.android.server.sensors; import static com.android.server.sensors.SensorManagerInternal.ProximityActiveListener; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.util.ArrayMap; @@ -62,6 +63,9 @@ public class SensorService extends SystemService { private static native void unregisterRuntimeSensorNative(long ptr, int handle); private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type, long timestampNanos, float[] values); + private static native boolean sendRuntimeSensorAdditionalInfoNative(long ptr, int handle, + int type, int serial, long timestampNanos, float[] values); + public SensorService(Context ctx) { super(ctx); @@ -129,6 +133,18 @@ public class SensorService extends SystemService { } @Override + public boolean sendSensorAdditionalInfo(int handle, int type, int serial, + long timestampNanos, @Nullable float[] values) { + synchronized (mLock) { + if (!mRuntimeSensorHandles.contains(handle)) { + return false; + } + return sendRuntimeSensorAdditionalInfoNative(mPtr, handle, type, serial, + timestampNanos, values); + } + } + + @Override public void addProximityActiveListener(@NonNull Executor executor, @NonNull ProximityActiveListener listener) { Objects.requireNonNull(executor, "executor must not be null"); diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp index eb729de6afd4..0bee7181c2d5 100644 --- a/services/core/jni/com_android_server_sensor_SensorService.cpp +++ b/services/core/jni/com_android_server_sensor_SensorService.cpp @@ -60,6 +60,8 @@ public: void unregisterRuntimeSensor(jint handle); jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp, jfloatArray values); + jboolean sendRuntimeSensorAdditionalInfo(JNIEnv* env, jint handle, jint type, jint serial, + jlong timestamp, jfloatArray values); private: sp<SensorService> mService; @@ -172,9 +174,9 @@ jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, j sensors_event_t event{ .version = sizeof(sensors_event_t), - .timestamp = timestamp, .sensor = handle, .type = type, + .timestamp = timestamp, }; int valuesLength = env->GetArrayLength(values); @@ -234,6 +236,42 @@ jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, j return err == OK; } +jboolean NativeSensorService::sendRuntimeSensorAdditionalInfo(JNIEnv* env, jint handle, jint type, + jint serial, jlong timestamp, + jfloatArray values) { + if (mService == nullptr) { + ALOGD("Dropping sendRuntimeSensorAdditionalInfo, sensor service not available."); + return false; + } + + sensors_event_t event{ + .version = sizeof(sensors_event_t), + .sensor = handle, + .type = SENSOR_TYPE_ADDITIONAL_INFO, + .timestamp = timestamp, + .additional_info = + (additional_info_event_t){ + .type = type, + .serial = serial, + }, + }; + + if (values != nullptr) { + int valuesLength = env->GetArrayLength(values); + if (valuesLength > 14) { + ALOGD("Dropping sendRuntimeSensorAdditionalInfo, number of values exceeds maximum."); + return false; + } + if (valuesLength > 0) { + jfloat* sensorValues = env->GetFloatArrayElements(values, nullptr); + memcpy(event.additional_info.data_float, sensorValues, valuesLength * sizeof(float)); + } + } + + status_t err = mService->sendRuntimeSensorEvent(event); + return err == OK; +} + NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate( JNIEnv* env, jobject listener) : mListener(env->NewGlobalRef(listener)) {} @@ -326,6 +364,13 @@ static jboolean sendRuntimeSensorEventNative(JNIEnv* env, jclass, jlong ptr, jin return service->sendRuntimeSensorEvent(env, handle, type, timestamp, values); } +static jboolean sendRuntimeSensorAdditionalInfoNative(JNIEnv* env, jclass, jlong ptr, jint handle, + jint type, jint serial, jlong timestamp, + jfloatArray values) { + auto* service = reinterpret_cast<NativeSensorService*>(ptr); + return service->sendRuntimeSensorAdditionalInfo(env, handle, type, serial, timestamp, values); +} + static const JNINativeMethod methods[] = { {"startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J", reinterpret_cast<void*>(startSensorServiceNative)}, @@ -340,6 +385,8 @@ static const JNINativeMethod methods[] = { reinterpret_cast<void*>(unregisterRuntimeSensorNative)}, {"sendRuntimeSensorEventNative", "(JIIJ[F)Z", reinterpret_cast<void*>(sendRuntimeSensorEventNative)}, + {"sendRuntimeSensorAdditionalInfoNative", "(JIIIJ[F)Z", + reinterpret_cast<void*>(sendRuntimeSensorAdditionalInfoNative)}, }; int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env) { diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java index 67fc564fa778..2e07cd8ae698 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java @@ -22,18 +22,25 @@ import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.sensor.IVirtualSensorCallback; import android.companion.virtual.sensor.VirtualSensor; +import android.companion.virtual.sensor.VirtualSensorAdditionalInfo; import android.companion.virtual.sensor.VirtualSensorConfig; import android.companion.virtual.sensor.VirtualSensorEvent; import android.content.AttributionSource; import android.hardware.Sensor; +import android.hardware.SensorAdditionalInfo; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -49,6 +56,7 @@ import com.google.common.collect.Iterables; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -65,6 +73,9 @@ public class SensorControllerTest { private static final int VIRTUAL_SENSOR_TYPE = Sensor.TYPE_ACCELEROMETER; + private static final float[] ADDITIONAL_INFO_VALUES_1 = new float[] {1.2f, 3.4f}; + private static final float[] ADDITIONAL_INFO_VALUES_2 = new float[] {5.6f, 7.8f}; + @Mock private SensorManagerInternal mSensorManagerInternalMock; @Mock @@ -155,6 +166,53 @@ public class SensorControllerTest { } @Test + public void sendSensorAdditionalInfo_invalidToken_throwsException() throws Exception { + SensorController sensorController = doCreateSensorSuccessfully(); + + final VirtualSensorAdditionalInfo info = + new VirtualSensorAdditionalInfo.Builder(SensorAdditionalInfo.TYPE_UNTRACKED_DELAY) + .addValues(ADDITIONAL_INFO_VALUES_1) + .addValues(ADDITIONAL_INFO_VALUES_2) + .build(); + assertThrows( + IllegalArgumentException.class, + () -> sensorController.sendSensorAdditionalInfo( + new Binder("invalidSensorToken"), info)); + } + + @Test + public void sendSensorAdditionalInfo_success() throws Exception { + SensorController sensorController = doCreateSensorSuccessfully(); + + clearInvocations(mSensorManagerInternalMock); + when(mSensorManagerInternalMock.sendSensorAdditionalInfo( + anyInt(), anyInt(), anyInt(), anyLong(), any())) + .thenReturn(true); + IBinder token = Iterables.getOnlyElement(sensorController.getSensorDescriptors().keySet()); + + final VirtualSensorAdditionalInfo info = + new VirtualSensorAdditionalInfo.Builder(SensorAdditionalInfo.TYPE_UNTRACKED_DELAY) + .addValues(ADDITIONAL_INFO_VALUES_1) + .addValues(ADDITIONAL_INFO_VALUES_2) + .build(); + sensorController.sendSensorAdditionalInfo(token, info); + + InOrder inOrder = inOrder(mSensorManagerInternalMock); + inOrder.verify(mSensorManagerInternalMock).sendSensorAdditionalInfo( + eq(SENSOR_HANDLE), eq(SensorAdditionalInfo.TYPE_FRAME_BEGIN), + /*serial=*/ eq(0), /* timestamp= */ anyLong(), /*values=*/ isNull()); + inOrder.verify(mSensorManagerInternalMock).sendSensorAdditionalInfo( + eq(SENSOR_HANDLE), eq(SensorAdditionalInfo.TYPE_UNTRACKED_DELAY), + /*serial=*/ eq(0), /* timestamp= */ anyLong(), eq(ADDITIONAL_INFO_VALUES_1)); + inOrder.verify(mSensorManagerInternalMock).sendSensorAdditionalInfo( + eq(SENSOR_HANDLE), eq(SensorAdditionalInfo.TYPE_UNTRACKED_DELAY), + /*serial=*/ eq(1), /* timestamp= */ anyLong(), eq(ADDITIONAL_INFO_VALUES_2)); + inOrder.verify(mSensorManagerInternalMock).sendSensorAdditionalInfo( + eq(SENSOR_HANDLE), eq(SensorAdditionalInfo.TYPE_FRAME_END), + /*serial=*/ eq(0), /* timestamp= */ anyLong(), /*values=*/ isNull()); + } + + @Test public void close_unregistersSensors() throws Exception { SensorController sensorController = doCreateSensorSuccessfully(); diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index f5282639ae6c..6e23edf936c7 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -614,7 +614,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P /** @hide */ public static int convertRssiAsuToDBm(int rssiAsu) { - if (rssiAsu == SIGNAL_STRENGTH_LTE_RSSI_ASU_UNKNOWN) { + if (rssiAsu == SIGNAL_STRENGTH_LTE_RSSI_ASU_UNKNOWN || rssiAsu == Integer.MAX_VALUE) { return CellInfo.UNAVAILABLE; } if ((rssiAsu < SIGNAL_STRENGTH_LTE_RSSI_VALID_ASU_MIN_VALUE |