diff options
182 files changed, 3741 insertions, 1732 deletions
diff --git a/core/java/Android.bp b/core/java/Android.bp index 980d9737aba7..7123ee7b5777 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -17,19 +17,19 @@ aidl_library { filegroup { name: "framework-core-sources", srcs: [ - "**/*.java", "**/*.aidl", - ":systemfeatures-gen-srcs", + "**/*.java", ":framework-nfc-non-updatable-sources", ":messagequeue-gen", ":ranging_stack_mock_initializer", + ":systemfeatures-gen-srcs", ], // Exactly one MessageQueue.java will be added to srcs by messagequeue-gen exclude_srcs: [ + "**/*_ravenwood.java", + ":dynamic_instrumentation_manager_aidl_sources", "android/os/*MessageQueue/**/*.java", "android/ranging/**/*.java", - ":dynamic_instrumentation_manager_aidl_sources", - "**/*_ravenwood.java", ], visibility: ["//frameworks/base"], } @@ -52,9 +52,9 @@ soong_config_module_type { "release_ranging_stack", ], properties: [ - "srcs", "cmd", "out", + "srcs", ], } @@ -153,9 +153,9 @@ filegroup { filegroup { name: "framework-core-sources-for-test-mock", srcs: [ + "android/accounts/AccountManager.java", "android/accounts/AccountManagerCallback.java", "android/accounts/AccountManagerFuture.java", - "android/accounts/AccountManager.java", "android/accounts/AccountsException.java", "android/accounts/AuthenticatorException.java", "android/accounts/OperationCanceledException.java", @@ -163,8 +163,8 @@ filegroup { "android/app/IApplicationThread.aidl", "android/app/IServiceConnection.aidl", "android/app/PackageDeleteObserver.java", - "android/content/ComponentCallbacks2.java", "android/content/ComponentCallbacks.java", + "android/content/ComponentCallbacks2.java", "android/content/ContentInterface.java", "android/content/ContentProvider.java", "android/content/ContentProviderNative.java", @@ -178,8 +178,8 @@ filegroup { "android/content/OperationApplicationException.java", "android/content/pm/ActivityInfo.java", "android/content/pm/ApplicationInfo.java", - "android/content/pm/InstantAppInfo.java", "android/content/pm/IPackageDataObserver.aidl", + "android/content/pm/InstantAppInfo.java", "android/content/pm/KeySet.java", "android/content/pm/PackageManager.java", "android/content/pm/VerifierDeviceIdentity.java", @@ -192,8 +192,8 @@ filegroup { "android/os/Bundle.java", "android/os/IBinder.java", "android/os/IInterface.java", - "android/os/Parcelable.java", "android/os/ParcelFileDescriptor.java", + "android/os/Parcelable.java", "android/os/RemoteException.java", "android/os/storage/VolumeInfo.java", "android/util/AndroidException.java", @@ -215,24 +215,24 @@ filegroup { filegroup { name: "libvibrator_aidl", srcs: [ + "android/os/ExternalVibrationScale.aidl", "android/os/IExternalVibrationController.aidl", "android/os/IExternalVibratorService.aidl", - "android/os/ExternalVibrationScale.aidl", ], } filegroup { name: "libpowermanager_aidl", srcs: [ - "android/os/Temperature.aidl", "android/os/CoolingDevice.aidl", + "android/os/IPowerManager.aidl", + "android/os/IScreenTimeoutPolicyListener.aidl", "android/os/IThermalEventListener.aidl", "android/os/IThermalHeadroomListener.aidl", - "android/os/IThermalStatusListener.aidl", "android/os/IThermalService.aidl", - "android/os/IPowerManager.aidl", + "android/os/IThermalStatusListener.aidl", "android/os/IWakeLockCallback.aidl", - "android/os/IScreenTimeoutPolicyListener.aidl", + "android/os/Temperature.aidl", ], } @@ -282,8 +282,8 @@ genrule { java_library { name: "uieventloggerlib", srcs: [ - "com/android/internal/logging/UiEventLoggerImpl.java", ":statslog-framework-java-gen", + "com/android/internal/logging/UiEventLoggerImpl.java", ], libs: [ "androidx.annotation_annotation", @@ -353,11 +353,11 @@ filegroup { name: "framework-permission-s-shared-srcs", srcs: [ ":modules-utils-preconditions-srcs", - "com/android/internal/infra/AndroidFuture.java", - "com/android/internal/infra/ServiceConnector.java", + "android/os/HandlerExecutor.java", "com/android/internal/infra/AndroidFuture.aidl", + "com/android/internal/infra/AndroidFuture.java", "com/android/internal/infra/IAndroidFuture.aidl", - "android/os/HandlerExecutor.java", + "com/android/internal/infra/ServiceConnector.java", ], } @@ -393,10 +393,10 @@ filegroup { "android/content/pm/FileSystemControlParcel.aidl", "android/content/pm/IDataLoader.aidl", "android/content/pm/IDataLoaderManager.aidl", - "android/content/pm/InstallationFileParcel.aidl", - "android/content/pm/InstallationFileLocation.aidl", "android/content/pm/IDataLoaderStatusListener.aidl", "android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl", + "android/content/pm/InstallationFileLocation.aidl", + "android/content/pm/InstallationFileParcel.aidl", ], } @@ -404,9 +404,9 @@ filegroup { name: "incremental_manager_aidl", srcs: [ "android/os/incremental/IIncrementalService.aidl", + "android/os/incremental/IStorageHealthListener.aidl", "android/os/incremental/IStorageLoadingProgressListener.aidl", "android/os/incremental/IncrementalNewFileParams.aidl", - "android/os/incremental/IStorageHealthListener.aidl", "android/os/incremental/PerUidReadTimeouts.aidl", "android/os/incremental/StorageHealthCheckParams.aidl", ], @@ -422,15 +422,15 @@ filegroup { filegroup { name: "hwbinder-stubs-srcs", srcs: [ - "android/os/HidlSupport.java", + "android/os/DeadObjectException.java", + "android/os/DeadSystemException.java", "android/os/HidlMemory.java", + "android/os/HidlSupport.java", "android/os/HwBinder.java", "android/os/HwBlob.java", "android/os/HwParcel.java", "android/os/IHwBinder.java", "android/os/IHwInterface.java", - "android/os/DeadObjectException.java", - "android/os/DeadSystemException.java", "android/os/NativeHandle.java", "android/os/RemoteException.java", "android/util/AndroidException.java", @@ -444,12 +444,12 @@ filegroup { cc_defaults { name: "incremental_default", cflags: [ + "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION", "-Wall", + "-Werror", "-Wextra", "-Wextra-semi", - "-Werror", "-Wzero-as-null-pointer-constant", - "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION", ], shared_libs: [ "libbinder", @@ -489,8 +489,8 @@ cc_library { ], defaults: ["incremental_default"], shared_libs: [ - "libincremental_aidl-cpp", "libdataloader_aidl-cpp", + "libincremental_aidl-cpp", ], } @@ -580,8 +580,8 @@ filegroup { filegroup { name: "framework-telephony-common-shared-srcs", srcs: [ - "android/os/RegistrantList.java", "android/os/Registrant.java", + "android/os/RegistrantList.java", "android/util/IndentingPrintWriter.java", "android/util/LocalLog.java", "android/util/TimeUtils.java", @@ -599,8 +599,8 @@ filegroup { name: "framework-ims-common-shared-srcs", srcs: [ ":modules-utils-preconditions-srcs", - "android/os/RegistrantList.java", "android/os/Registrant.java", + "android/os/RegistrantList.java", "com/android/internal/os/SomeArgs.java", ], } @@ -609,9 +609,9 @@ filegroup { filegroup { name: "framework-core-sources-for-fuzzers", srcs: [ - "android/os/IInterface.java", "android/os/Binder.java", "android/os/IBinder.java", + "android/os/IInterface.java", "android/os/Parcelable.java", ], } @@ -657,6 +657,17 @@ filegroup { } java_library { + name: "protolog-common-lib", + srcs: [ + ":protolog-common-src", + ], + host_supported: true, + static_libs: [ + "framework-annotations-lib", + ], +} + +java_library { name: "protolog-group", srcs: [ "com/android/internal/protolog/common/IProtoLogGroup.java", @@ -694,7 +705,9 @@ java_library { name: "protolog-groups", srcs: [ "com/android/internal/protolog/WmProtoLogGroups.java", - ":protolog-common-src", + ], + static_libs: [ + "protolog-common-lib", ], } diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 01b2953362b5..6c6709bb2b47 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -369,6 +369,11 @@ interface IActivityTaskManager { in RemoteCallback navigationObserver, in BackAnimationAdapter adaptor); /** + * Registers a callback to be invoked when the system server requests a back gesture. + */ + void registerBackGestureDelegate(in RemoteCallback monitor); + + /** * registers a callback to be invoked when a background activity launch is aborted. * * @param observer callback to be registered. diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java index 8d4acfcb30d7..7f228ab37f21 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensor.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java @@ -174,7 +174,7 @@ public final class VirtualSensor implements Parcelable { @FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO) public void sendAdditionalInfo(@NonNull VirtualSensorAdditionalInfo info) { if (!Flags.virtualSensorAdditionalInfo()) { - return; + throw new UnsupportedOperationException("Sensor additional info not supported."); } if ((mFlags & VirtualSensorConfig.ADDITIONAL_INFO_MASK) == 0) { throw new UnsupportedOperationException("Sensor additional info not supported."); diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java index a4fca507b1d5..0f1a2989b808 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java @@ -90,6 +90,7 @@ public final class VirtualSensorAdditionalInfo implements Parcelable { * * @see SensorAdditionalInfo#type */ + @VirtualSensorAdditionalInfo.Type public int getType() { return mType; } @@ -114,6 +115,21 @@ public final class VirtualSensorAdditionalInfo implements Parcelable { @NonNull private final ArrayList<float[]> mValues = new ArrayList<>(); + /** Payload size for {@link SensorAdditionalInfo#TYPE_SAMPLING} */ + private static final int TYPE_SAMPLING_PLAYLOAD_SIZE = 2; + + /** Payload size for {@link SensorAdditionalInfo#TYPE_UNTRACKED_DELAY} */ + private static final int TYPE_UNTRACKED_DELAY_PAYLOAD_SIZE = 2; + + /** Payload size for {@link SensorAdditionalInfo#TYPE_INTERNAL_TEMPERATURE} */ + private static final int TYPE_INTERNAL_TEMPERATURE_PLAYLOAD_SIZE = 1; + + /** Payload size for {@link SensorAdditionalInfo#TYPE_VEC3_CALIBRATION} */ + private static final int TYPE_VEC3_CALIBRATION_PAYLOAD_SIZE = 12; + + /** Payload size for {@link SensorAdditionalInfo#TYPE_SENSOR_PLACEMENT} */ + private static final int TYPE_SENSOR_PLACEMENT_PAYLOAD_SIZE = 12; + /** * Creates a new builder. * @@ -135,28 +151,31 @@ public final class VirtualSensorAdditionalInfo implements Parcelable { } /** - * Additional info payload data represented in float values. Depending on the type of - * information, this may be null. + * Additional info payload data represented in float values. * + * @param values the float values of this additional info frame. + * @throws IllegalArgumentException if the payload size doesn't match the expectation + * for the given type, as documented in {@link SensorAdditionalInfo}. * @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: + assertValuesLength(values, TYPE_UNTRACKED_DELAY_PAYLOAD_SIZE); + break; case SensorAdditionalInfo.TYPE_SAMPLING: - assertValuesLength(values, 2); + assertValuesLength(values, TYPE_SAMPLING_PLAYLOAD_SIZE); break; case SensorAdditionalInfo.TYPE_INTERNAL_TEMPERATURE: - assertValuesLength(values, 1); + assertValuesLength(values, TYPE_INTERNAL_TEMPERATURE_PLAYLOAD_SIZE); break; case SensorAdditionalInfo.TYPE_VEC3_CALIBRATION: + assertValuesLength(values, TYPE_VEC3_CALIBRATION_PAYLOAD_SIZE); + break; case SensorAdditionalInfo.TYPE_SENSOR_PLACEMENT: - assertValuesLength(values, 11); + assertValuesLength(values, TYPE_SENSOR_PLACEMENT_PAYLOAD_SIZE); break; } } else if (values.length != mValues.getFirst().length) { diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 210653bb41e5..63c819eb8233 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -5198,12 +5198,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * </tr> * <tr> * <td style="text-align: center;">PRIV</td> - * <td style="text-align: center;">S1080P</td> - * <td style="text-align: center;">PRIV</td> - * <td style="text-align: center;">UHD</td> - * </tr> - * <tr> - * <td style="text-align: center;">PRIV</td> * <td style="text-align: center;">S720P</td> * <td style="text-align: center;">JPEG/JPEG_R</td> * <td style="text-align: center;">MAXIMUM_16_9</td> @@ -5271,28 +5265,36 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * </thead> * <tbody> * <tr> - * <td style="text-align: center;">PRIV</td> + * <td style="text-align: center;">PRIV Preview</td> * <td style="text-align: center;">S1080P</td> - * <td style="text-align: center;">PRIV</td> + * <td style="text-align: center;">PRIV Video</td> * <td style="text-align: center;">S1080P</td> * <td style="text-align: center;"></td> * <td style="text-align: center;"></td> * </tr> * <tr> - * <td style="text-align: center;">PRIV</td> + * <td style="text-align: center;">PRIV Preview</td> * <td style="text-align: center;">S1080P</td> - * <td style="text-align: center;">PRIV</td> + * <td style="text-align: center;">PRIV Video</td> * <td style="text-align: center;">S1440P</td> * <td style="text-align: center;"></td> * <td style="text-align: center;"></td> * </tr> * <tr> - * <td style="text-align: center;">PRIV</td> + * <td style="text-align: center;">PRIV Preview</td> * <td style="text-align: center;">S1080P</td> - * <td style="text-align: center;">YUV</td> + * <td style="text-align: center;">PRIV Video</td> + * <td style="text-align: center;">UHD</td> + * <td style="text-align: center;"></td> + * <td style="text-align: center;"></td> + * </tr> + * <tr> + * <td style="text-align: center;">PRIV Preview</td> * <td style="text-align: center;">S1080P</td> + * <td style="text-align: center;">YUV Analysis</td> * <td style="text-align: center;">S1080P</td> - * <td style="text-align: center;">PRIV</td> + * <td style="text-align: center;">PRIV Video</td> + * <td style="text-align: center;">1080P</td> * </tr> * </tbody> * </table> diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java index c3c8c3d63bec..aae09e6f4c01 100644 --- a/core/java/android/hardware/display/DisplayTopology.java +++ b/core/java/android/hardware/display/DisplayTopology.java @@ -169,7 +169,23 @@ public final class DisplayTopology implements Parcelable { * @hide */ public void addDisplay(int displayId, float width, float height) { - addDisplay(displayId, width, height, /* shouldLog= */ true); + if (findDisplay(displayId, mRoot) != null) { + return; + } + if (mRoot == null) { + mRoot = new TreeNode(displayId, width, height, POSITION_LEFT, /* offset= */ 0); + mPrimaryDisplayId = displayId; + } else if (mRoot.mChildren.isEmpty()) { + // This is the 2nd display. Align the middles of the top and bottom edges. + float offset = mRoot.mWidth / 2 - width / 2; + TreeNode display = new TreeNode(displayId, width, height, POSITION_TOP, offset); + mRoot.mChildren.add(display); + } else { + TreeNode rightMostDisplay = findRightMostDisplay(mRoot, mRoot.mWidth).first; + TreeNode newDisplay = new TreeNode(displayId, width, height, POSITION_RIGHT, + /* offset= */ 0); + rightMostDisplay.mChildren.add(newDisplay); + } } /** @@ -216,7 +232,7 @@ public final class DisplayTopology implements Parcelable { while (!queue.isEmpty()) { TreeNode node = queue.poll(); if (node.mDisplayId != displayId) { - addDisplay(node.mDisplayId, node.mWidth, node.mHeight, /* shouldLog= */ false); + addDisplay(node.mDisplayId, node.mWidth, node.mHeight); } queue.addAll(node.mChildren); } @@ -227,10 +243,6 @@ public final class DisplayTopology implements Parcelable { } else { mPrimaryDisplayId = Display.INVALID_DISPLAY; } - Slog.i(TAG, "Primary display with ID " + displayId - + " removed, new primary display: " + mPrimaryDisplayId); - } else { - Slog.i(TAG, "Display with ID " + displayId + " removed"); } return true; } @@ -598,37 +610,6 @@ public final class DisplayTopology implements Parcelable { return out.toString(); } - private void addDisplay(int displayId, float width, float height, boolean shouldLog) { - if (findDisplay(displayId, mRoot) != null) { - return; - } - if (mRoot == null) { - mRoot = new TreeNode(displayId, width, height, POSITION_LEFT, /* offset= */ 0); - mPrimaryDisplayId = displayId; - if (shouldLog) { - Slog.i(TAG, "First display added: " + mRoot); - } - } else if (mRoot.mChildren.isEmpty()) { - // This is the 2nd display. Align the middles of the top and bottom edges. - float offset = mRoot.mWidth / 2 - width / 2; - TreeNode display = new TreeNode(displayId, width, height, POSITION_TOP, offset); - mRoot.mChildren.add(display); - if (shouldLog) { - Slog.i(TAG, "Second display added: " + display + ", parent ID: " - + mRoot.mDisplayId); - } - } else { - TreeNode rightMostDisplay = findRightMostDisplay(mRoot, mRoot.mWidth).first; - TreeNode newDisplay = new TreeNode(displayId, width, height, POSITION_RIGHT, - /* offset= */ 0); - rightMostDisplay.mChildren.add(newDisplay); - if (shouldLog) { - Slog.i(TAG, "Display added: " + newDisplay + ", parent ID: " - + rightMostDisplay.mDisplayId); - } - } - } - /** * @param display The display from which the search should start. * @param xPos The x position of the right edge of that display. diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 71d79bb32807..cc1dee7a5747 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1780,7 +1780,7 @@ public abstract class BatteryStats { out.writeInt(statIrqTime); out.writeInt(statSoftIrqTime); out.writeInt(statIdlTime); - out.writeString(statSubsystemPowerState); + out.writeString8(statSubsystemPowerState); } public void readFromParcel(Parcel in) { @@ -1801,7 +1801,15 @@ public abstract class BatteryStats { statIrqTime = in.readInt(); statSoftIrqTime = in.readInt(); statIdlTime = in.readInt(); - statSubsystemPowerState = in.readString(); + statSubsystemPowerState = in.readString8(); + } + + + public boolean isEmpty() { + return userTime == 0 && systemTime == 0 && appCpuUid1 == Process.INVALID_UID + && appCpuUid2 == Process.INVALID_UID && appCpuUid3 == Process.INVALID_UID + && statSystemTime == 0 && statIOWaitTime == 0 && statIrqTime == 0 + && statSoftIrqTime == 0 && statIdlTime == 0 && statSubsystemPowerState == null; } } @@ -2238,6 +2246,7 @@ public abstract class BatteryStats { tagsFirstOccurrence = false; powerStats = null; processStateChange = null; + stepDetails = null; } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) @@ -2289,6 +2298,7 @@ public abstract class BatteryStats { currentTime = o.currentTime; powerStats = o.powerStats; processStateChange = o.processStateChange; + stepDetails = o.stepDetails; } public boolean sameNonEvent(HistoryItem o) { @@ -7318,7 +7328,8 @@ public abstract class BatteryStats { } item.append(", SubsystemPowerState "); - item.append(rec.stepDetails.statSubsystemPowerState); + item.append(rec.stepDetails.statSubsystemPowerState != null + ? rec.stepDetails.statSubsystemPowerState : "Empty"); item.append("\n"); } else { item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(','); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b7e296228e45..e7bca1419418 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -13317,10 +13317,18 @@ public final class Settings { public static final String CONTEXTUAL_SEARCH_PACKAGE = "contextual_search_package"; /** - * Inetger property which determines whether advanced protection is on or not. + * Integer property which determines whether advanced protection is on or not. * @hide */ public static final String ADVANCED_PROTECTION_MODE = "advanced_protection_mode"; + + /** + * Integer property which determines whether advanced protection USB data protection + * feature is on or not. + * + * @hide + */ + public static final String AAPM_USB_DATA_PROTECTION = "aapm_usb_data_protection"; } /** diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index c4347f05f4a3..c7ae3283c46c 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -28,6 +28,7 @@ import static android.view.SurfaceControlProto.NAME; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.DurationNanosLong; import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntDef; @@ -469,9 +470,9 @@ public final class SurfaceControl implements Parcelable { private final long mFrameVsyncId; private final @JankType int mJankType; - private final long mFrameIntervalNs; - private final long mScheduledAppFrameTimeNs; - private final long mActualAppFrameTimeNs; + private final @DurationNanosLong long mFrameIntervalNs; + private final @DurationNanosLong long mScheduledAppFrameTimeNs; + private final @DurationNanosLong long mActualAppFrameTimeNs; /** * @hide @@ -512,7 +513,7 @@ public final class SurfaceControl implements Parcelable { * @return the frame interval in ns * @hide */ - public long getFrameIntervalNanos() { + public @DurationNanosLong long getFrameIntervalNanos() { return mFrameIntervalNs; } @@ -525,7 +526,7 @@ public final class SurfaceControl implements Parcelable { * * @return scheduled app time in ns */ - public long getScheduledAppFrameTimeNanos() { + public @DurationNanosLong long getScheduledAppFrameTimeNanos() { return mScheduledAppFrameTimeNs; } @@ -534,7 +535,7 @@ public final class SurfaceControl implements Parcelable { * * @return the actual app time in ns */ - public long getActualAppFrameTimeNanos() { + public @DurationNanosLong long getActualAppFrameTimeNanos() { return mActualAppFrameTimeNs; } diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java index 5e8ce5ee557f..04e6c68859c9 100644 --- a/core/java/android/window/DesktopExperienceFlags.java +++ b/core/java/android/window/DesktopExperienceFlags.java @@ -64,6 +64,7 @@ public enum DesktopExperienceFlags { ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX(Flags::enableDynamicRadiusComputationBugfix, false), ENABLE_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS(Flags::keyboardShortcutsToSwitchDesks, false), ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT(Flags::enableMoveToNextDisplayShortcut, true), + ENABLE_MULTIDISPLAY_TRACKPAD_BACK_GESTURE(Flags::enableMultidisplayTrackpadBackGesture, false), ENABLE_MULTIPLE_DESKTOPS_BACKEND(Flags::enableMultipleDesktopsBackend, false), ENABLE_MULTIPLE_DESKTOPS_FRONTEND(Flags::enableMultipleDesktopsFrontend, false), ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS( diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index d5120ffb4de6..9b3d6242b213 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -66,16 +66,16 @@ public enum DesktopModeFlags { ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX( Flags::enableDesktopIndicatorInSeparateThreadBugfix, false), ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX( - Flags::enableDesktopOpeningDeeplinkMinimizeAnimationBugfix, false), + Flags::enableDesktopOpeningDeeplinkMinimizeAnimationBugfix, true), ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX( - Flags::enableDesktopRecentsTransitionsCornersBugfix, false), + Flags::enableDesktopRecentsTransitionsCornersBugfix, true), ENABLE_DESKTOP_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE_BUGFIX( Flags::skipCompatUiEducationInDesktopMode, true), ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true), ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX( - Flags::enableDesktopTabTearingMinimizeAnimationBugfix, false), + Flags::enableDesktopTabTearingMinimizeAnimationBugfix, true), ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX( - Flags::enableDesktopTrampolineCloseAnimationBugfix, false), + Flags::enableDesktopTrampolineCloseAnimationBugfix, true), ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER( Flags::enableDesktopWallpaperActivityForSystemUser, true), ENABLE_DESKTOP_WINDOWING_APP_TO_WEB(Flags::enableDesktopWindowingAppToWeb, true), @@ -107,7 +107,7 @@ public enum DesktopModeFlags { true), ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD(Flags::enableDragResizeSetUpInBgThread, true), ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX( - Flags::enableDragToDesktopIncomingTransitionsBugfix, false), + Flags::enableDragToDesktopIncomingTransitionsBugfix, true), ENABLE_FULLY_IMMERSIVE_IN_DESKTOP(Flags::enableFullyImmersiveInDesktop, true), ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true), ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true), @@ -134,7 +134,7 @@ public enum DesktopModeFlags { ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING(Flags::enableTopVisibleRootTaskPerUserTracking, true), ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX( - Flags::enableVisualIndicatorInTransitionBugfix, false), + Flags::enableVisualIndicatorInTransitionBugfix, true), ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true), ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true), ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true), diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java index 53c64bd6e664..4ddd73319840 100644 --- a/core/java/android/window/TaskSnapshot.java +++ b/core/java/android/window/TaskSnapshot.java @@ -37,6 +37,7 @@ import com.android.window.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.function.Consumer; /** * Represents a task snapshot. @@ -76,6 +77,7 @@ public class TaskSnapshot implements Parcelable { // Must be one of the named color spaces, otherwise, always use SRGB color space. private final ColorSpace mColorSpace; private int mInternalReferences; + private Consumer<HardwareBuffer> mSafeSnapshotReleaser; /** Keep in cache, doesn't need reference. */ public static final int REFERENCE_NONE = 0; @@ -365,8 +367,24 @@ public class TaskSnapshot implements Parcelable { mInternalReferences &= ~usage; if (Flags.releaseSnapshotAggressively() && mInternalReferences == 0 && mSnapshot != null && !mSnapshot.isClosed()) { - mSnapshot.close(); + if (mSafeSnapshotReleaser != null) { + mSafeSnapshotReleaser.accept(mSnapshot); + } else { + mSnapshot.close(); + } + } + } + + /** + * Register a safe release callback, instead of immediately closing the hardware buffer when + * no more reference, to let the system server decide when to close it. + * Only used in core. + */ + public synchronized void setSafeRelease(Consumer<HardwareBuffer> releaser) { + if (!Flags.safeReleaseSnapshotAggressively()) { + return; } + mSafeSnapshotReleaser = releaser; } public static final @NonNull Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() { diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 8dd0457248a4..59dd32258d8c 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -516,6 +516,17 @@ flag { } flag { + name: "safe_release_snapshot_aggressively" + namespace: "windowing_frontend" + description: "Protect task snapshot memory from premature release, which can occur when a local variable holds a reference while the snapshot is removed from the cache." + bug: "238206323" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "scramble_snapshot_file_name" namespace: "windowing_frontend" description: "Scramble the file name of task snapshot." @@ -524,4 +535,22 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "delegate_back_gesture_to_shell" + namespace: "windowing_frontend" + description: "Delegate back gesture event to back animation controller." + bug: "394599430" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_multidisplay_trackpad_back_gesture" + namespace: "lse_desktop_experience" + description: "Adds support for trackpad back gestures on connected displays" + bug: "382774299" }
\ No newline at end of file diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index f1c47a7a023b..3fc74c9f1f54 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -79,7 +79,7 @@ public class BatteryStatsHistory { private static final String TAG = "BatteryStatsHistory"; // Current on-disk Parcel version. Must be updated when the format of the parcelable changes - private static final int VERSION = 213; + private static final int VERSION = 214; // Part of initial delta int that specifies the time delta. static final int DELTA_TIME_MASK = 0x7ffff; @@ -316,7 +316,6 @@ public class BatteryStatsHistory { } private final Parcel mHistoryBuffer; - private final HistoryStepDetailsCalculator mStepDetailsCalculator; private final Clock mClock; private int mMaxHistoryBufferSize; @@ -337,25 +336,6 @@ public class BatteryStatsHistory { */ private List<Parcel> mHistoryParcels = null; - /** - * When iterating history files, the current file index. - */ - private BatteryHistoryFragment mCurrentFragment; - - /** - * When iterating history files, the current file parcel. - */ - private Parcel mCurrentParcel; - /** - * When iterating history file, the current parcel's Parcel.dataSize(). - */ - private int mCurrentParcelEnd; - /** - * Used when BatteryStatsImpl object is created from deserialization of a parcel, - * such as Settings app or checkin file, to iterate over history parcels. - */ - private int mParcelIndex = 0; - private final ReentrantLock mWriteLock = new ReentrantLock(); private final HistoryItem mHistoryCur = new HistoryItem(); @@ -384,28 +364,11 @@ public class BatteryStatsHistory { // Monotonically increasing size of written history private long mMonotonicHistorySize; private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>(); - private byte mLastHistoryStepLevel = 0; private boolean mMutable = true; private int mIteratorCookie; private final BatteryStatsHistory mWritableHistory; /** - * A delegate responsible for computing additional details for a step in battery history. - */ - public interface HistoryStepDetailsCalculator { - /** - * Returns additional details for the current history step or null. - */ - @Nullable - HistoryStepDetails getHistoryStepDetails(); - - /** - * Resets the calculator to get ready for a new battery session - */ - void clear(); - } - - /** * A delegate for android.os.Trace to allow testing static calls. Due to * limitations in Android Tracing (b/153319140), the delegate also records * counter values in system properties which allows reading the value at the @@ -472,21 +435,17 @@ public class BatteryStatsHistory { * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps */ public BatteryStatsHistory(Parcel historyBuffer, int maxHistoryBufferSize, - @Nullable BatteryHistoryStore store, HistoryStepDetailsCalculator stepDetailsCalculator, - Clock clock, MonotonicClock monotonicClock, TraceDelegate tracer, - EventLogger eventLogger) { - this(historyBuffer, maxHistoryBufferSize, store, - stepDetailsCalculator, - clock, monotonicClock, tracer, eventLogger, null); + @Nullable BatteryHistoryStore store, Clock clock, MonotonicClock monotonicClock, + TraceDelegate tracer, EventLogger eventLogger) { + this(historyBuffer, maxHistoryBufferSize, store, clock, monotonicClock, tracer, eventLogger, + null); } private BatteryStatsHistory(@Nullable Parcel historyBuffer, int maxHistoryBufferSize, - @Nullable BatteryHistoryStore store, - @NonNull HistoryStepDetailsCalculator stepDetailsCalculator, @NonNull Clock clock, + @Nullable BatteryHistoryStore store, @NonNull Clock clock, @NonNull MonotonicClock monotonicClock, @NonNull TraceDelegate tracer, @NonNull EventLogger eventLogger, @Nullable BatteryStatsHistory writableHistory) { mMaxHistoryBufferSize = maxHistoryBufferSize; - mStepDetailsCalculator = stepDetailsCalculator; mTracer = tracer; mClock = clock; mMonotonicClock = monotonicClock; @@ -527,7 +486,6 @@ public class BatteryStatsHistory { mClock = Clock.SYSTEM_CLOCK; mTracer = null; mStore = null; - mStepDetailsCalculator = null; mEventLogger = new EventLogger(); mWritableHistory = null; mMutable = false; @@ -556,9 +514,6 @@ public class BatteryStatsHistory { mNextHistoryTagIdx = 0; mNumHistoryTagChars = 0; mHistoryBufferLastPos = -1; - if (mStepDetailsCalculator != null) { - mStepDetailsCalculator.clear(); - } } /** @@ -596,7 +551,7 @@ public class BatteryStatsHistory { Parcel historyBufferCopy = Parcel.obtain(); historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize()); - return new BatteryStatsHistory(historyBufferCopy, 0, mStore, null, + return new BatteryStatsHistory(historyBufferCopy, 0, mStore, null, null, null, mEventLogger, this); } } finally { @@ -712,10 +667,6 @@ public class BatteryStatsHistory { if (mStore != null) { mStore.lock(); } - mCurrentFragment = null; - mCurrentParcel = null; - mCurrentParcelEnd = 0; - mParcelIndex = 0; BatteryStatsHistoryIterator iterator = new BatteryStatsHistoryIterator( this, startTimeMs, endTimeMs); mIteratorCookie = System.identityHashCode(iterator); @@ -1614,6 +1565,21 @@ public class BatteryStatsHistory { } /** + * Records an update containing HistoryStepDetails, except if the details are empty. + */ + public void recordHistoryStepDetails(HistoryStepDetails details, long elapsedRealtimeMs, + long uptimeMs) { + if (details.isEmpty()) { + return; + } + synchronized (this) { + mHistoryCur.stepDetails = details; + writeHistoryItem(elapsedRealtimeMs, uptimeMs); + mHistoryCur.stepDetails = null; + } + } + + /** * Writes the current history item to history. */ public void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs) { @@ -1632,8 +1598,8 @@ public class BatteryStatsHistory { mHistoryAddTmp.processStateChange = null; mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE; mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG; + mHistoryAddTmp.stepDetails = null; writeHistoryItem(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp); - } } mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG; @@ -1952,15 +1918,8 @@ public class BatteryStatsHistory { } int firstToken = deltaTimeToken | (cur.states & BatteryStatsHistory.DELTA_STATE_MASK); - if (cur.batteryLevel < mLastHistoryStepLevel || mLastHistoryStepLevel == 0) { - cur.stepDetails = mStepDetailsCalculator.getHistoryStepDetails(); - if (cur.stepDetails != null) { - batteryLevelInt |= BatteryStatsHistory.BATTERY_LEVEL_DETAILS_FLAG; - mLastHistoryStepLevel = cur.batteryLevel; - } - } else { - cur.stepDetails = null; - mLastHistoryStepLevel = cur.batteryLevel; + if (cur.stepDetails != null) { + batteryLevelInt |= BatteryStatsHistory.BATTERY_LEVEL_DETAILS_FLAG; } final boolean batteryLevelIntChanged = batteryLevelInt != 0; @@ -2055,6 +2014,7 @@ public class BatteryStatsHistory { + Integer.toHexString(cur.states2)); } } + cur.tagsFirstOccurrence = false; if (cur.wakelockTag != null || cur.wakeReasonTag != null) { int wakeLockIndex; int wakeReasonIndex; diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 9b3a6cba5f23..36564cd90d05 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -358,7 +358,7 @@ <!-- USA: 5-6 digits (premium codes from https://www.premiumsmsrefunds.com/ShortCodes.htm), visual voicemail code for T-Mobile: 122 --> - <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611|96831|10907" /> + <shortcode country="us" pattern="\\d{5,6}" free="122|\\d{5,6}" /> <!--Uruguay : 1-6 digits (standard system default, not country specific) --> <shortcode country="uy" pattern="\\d{1,6}" free="55002|191289" /> diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 8f85617acae3..98278f4529ff 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -184,6 +184,14 @@ prebuilt_etc { } prebuilt_etc { + name: "privapp_whitelist_com.android.statementservice", + system_ext_specific: true, + sub_dir: "permissions", + src: "com.android.statementservice.xml", + filename_from_src: true, +} + +prebuilt_etc { name: "privapp_whitelist_com.android.settings.intelligence", product_specific: true, sub_dir: "permissions", diff --git a/data/etc/com.android.statementservice.xml b/data/etc/com.android.statementservice.xml new file mode 100644 index 000000000000..e102af206395 --- /dev/null +++ b/data/etc/com.android.statementservice.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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 + --> +<permissions> + <privapp-permissions package="com.android.statementservice"> + <permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/> + <permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 9234902335c1..1dd0465f691e 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -623,12 +623,6 @@ applications that come with the platform <permission name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"/> </privapp-permissions> - <privapp-permissions package="com.android.statementservice"> - <permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/> - <permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/> - <permission name="android.permission.INTERACT_ACROSS_USERS"/> - </privapp-permissions> - <privapp-permissions package="com.android.soundpicker"> <permission name="android.permission.INTERACT_ACROSS_USERS" /> </privapp-permissions> diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index bcb6c4f555f7..033c934056d6 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -26,9 +26,11 @@ package { java_library { name: "wm_shell_protolog-groups", srcs: [ - ":protolog-common-src", "src/com/android/wm/shell/protolog/ShellProtoLogGroup.java", ], + static_libs: [ + "protolog-common-lib", + ], } filegroup { @@ -159,12 +161,12 @@ java_library { android_library { name: "WindowManager-Shell", srcs: [ - ":wm_shell_protolog_src", // TODO(b/168581922) protologtool do not support kotlin(*.kt) - "src/com/android/wm/shell/EventLogTags.logtags", ":wm_shell-aidls", ":wm_shell-shared-aidls", ":wm_shell-sources-kt", + ":wm_shell_protolog_src", + "src/com/android/wm/shell/EventLogTags.logtags", ], resource_dirs: [ "res", diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl index 8481c446c6aa..8dcda53b602b 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl @@ -18,6 +18,7 @@ package com.android.wm.shell.shared; import android.window.RemoteTransition; import android.window.TransitionFilter; +import android.view.InsetsState; /** * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks @@ -29,5 +30,10 @@ oneway interface IHomeTransitionListener { * Called when a transition changes the visibility of the home activity on the default display. */ void onHomeVisibilityChanged(in boolean isVisible); + + /** + * Called when the insets at display-level change. + */ + void onDisplayInsetsChanged(in InsetsState insets); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/ContextUtils.kt index 0b36f452348a..27db5297b758 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/ContextUtils.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.bubbles +package com.android.wm.shell.shared.bubbles import android.content.Context import android.view.View diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java index e5a4cd034e72..a9224b02ad31 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java @@ -451,6 +451,6 @@ public class DesktopModeStatus { pw.println(maxTaskLimitHandle == null ? "null" : maxTaskLimitHandle.getInt(/* def= */ -1)); pw.print(innerPrefix); pw.print("showAppHandle config override="); - pw.print(overridesShowAppHandle(context)); + pw.println(overridesShowAppHandle(context)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java index 8e78686ac13d..8e3dc4c36c1d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java @@ -66,7 +66,7 @@ public class SizeChangeAnimation { * The maximum of stretching applied to any surface during interpolation (since the animation * is a combination of stretching/cropping/fading). */ - private static final float SCALE_FACTOR = 0.7f; + private static final float DEFAULT_SCALE_FACTOR = 0.7f; /** * Since this animation is made of several sub-animations, we want to pre-arrange the @@ -82,13 +82,27 @@ public class SizeChangeAnimation { */ private static final int ANIMATION_RESOLUTION = 1000; + /** + * Initialize a size-change animation from start to end bounds + */ public SizeChangeAnimation(Rect startBounds, Rect endBounds) { - this(startBounds, endBounds, 1f); + this(startBounds, endBounds, 1f, DEFAULT_SCALE_FACTOR); } - public SizeChangeAnimation(Rect startBounds, Rect endBounds, float initialScale) { - mAnimation = buildContainerAnimation(startBounds, endBounds, initialScale); - mSnapshotAnim = buildSnapshotAnimation(startBounds, endBounds); + /** + * Initialize a size-change animation from start to end bounds. + * <p> + * Allows specifying the initial scale factor, {@code initialScale}, that is applied to the + * start bounds. This can be useful for example when a task is scaled down when the size change + * animation starts. + * <p> + * By default the max scale applied to any surface is {@link #DEFAULT_SCALE_FACTOR}. Use + * {@code scaleFactor} to override it. + */ + public SizeChangeAnimation(Rect startBounds, Rect endBounds, float initialScale, + float scaleFactor) { + mAnimation = buildContainerAnimation(startBounds, endBounds, initialScale, scaleFactor); + mSnapshotAnim = buildSnapshotAnimation(startBounds, endBounds, scaleFactor); } /** @@ -172,15 +186,15 @@ public class SizeChangeAnimation { /** Animation for the whole container (snapshot is inside this container). */ private static AnimationSet buildContainerAnimation(Rect startBounds, Rect endBounds, - float initialScale) { + float initialScale, float scaleFactor) { final long duration = ANIMATION_RESOLUTION; boolean growing = endBounds.width() - startBounds.width() + endBounds.height() - startBounds.height() >= 0; - long scalePeriod = (long) (duration * SCALE_FACTOR); - float startScaleX = SCALE_FACTOR * ((float) startBounds.width()) / endBounds.width() - + (1.f - SCALE_FACTOR); - float startScaleY = SCALE_FACTOR * ((float) startBounds.height()) / endBounds.height() - + (1.f - SCALE_FACTOR); + long scalePeriod = (long) (duration * scaleFactor); + float startScaleX = scaleFactor * ((float) startBounds.width()) / endBounds.width() + + (1.f - scaleFactor); + float startScaleY = scaleFactor * ((float) startBounds.height()) / endBounds.height() + + (1.f - scaleFactor); final AnimationSet animSet = new AnimationSet(true); final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1); @@ -218,15 +232,16 @@ public class SizeChangeAnimation { } /** The snapshot surface is assumed to be a child of the container surface. */ - private static AnimationSet buildSnapshotAnimation(Rect startBounds, Rect endBounds) { + private static AnimationSet buildSnapshotAnimation(Rect startBounds, Rect endBounds, + float scaleFactor) { final long duration = ANIMATION_RESOLUTION; boolean growing = endBounds.width() - startBounds.width() + endBounds.height() - startBounds.height() >= 0; - long scalePeriod = (long) (duration * SCALE_FACTOR); - float endScaleX = 1.f / (SCALE_FACTOR * ((float) startBounds.width()) / endBounds.width() - + (1.f - SCALE_FACTOR)); - float endScaleY = 1.f / (SCALE_FACTOR * ((float) startBounds.height()) / endBounds.height() - + (1.f - SCALE_FACTOR)); + long scalePeriod = (long) (duration * scaleFactor); + float endScaleX = 1.f / (scaleFactor * ((float) startBounds.width()) / endBounds.width() + + (1.f - scaleFactor)); + float endScaleY = 1.f / (scaleFactor * ((float) startBounds.height()) / endBounds.height() + + (1.f - scaleFactor)); AnimationSet snapAnimSet = new AnimationSet(true); // Animation for the "old-state" snapshot that is atop the task. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 81cf031994a2..746632f67725 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -286,6 +286,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont this::createExternalInterface, this); mShellCommandHandler.addDumpCallback(this::dump, this); mShellController.addConfigurationChangeListener(this); + registerBackGestureDelegate(); } public BackAnimation getBackAnimationImpl() { @@ -1144,6 +1145,32 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mBackAnimationAdapter = new BackAnimationAdapter(runner); } + private void registerBackGestureDelegate() { + if (!Flags.delegateBackGestureToShell()) { + return; + } + final RemoteCallback requestBackMonitor = new RemoteCallback( + new RemoteCallback.OnResultListener() { + @Override + public void onResult(@Nullable Bundle result) { + mShellExecutor.execute(() -> { + if (mBackGestureStarted) { + Log.w(TAG, "Back gesture is running, ignore request"); + return; + } + onMotionEvent(0, 0, KeyEvent.ACTION_DOWN, EDGE_NONE); + setTriggerBack(true); + onMotionEvent(0, 0, KeyEvent.ACTION_UP, EDGE_NONE); + }); + } + }); + try { + mActivityTaskManager.registerBackGestureDelegate(requestBackMonitor); + } catch (RemoteException remoteException) { + Log.w(TAG, "Failed register back gesture request ", remoteException); + } + } + /** * Description of current BackAnimationController state. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 81eff6f7399a..70fa48cca0b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -120,6 +120,7 @@ import com.android.wm.shell.shared.bubbles.BubbleBarLocation; import com.android.wm.shell.shared.bubbles.BubbleBarLocation.UpdateSource; import com.android.wm.shell.shared.bubbles.BubbleBarUpdate; import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider; +import com.android.wm.shell.shared.bubbles.ContextUtils; import com.android.wm.shell.shared.bubbles.DeviceConfig; import com.android.wm.shell.shared.draganddrop.DragAndDropConstants; import com.android.wm.shell.sysui.ConfigurationChangeListener; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 3dce45690cf2..4900b6fc77ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -94,6 +94,7 @@ import com.android.wm.shell.shared.TypefaceUtils.FontFamily; import com.android.wm.shell.shared.animation.Interpolators; import com.android.wm.shell.shared.animation.PhysicsAnimator; import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; +import com.android.wm.shell.shared.bubbles.ContextUtils; import com.android.wm.shell.shared.bubbles.DeviceConfig; import com.android.wm.shell.shared.bubbles.DismissView; import com.android.wm.shell.shared.bubbles.RelativeTouchListener; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index fa22a3961002..b89bfd5c969e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -596,11 +596,11 @@ public class BubbleBarAnimationHelper { final Size size = getExpandedViewSize(); Point position = getExpandedViewRestPosition(size); - final SizeChangeAnimation sca = - new SizeChangeAnimation( - new Rect(origBounds.left - position.x, origBounds.top - position.y, - origBounds.right - position.x, origBounds.bottom - position.y), - new Rect(0, 0, size.getWidth(), size.getHeight()), origScale); + Rect startBounds = new Rect(origBounds.left - position.x, origBounds.top - position.y, + origBounds.right - position.x, origBounds.bottom - position.y); + Rect endBounds = new Rect(0, 0, size.getWidth(), size.getHeight()); + final SizeChangeAnimation sca = new SizeChangeAnimation(startBounds, endBounds, + origScale, /* scaleFactor= */ 1f); sca.initialize(bbev, taskLeash, snapshot, startT); Animator a = sca.buildViewAnimator(bbev, tvSf, snapshot, /* onFinish */ (va) -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index f62fd819319e..b3c25d495002 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -762,6 +762,7 @@ public abstract class WMShellBaseModule { ShellTaskOrganizer organizer, TransactionPool pool, DisplayController displayController, + DisplayInsetsController displayInsetsController, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler, @ShellAnimationThread ShellExecutor animExecutor, @@ -773,15 +774,19 @@ public abstract class WMShellBaseModule { shellInit = new ShellInit(mainExecutor); } return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer, - pool, displayController, mainExecutor, mainHandler, animExecutor, - rootTaskDisplayAreaOrganizer, homeTransitionObserver, focusTransitionObserver); + pool, displayController, displayInsetsController, mainExecutor, mainHandler, + animExecutor, rootTaskDisplayAreaOrganizer, homeTransitionObserver, + focusTransitionObserver); } @WMSingleton @Provides static HomeTransitionObserver provideHomeTransitionObserver(Context context, - @ShellMainThread ShellExecutor mainExecutor) { - return new HomeTransitionObserver(context, mainExecutor); + @ShellMainThread ShellExecutor mainExecutor, + DisplayInsetsController displayInsetsController, + ShellInit shellInit) { + return new HomeTransitionObserver(context, mainExecutor, displayInsetsController, + shellInit); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java index ce98b03b77a6..bff08ba6d88f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java @@ -42,9 +42,10 @@ public class DefaultSurfaceAnimator { @NonNull Animation anim, @NonNull SurfaceControl leash, @NonNull Runnable finishCallback, @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius, - @Nullable Rect clipRect) { + @Nullable Rect clipRect, + @Nullable TransitionAnimationHelper.RoundedContentPerDisplay roundedBounds) { final DefaultAnimationAdapter adapter = new DefaultAnimationAdapter(anim, leash, - position, clipRect, cornerRadius); + position, clipRect, cornerRadius, roundedBounds); buildSurfaceAnimation(animations, anim, finishCallback, pool, mainExecutor, adapter); } @@ -109,9 +110,17 @@ public class DefaultSurfaceAnimator { @Nullable final Rect mClipRect; @Nullable private final Rect mAnimClipRect; final float mCornerRadius; + final int mWindowBottom; + + /** + * Inset changes aren't synchronized with transitions, so use a "provider" to track the + * bottom of the display content during the animation. + */ + @Nullable final TransitionAnimationHelper.RoundedContentPerDisplay mRoundedContentBounds; DefaultAnimationAdapter(@NonNull Animation anim, @NonNull SurfaceControl leash, - @Nullable Point position, @Nullable Rect clipRect, float cornerRadius) { + @Nullable Point position, @Nullable Rect clipRect, float cornerRadius, + TransitionAnimationHelper.RoundedContentPerDisplay roundedBounds) { super(leash); mAnim = anim; mPosition = (position != null && (position.x != 0 || position.y != 0)) @@ -119,6 +128,8 @@ public class DefaultSurfaceAnimator { mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null; mAnimClipRect = mClipRect != null ? new Rect() : null; mCornerRadius = cornerRadius; + mWindowBottom = clipRect != null ? clipRect.bottom : 0; + mRoundedContentBounds = roundedBounds; } @Override @@ -136,6 +147,11 @@ public class DefaultSurfaceAnimator { if (mClipRect != null) { boolean needCrop = false; + if (mRoundedContentBounds != null) { + mClipRect.bottom = Math.min(mRoundedContentBounds.mBounds.bottom, + mWindowBottom); + } + mAnimClipRect.set(mClipRect); if (transformation.hasClipRect()) { mAnimClipRect.intersectUnchecked(transformation.getClipRect()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index bf5800330979..e9200834c5dd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -59,13 +59,13 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static com.android.internal.jank.Cuj.CUJ_DEFAULT_TASK_TO_TASK_ANIMATION; -import static com.android.internal.policy.TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CHANGE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN; +import static com.android.wm.shell.Flags.enableDynamicInsetsForAppLaunch; import static com.android.wm.shell.transition.DefaultSurfaceAnimator.buildSurfaceAnimation; import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet; import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo; @@ -111,11 +111,13 @@ import com.android.window.flags.Flags; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.animation.SizeChangeAnimation; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.shared.TransitionUtil; +import com.android.wm.shell.shared.animation.Interpolators; import com.android.wm.shell.sysui.ShellInit; import java.util.ArrayList; @@ -125,6 +127,7 @@ import java.util.function.Consumer; /** The default handler that handles anything not already handled. */ public class DefaultTransitionHandler implements Transitions.TransitionHandler { private static final int MAX_ANIMATION_DURATION = 3000; + private static final int SIZE_CHANGE_ANIMATION_DURATION = 400; private final TransactionPool mTransactionPool; private final DisplayController mDisplayController; @@ -134,6 +137,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { private final ShellExecutor mAnimExecutor; private final TransitionAnimation mTransitionAnimation; private final DevicePolicyManager mDevicePolicyManager; + private final TransitionAnimationHelper.RoundedContentTracker mRoundedContentBounds; /** Keeps track of the currently-running animations associated with each transition. */ private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>(); @@ -163,6 +167,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { DefaultTransitionHandler(@NonNull Context context, @NonNull ShellInit shellInit, @NonNull DisplayController displayController, + @NonNull DisplayInsetsController displayInsetsController, @NonNull TransactionPool transactionPool, @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, @NonNull ShellExecutor animExecutor, @@ -179,6 +184,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class); shellInit.addInitCallback(this::onInit, this); mRootTDAOrganizer = rootTDAOrganizer; + mRoundedContentBounds = new TransitionAnimationHelper.RoundedContentTracker( + displayController, displayInsetsController); mInteractionJankMonitor = interactionJankMonitor; } @@ -191,6 +198,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { mMainHandler); TransitionAnimation.initAttributeCache(mContext, mMainHandler); + mRoundedContentBounds.init(); } private void updateEnterpriseThumbnailDrawable() { @@ -297,6 +305,18 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return ROTATION_ANIMATION_SEAMLESS; } + @Nullable + final TransitionAnimationHelper.RoundedContentPerDisplay getRoundedContentBounds( + TransitionInfo.Change change) { + if (!enableDynamicInsetsForAppLaunch()) { + return null; + } + if (change.getTaskInfo() == null && change.getActivityComponent() == null) { + return null; + } + return mRoundedContentBounds.forDisplay(change.getEndDisplayId()); + } + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -556,7 +576,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { change.getEndAbsBounds().left - animRoot.getOffset().x, change.getEndAbsBounds().top - animRoot.getOffset().y); - if (change.getActivityComponent() != null) { + final boolean isActivity = change.getActivityComponent() != null; + if (isActivity) { // For appcompat letterbox: we intentionally report the task-bounds so that we // can animate as-if letterboxes are "part of" the activity. This means we can't // always rely solely on endAbsBounds and need to also max with endRelOffset. @@ -564,7 +585,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { animRelOffset.y = Math.max(animRelOffset.y, change.getEndRelOffset().y); } - if (change.getActivityComponent() != null && !isActivityLevel + if (isActivity && !isActivityLevel && !mRotator.isRotated(change)) { // At this point, this is an independent activity change in a non-activity // transition. This means that an activity transition got erroneously combined @@ -589,8 +610,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, - mTransactionPool, mMainExecutor, animRelOffset, cornerRadius, - clipRect); + mTransactionPool, mMainExecutor, animRelOffset, cornerRadius, clipRect, + isTask || isActivity + ? mRoundedContentBounds.forDisplay(change.getEndDisplayId()) + : null); final TransitionInfo.AnimationOptions options = change.getAnimationOptions(); if (options != null) { @@ -779,15 +802,16 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { private void startBoundsChangeAnimation(@NonNull SurfaceControl.Transaction startT, @NonNull ArrayList<Animator> animations, @NonNull TransitionInfo.Change change, @NonNull Runnable finishCb, @NonNull ShellExecutor mainExecutor) { - final SizeChangeAnimation sca = - new SizeChangeAnimation(change.getStartAbsBounds(), change.getEndAbsBounds()); + final SizeChangeAnimation sca = new SizeChangeAnimation(change.getStartAbsBounds(), + change.getEndAbsBounds(), /* initialScale= */ 1f, /* scaleFactor= */ 1f); sca.initialize(change.getLeash(), change.getSnapshot(), startT); final ValueAnimator va = sca.buildAnimator(change.getLeash(), change.getSnapshot(), (animator) -> mainExecutor.execute(() -> { animations.remove(animator); finishCb.run(); })); - va.setDuration(DEFAULT_APP_TRANSITION_DURATION); + va.setDuration(SIZE_CHANGE_ANIMATION_DURATION); + va.setInterpolator(Interpolators.EMPHASIZED); animations.add(va); } @@ -932,7 +956,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a.restrictDuration(MAX_ANIMATION_DURATION); a.scaleCurrentDuration(mTransitionAnimationScaleSetting); buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool, - mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds()); + mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(), + getRoundedContentBounds(change)); } private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations, @@ -956,7 +981,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a.restrictDuration(MAX_ANIMATION_DURATION); a.scaleCurrentDuration(mTransitionAnimationScaleSetting); buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool, - mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds()); + mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(), + getRoundedContentBounds(change)); } private static int getWallpaperTransitType(TransitionInfo info) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java index cca982142a3a..c71458dec5ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java @@ -29,15 +29,18 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.content.Context; import android.os.IBinder; +import android.view.InsetsState; import android.view.SurfaceControl; import android.window.TransitionInfo; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.shared.IHomeTransitionListener; import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; +import com.android.wm.shell.sysui.ShellInit; /** * The {@link TransitionObserver} that observes for transitions involving the home @@ -51,13 +54,30 @@ public class HomeTransitionObserver implements TransitionObserver, private @NonNull final Context mContext; private @NonNull final ShellExecutor mMainExecutor; + private @NonNull final DisplayInsetsController mDisplayInsetsController; private IBinder mPendingStartDragTransition; private Boolean mPendingHomeVisibilityUpdate; public HomeTransitionObserver(@NonNull Context context, - @NonNull ShellExecutor mainExecutor) { + @NonNull ShellExecutor mainExecutor, + @NonNull DisplayInsetsController displayInsetsController, + @NonNull ShellInit shellInit) { mContext = context; mMainExecutor = mainExecutor; + mDisplayInsetsController = displayInsetsController; + + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, + new DisplayInsetsController.OnInsetsChangedListener() { + @Override + public void insetsChanged(InsetsState insetsState) { + if (mListener == null) return; + mListener.call(l -> l.onDisplayInsetsChanged(insetsState)); + } + }); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java index aa42b7f0ca76..8100f1d1a9a9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java @@ -347,21 +347,21 @@ class ScreenRotationAnimation { @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, - null /* clipRect */); + null /* clipRect */, null); } private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, - null /* clipRect */); + null /* clipRect */, null); } private void buildScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { buildSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, - null /* clipRect */); + null /* clipRect */, null); } private void buildLumaAnimation(@NonNull ArrayList<Animator> animations, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index edfb56019a60..48b48640a37f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -39,6 +39,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WindowConfiguration; import android.graphics.Color; +import android.graphics.Insets; +import android.graphics.Rect; +import android.util.SparseArray; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.animation.Animation; @@ -47,6 +52,9 @@ import android.window.TransitionInfo; import com.android.internal.R; import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.ProtoLog; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.TransitionUtil; @@ -325,4 +333,77 @@ public class TransitionAnimationHelper { } return false; } + + /** + * In some situations (eg. TaskBar) the content area of a display appears to be rounded. For + * these situations, we may want the animation to also express the same rounded corners (even + * though in steady-state, the app internally manages the insets). This class Keeps track of, + * and provides, the bounds of rounded-corner display content. + * + * This is used to enable already-running animations to adapt to changes in taskbar/navbar + * position live. + */ + public static class RoundedContentPerDisplay implements + DisplayInsetsController.OnInsetsChangedListener { + + /** The current bounds of the display content (post-inset). */ + final Rect mBounds = new Rect(); + + @Override + public void insetsChanged(InsetsState insetsState) { + Insets insets = Insets.NONE; + for (int i = insetsState.sourceSize() - 1; i >= 0; i--) { + final InsetsSource source = insetsState.sourceAt(i); + if (!source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { + continue; + } + insets = Insets.max(source.calculateInsets(insetsState.getDisplayFrame(), false), + insets); + } + mBounds.set(insetsState.getDisplayFrame()); + mBounds.inset(insets); + } + } + + /** + * Keeps track of the bounds of rounded-corner display content (post-inset). + * + * @see RoundedContentPerDisplay + */ + public static class RoundedContentTracker implements + DisplayController.OnDisplaysChangedListener { + final DisplayController mDisplayController; + final DisplayInsetsController mDisplayInsetsController; + final SparseArray<RoundedContentPerDisplay> mPerDisplay = new SparseArray<>(); + + RoundedContentTracker(DisplayController dc, DisplayInsetsController dic) { + mDisplayController = dc; + mDisplayInsetsController = dic; + } + + void init() { + mDisplayController.addDisplayWindowListener(this); + } + + RoundedContentPerDisplay forDisplay(int displayId) { + return mPerDisplay.get(displayId); + } + + @Override + public void onDisplayAdded(int displayId) { + final RoundedContentPerDisplay perDisplay = new RoundedContentPerDisplay(); + mDisplayInsetsController.addInsetsChangedListener(displayId, perDisplay); + mPerDisplay.put(displayId, perDisplay); + final DisplayLayout dl = mDisplayController.getDisplayLayout(displayId); + perDisplay.mBounds.set(0, 0, dl.width(), dl.height()); + } + + @Override + public void onDisplayRemoved(int displayId) { + final RoundedContentPerDisplay listener = mPerDisplay.removeReturnOld(displayId); + if (listener != null) { + mDisplayInsetsController.removeInsetsChangedListener(displayId, listener); + } + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 4f49ebcd2e83..3dc8733c879d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -84,6 +84,7 @@ import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; @@ -314,13 +315,14 @@ public class Transitions implements RemoteCallable<Transitions>, @NonNull ShellTaskOrganizer organizer, @NonNull TransactionPool pool, @NonNull DisplayController displayController, + @NonNull DisplayInsetsController displayInsetsController, @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, @NonNull ShellExecutor animExecutor, @NonNull HomeTransitionObserver homeTransitionObserver, @NonNull FocusTransitionObserver focusTransitionObserver) { this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool, - displayController, mainExecutor, mainHandler, animExecutor, + displayController, displayInsetsController, mainExecutor, mainHandler, animExecutor, new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit), homeTransitionObserver, focusTransitionObserver); } @@ -332,6 +334,7 @@ public class Transitions implements RemoteCallable<Transitions>, @NonNull ShellTaskOrganizer organizer, @NonNull TransactionPool pool, @NonNull DisplayController displayController, + @NonNull DisplayInsetsController displayInsetsController, @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, @NonNull ShellExecutor animExecutor, @@ -345,8 +348,8 @@ public class Transitions implements RemoteCallable<Transitions>, mDisplayController = displayController; mPlayerImpl = new TransitionPlayerImpl(); mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit, - displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer, - InteractionJankMonitor.getInstance()); + displayController, displayInsetsController, pool, mainExecutor, mainHandler, + animExecutor, rootTDAOrganizer, InteractionJankMonitor.getInstance()); mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor); mShellCommandHandler = shellCommandHandler; mShellController = shellController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt index 25cf06b4247c..cdadce57d610 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt @@ -50,7 +50,6 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.Accessibilit import androidx.core.view.isGone import com.android.window.flags.Flags import com.android.wm.shell.R -import com.android.wm.shell.bubbles.ContextUtils.isRtl import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_APP_HANDLE_MENU_DESKTOP_VIEW import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_APP_HANDLE_MENU_FULLSCREEN @@ -58,6 +57,7 @@ import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventE import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper +import com.android.wm.shell.shared.bubbles.ContextUtils.isRtl import com.android.wm.shell.shared.split.SplitScreenConstants import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 077b35583e04..f6e49853eddf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -629,9 +629,9 @@ public class StageCoordinatorTests extends ShellTestCase { private Transitions createTestTransitions() { ShellInit shellInit = new ShellInit(mMainExecutor); final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class), - mTaskOrganizer, mTransactionPool, mock(DisplayController.class), mMainExecutor, - mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class), - mock(FocusTransitionObserver.class)); + mTaskOrganizer, mTransactionPool, mock(DisplayController.class), + mDisplayInsetsController, mMainExecutor, mMainHandler, mAnimExecutor, + mock(HomeTransitionObserver.class), mock(FocusTransitionObserver.class)); shellInit.init(); return t; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java index 18fdbeff40f4..6996d44af034 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java @@ -53,6 +53,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.sysui.ShellInit; @@ -78,6 +79,8 @@ public class DefaultTransitionHandlerTest extends ShellTestCase { InstrumentationRegistry.getInstrumentation().getTargetContext(); private final DisplayController mDisplayController = mock(DisplayController.class); + private final DisplayInsetsController mDisplayInsetsController = + mock(DisplayInsetsController.class); private final TransactionPool mTransactionPool = new MockTransactionPool(); private final TestShellExecutor mMainExecutor = new TestShellExecutor(); private final TestShellExecutor mAnimExecutor = new TestShellExecutor(); @@ -95,7 +98,7 @@ public class DefaultTransitionHandlerTest extends ShellTestCase { mContext, mShellInit); mTransitionHandler = new DefaultTransitionHandler( - mContext, mShellInit, mDisplayController, + mContext, mShellInit, mDisplayController, mDisplayInsetsController, mTransactionPool, mMainExecutor, mMainHandler, mAnimExecutor, mRootTaskDisplayAreaOrganizer, mock(InteractionJankMonitor.class)); mShellInit.init(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java index 55bff09e0ae2..52634c08dafd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java @@ -61,6 +61,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.shared.IHomeTransitionListener; import com.android.wm.shell.shared.TransactionPool; @@ -88,6 +89,8 @@ public class HomeTransitionObserverTest extends ShellTestCase { private final TestShellExecutor mMainExecutor = new TestShellExecutor(); private final Handler mMainHandler = new Handler(Looper.getMainLooper()); private final DisplayController mDisplayController = mock(DisplayController.class); + private final DisplayInsetsController mDisplayInsetsController = + mock(DisplayInsetsController.class); private IHomeTransitionListener mListener; private Transitions mTransition; @@ -98,10 +101,11 @@ public class HomeTransitionObserverTest extends ShellTestCase { mListener = mock(IHomeTransitionListener.class); when(mListener.asBinder()).thenReturn(mock(IBinder.class)); - mHomeTransitionObserver = new HomeTransitionObserver(mContext, mMainExecutor); + mHomeTransitionObserver = new HomeTransitionObserver(mContext, mMainExecutor, + mDisplayInsetsController, mock(ShellInit.class)); mTransition = new Transitions(mContext, mock(ShellInit.class), mock(ShellController.class), - mOrganizer, mTransactionPool, mDisplayController, mMainExecutor, - mMainHandler, mAnimExecutor, mHomeTransitionObserver, + mOrganizer, mTransactionPool, mDisplayController, mDisplayInsetsController, + mMainExecutor, mMainHandler, mAnimExecutor, mHomeTransitionObserver, mock(FocusTransitionObserver.class)); mHomeTransitionObserver.setHomeTransitionListener(mTransition, mListener); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 9849b1174d8e..44bb2154f170 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -107,6 +107,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.recents.IRecentsAnimationRunner; @@ -145,6 +146,8 @@ public class ShellTransitionTests extends ShellTestCase { private final ShellExecutor mAnimExecutor = new TestShellExecutor(); private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler(); private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + private final DisplayInsetsController mDisplayInsets = + mock(DisplayInsetsController.class); @Before public void setUp() { @@ -156,9 +159,9 @@ public class ShellTransitionTests extends ShellTestCase { public void instantiate_addInitCallback() { ShellInit shellInit = mock(ShellInit.class); final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class), - mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor, - mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class), - mock(FocusTransitionObserver.class)); + mOrganizer, mTransactionPool, createTestDisplayController(), mDisplayInsets, + mMainExecutor, mMainHandler, mAnimExecutor, + mock(HomeTransitionObserver.class), mock(FocusTransitionObserver.class)); // One from Transitions, one from RootTaskDisplayAreaOrganizer verify(shellInit).addInitCallback(any(), eq(t)); verify(shellInit).addInitCallback(any(), isA(RootTaskDisplayAreaOrganizer.class)); @@ -169,9 +172,9 @@ public class ShellTransitionTests extends ShellTestCase { ShellInit shellInit = new ShellInit(mMainExecutor); ShellController shellController = mock(ShellController.class); final Transitions t = new Transitions(mContext, shellInit, shellController, - mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor, - mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class), - mock(FocusTransitionObserver.class)); + mOrganizer, mTransactionPool, createTestDisplayController(), mDisplayInsets, + mMainExecutor, mMainHandler, mAnimExecutor, + mock(HomeTransitionObserver.class), mock(FocusTransitionObserver.class)); shellInit.init(); verify(shellController, times(1)).addExternalInterface( eq(IShellTransitions.DESCRIPTOR), any(), any()); @@ -1314,8 +1317,9 @@ public class ShellTransitionTests extends ShellTestCase { ShellInit shellInit = new ShellInit(mMainExecutor); final Transitions transitions = new Transitions(mContext, shellInit, mock(ShellController.class), mOrganizer, - mTransactionPool, createTestDisplayController(), mMainExecutor, - mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class), + mTransactionPool, createTestDisplayController(), mDisplayInsets, + mMainExecutor, mMainHandler, mAnimExecutor, + mock(HomeTransitionObserver.class), mock(FocusTransitionObserver.class)); final RecentTasksController mockRecentsTaskController = mock(RecentTasksController.class); doReturn(mContext).when(mockRecentsTaskController).getContext(); @@ -1909,8 +1913,8 @@ public class ShellTransitionTests extends ShellTestCase { private Transitions createTestTransitions() { ShellInit shellInit = new ShellInit(mMainExecutor); final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class), - mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor, - mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class), + mOrganizer, mTransactionPool, createTestDisplayController(), mDisplayInsets, + mMainExecutor, mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class), mock(FocusTransitionObserver.class)); shellInit.init(); return t; diff --git a/libs/androidfw/LocaleDataLookup.cpp b/libs/androidfw/LocaleDataLookup.cpp index 9aacdcb9ca92..ed645826234d 100644 --- a/libs/androidfw/LocaleDataLookup.cpp +++ b/libs/androidfw/LocaleDataLookup.cpp @@ -5774,7 +5774,6 @@ const char* lookupLikelyScript(uint32_t packed_lang_region) { case 0xD2120000u: // squ -> Latn case 0x73724D45u: // sr-ME -> Latn case 0x7372524Fu: // sr-RO -> Latn - case 0x73725255u: // sr-RU -> Latn case 0x73725452u: // sr-TR -> Latn case 0x82320000u: // sra -> Latn case 0x92320000u: // sre -> Latn @@ -7265,7 +7264,6 @@ const char* lookupLikelyScript(uint32_t packed_lang_region) { return SCRIPT_CODES[73u]; case 0x8ACD0000u: // nwc -> Newa return SCRIPT_CODES[74u]; - case 0xB40C474Eu: // man-GN -> Nkoo case 0xBA0D0000u: // nqo -> Nkoo return SCRIPT_CODES[75u]; case 0xDCF90000u: // zhx -> Nshu diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 3ef970830dc4..46dcc1f4c50b 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -164,8 +164,13 @@ std::string Bitmap::getAshmemId(const char* tag, uint64_t bitmapId, android::base::ReadFileToString("/proc/self/cmdline", &temp); return temp; }(); - return std::format("bitmap/{}-id_{}-{}x{}-size_{}-{}", - tag, bitmapId, width, height, size, sCmdline); + /* counter is to ensure the uniqueness of the ashmem filename, + * e.g. a bitmap with same mId could be sent multiple times, an + * ashmem region is created each time + */ + static std::atomic<uint32_t> counter{0}; + return std::format("bitmap/{}_{}_{}x{}_size-{}_id-{}_{}", + tag, counter.fetch_add(1), width, height, size, bitmapId, sCmdline); } sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(SkBitmap* bitmap) { diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index c9ec31bab048..7221f1ddeb7f 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -259,3 +259,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_output_switcher_personal_audio_sharing" + namespace: "cross_device_experiences" + description: "Enables personal audio sharing in the output switcher." + bug: "385672684" +}
\ No newline at end of file diff --git a/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm b/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm index 6c947c77ad3d..455fb83474b6 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm @@ -37,8 +37,8 @@ key 0 { key 1 { label: '1' base: '1' - shift: '!' - ralt: '\u0303' + shift: '\'' + ralt: '\u007e' } key 2 { @@ -80,7 +80,7 @@ key 7 { label: '7' base: '7' shift: '=' - ralt: '\u0300' + ralt: '`' } key 8 { @@ -374,6 +374,7 @@ key M { base: 'm' shift, capslock: 'M' shift+capslock: 'm' + ralt: '<' } key COMMA { @@ -387,6 +388,7 @@ key PERIOD { label: '.' base: '.' shift: ':' + ralt: '>' } key MINUS { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java index bcc737a351a9..d05aaaa6e389 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java @@ -52,8 +52,11 @@ import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.Objects; +import java.util.stream.Stream; /** * This is a utility class for defining some utility methods and constants @@ -69,7 +72,8 @@ public class PackageUtil { //intent attribute strings related to uninstall public static final String INTENT_ATTR_PACKAGE_NAME=PREFIX+"PackageName"; private static final String DOWNLOADS_AUTHORITY = "downloads"; - private static final String SPLIT_BASE_APK_END_WITH = "base.apk"; + private static final String SPLIT_BASE_APK_SUFFIX = "base.apk"; + private static final String SPLIT_APK_SUFFIX = ".apk"; /** * Utility method to get package information for a given {@link File} @@ -77,11 +81,20 @@ public class PackageUtil { @Nullable public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) { String filePath = sourceFile.getAbsolutePath(); - if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) { + if (filePath.endsWith(SPLIT_BASE_APK_SUFFIX)) { File dir = sourceFile.getParentFile(); - if (dir.listFiles().length > 1) { - // split apks, use file directory to get archive info - filePath = dir.getPath(); + try (Stream<Path> list = Files.list(dir.toPath())) { + long count = list + .filter((name) -> name.endsWith(SPLIT_APK_SUFFIX)) + .limit(2) + .count(); + if (count > 1) { + // split apks, use file directory to get archive info + filePath = dir.getPath(); + } + } catch (Exception ignored) { + // No access to the parent directory, proceed to read app snippet + // from the base apk only } } try { @@ -240,9 +253,10 @@ public class PackageUtil { appInfo.publicSourceDir = archiveFilePath; if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) { - final File[] files = sourceFile.getParentFile().listFiles(); + final File[] files = sourceFile.getParentFile().listFiles( + (dir, name) -> name.endsWith(SPLIT_APK_SUFFIX)); final String[] splits = Arrays.stream(appInfo.splitNames) - .map(i -> findFilePath(files, i + ".apk")) + .map(i -> findFilePath(files, i + SPLIT_APK_SUFFIX)) .filter(Objects::nonNull) .toArray(String[]::new); @@ -283,7 +297,9 @@ public class PackageUtil { } private static String findFilePath(File[] files, String postfix) { - for (File file : files) { + final int length = files != null ? files.length : 0; + for (int i = 0; i < length; i++) { + File file = files[i]; final String path = file.getAbsolutePath(); if (path.endsWith(postfix)) { return path; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt index e8477ef261a8..b84b903ac1cb 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt @@ -41,6 +41,8 @@ import android.util.Log import com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet import java.io.ByteArrayOutputStream import java.io.File +import java.nio.file.Files +import java.nio.file.Path import kotlinx.parcelize.Parceler import kotlinx.parcelize.Parcelize @@ -48,6 +50,7 @@ object PackageUtil { private val LOG_TAG = InstallRepository::class.java.simpleName private const val DOWNLOADS_AUTHORITY = "downloads" private const val SPLIT_BASE_APK_SUFFIX = "base.apk" + private const val SPLIT_APK_SUFFIX = ".apk" const val localLogv = false const val ARGS_ABORT_REASON: String = "abort_reason" @@ -440,9 +443,20 @@ object PackageUtil { var filePath = sourceFile.absolutePath if (filePath.endsWith(SPLIT_BASE_APK_SUFFIX)) { val dir = sourceFile.parentFile - if ((dir?.listFiles()?.size ?: 0) > 1) { - // split apks, use file directory to get archive info - filePath = dir.path + try { + Files.list(dir.toPath()).use { list -> + val count: Long = list + .filter { name: Path -> name.endsWith(SPLIT_APK_SUFFIX) } + .limit(2) + .count() + if (count > 1) { + // split apks, use file directory to get archive info + filePath = dir.path + } + } + } catch (ignored: Exception) { + // No access to the parent directory, proceed to read app snippet + // from the base apk only } } return try { diff --git a/packages/SettingsLib/ValuePreference/Android.bp b/packages/SettingsLib/ValuePreference/Android.bp new file mode 100644 index 000000000000..1cdcd2247eaf --- /dev/null +++ b/packages/SettingsLib/ValuePreference/Android.bp @@ -0,0 +1,31 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_library { + name: "SettingsLibValuePreference", + use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + static_libs: [ + "androidx.annotation_annotation", + "androidx.preference_preference", + "SettingsLibSettingsTheme", + ], + + sdk_version: "system_current", + min_sdk_version: "23", + apex_available: [ + "//apex_available:platform", + ], +} diff --git a/packages/SettingsLib/ValuePreference/AndroidManifest.xml b/packages/SettingsLib/ValuePreference/AndroidManifest.xml new file mode 100644 index 000000000000..217282be9c80 --- /dev/null +++ b/packages/SettingsLib/ValuePreference/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.widget.preference.value"> + + <uses-sdk android:minSdkVersion="21"/> + +</manifest> diff --git a/packages/SettingsLib/ValuePreference/res/layout/settingslib_expressive_value_preference.xml b/packages/SettingsLib/ValuePreference/res/layout/settingslib_expressive_value_preference.xml new file mode 100644 index 000000000000..4fd3ab10fb16 --- /dev/null +++ b/packages/SettingsLib/ValuePreference/res/layout/settingslib_expressive_value_preference.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="72dp" + android:gravity="center_vertical" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:background="?android:attr/selectableItemBackground" + android:clipToPadding="false" + android:baselineAligned="false" + android:filterTouchesWhenObscured="false"> + + <include layout="@layout/settingslib_expressive_preference_icon_frame"/> + + <include layout="@layout/settingslib_expressive_value_preference_text_frame"/> + + <!-- Preference should place its actual preference widget here. --> + <LinearLayout + android:id="@android:id/widget_frame" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="end|center_vertical" + android:paddingStart="@dimen/settingslib_expressive_space_small1" + android:paddingEnd="0dp" + android:orientation="vertical"/> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/ValuePreference/res/layout/settingslib_expressive_value_preference_text_frame.xml b/packages/SettingsLib/ValuePreference/res/layout/settingslib_expressive_value_preference_text_frame.xml new file mode 100644 index 000000000000..a385ee521a74 --- /dev/null +++ b/packages/SettingsLib/ValuePreference/res/layout/settingslib_expressive_value_preference_text_frame.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="@dimen/settingslib_expressive_space_none" + android:layout_height="wrap_content" + android:layout_weight="1" + android:paddingVertical="@dimen/settingslib_expressive_space_small1" + android:paddingStart="@dimen/settingslib_expressive_space_none" + android:paddingEnd="@dimen/settingslib_expressive_space_small1" + android:filterTouchesWhenObscured="false"> + + <TextView + android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:textAlignment="viewStart" + android:maxLines="2" + android:textAppearance="@style/TextAppearance.SettingsLib.ValuePreferenceTitle" + android:ellipsize="marquee"/> + + <TextView + android:id="@android:id/summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@android:id/title" + android:layout_alignStart="@android:id/title" + android:layout_gravity="start" + android:textAlignment="viewStart" + android:textAppearance="?android:attr/textAppearanceListItemSecondary" + android:textColor="?android:attr/textColorSecondary" + android:maxLines="10"/> +</RelativeLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/ValuePreference/res/values/styles.xml b/packages/SettingsLib/ValuePreference/res/values/styles.xml new file mode 100644 index 000000000000..284819498f64 --- /dev/null +++ b/packages/SettingsLib/ValuePreference/res/values/styles.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<resources xmlns:tools="http://schemas.android.com/tools"> + <style name="TextAppearance.SettingsLib.ValuePreferenceTitle" + parent="@style/TextAppearance.SettingsLib.DisplaySmall"> + <item name="android:textColor">@color/settingslib_text_color_primary</item> + </style> +</resources> diff --git a/packages/SettingsLib/ValuePreference/src/com/android/settingslib/widget/ValuePreference.kt b/packages/SettingsLib/ValuePreference/src/com/android/settingslib/widget/ValuePreference.kt new file mode 100644 index 000000000000..475638c8b406 --- /dev/null +++ b/packages/SettingsLib/ValuePreference/src/com/android/settingslib/widget/ValuePreference.kt @@ -0,0 +1,44 @@ +/* + * 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.settingslib.widget + +import android.content.Context +import android.util.AttributeSet +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import com.android.settingslib.widget.preference.value.R + +/** The BulletPreference shows a text which describe a feature. */ +class ValuePreference +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : Preference(context, attrs, defStyleAttr, defStyleRes) { + + init { + layoutResource = R.layout.settingslib_expressive_value_preference + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + holder.isDividerAllowedAbove = false + holder.isDividerAllowedBelow = false + } +} diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 1297aa3ff7d5..6a7e048b577a 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -288,7 +288,7 @@ <string name="bluetooth_profile_hid">Input device</string> <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] --> <string name="bluetooth_profile_pan">Internet access</string> - <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] --> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=80] --> <string name="bluetooth_profile_pbap">Allow access to contacts and call history</string> <!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] --> <string name="bluetooth_profile_pbap_summary">Info will be used for call announcements and more</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java index 155c7e6530aa..e46574cef917 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java @@ -37,9 +37,11 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.settingslib.R; +import com.android.settingslib.flags.Flags; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; public class LeAudioProfile implements LocalBluetoothProfile { @@ -59,6 +61,10 @@ public class LeAudioProfile implements LocalBluetoothProfile { // Order of this profile in device profiles list private static final int ORDINAL = 1; + // Cached callbacks being registered before service is connected. + private ConcurrentHashMap<BluetoothLeAudio.Callback, Executor> + mCachedCallbackExecutorMap = new ConcurrentHashMap<>(); + // These callbacks run on the main thread. private final class LeAudioServiceListener implements BluetoothProfile.ServiceListener { @@ -88,7 +94,19 @@ public class LeAudioProfile implements LocalBluetoothProfile { // Check current list of CachedDevices to see if any are hearing aid devices. mDeviceManager.updateHearingAidsDevices(); mProfileManager.callServiceConnectedListeners(); - mIsProfileReady = true; + if (!mIsProfileReady) { + mIsProfileReady = true; + if (Flags.adoptPrimaryGroupManagementApiV2()) { + if (DEBUG) { + Log.d( + TAG, + "onServiceConnected, register mCachedCallbackExecutorMap = " + + mCachedCallbackExecutorMap); + } + mCachedCallbackExecutorMap.forEach( + (callback, executor) -> registerCallback(executor, callback)); + } + } } public void onServiceDisconnected(int profile) { @@ -96,7 +114,12 @@ public class LeAudioProfile implements LocalBluetoothProfile { Log.d(TAG, "Bluetooth service disconnected"); } mProfileManager.callServiceDisconnectedListeners(); - mIsProfileReady = false; + if (mIsProfileReady) { + mIsProfileReady = false; + if (Flags.adoptPrimaryGroupManagementApiV2()) { + mCachedCallbackExecutorMap.clear(); + } + } } } @@ -367,6 +390,9 @@ public class LeAudioProfile implements LocalBluetoothProfile { @NonNull BluetoothLeAudio.Callback callback) { if (mService == null) { Log.w(TAG, "Proxy not attached to service. Cannot register callback."); + if (Flags.adoptPrimaryGroupManagementApiV2()) { + mCachedCallbackExecutorMap.putIfAbsent(callback, executor); + } return; } mService.registerCallback(executor, callback); @@ -384,6 +410,9 @@ public class LeAudioProfile implements LocalBluetoothProfile { * callback is registered */ public void unregisterCallback(@NonNull BluetoothLeAudio.Callback callback) { + if (Flags.adoptPrimaryGroupManagementApiV2()) { + mCachedCallbackExecutorMap.remove(callback); + } if (mService == null) { Log.w(TAG, "Proxy not attached to service. Cannot unregister callback."); return; diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index d0f84627f8d8..463e94bfa0d6 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -464,6 +464,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, new InclusiveIntegerRangeValidator(0, 1)); VALIDATORS.put(Secure.ADVANCED_PROTECTION_MODE, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.AAPM_USB_DATA_PROTECTION, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_APP_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 7179cbdf93fb..e9b6c73cb800 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -642,6 +642,7 @@ public class SettingsBackupTest { private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS = newHashSet( + Settings.Secure.AAPM_USB_DATA_PROTECTION, Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, // Deprecated since O. Settings.Secure.ALLOW_PRIMARY_GAIA_ACCOUNT_REMOVAL_FOR_TESTS, diff --git a/packages/StatementService/Android.bp b/packages/StatementService/Android.bp index 39b0302beff8..3fc9aabb7edb 100644 --- a/packages/StatementService/Android.bp +++ b/packages/StatementService/Android.bp @@ -32,7 +32,11 @@ android_app { }, target_sdk_version: "29", platform_apis: true, + system_ext_specific: true, privileged: true, + required: [ + "privapp_whitelist_com.android.statementservice", + ], certificate: "platform", static_libs: [ "StatementServiceParser", diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index c6bc1c70ad18..3cb30258fcb1 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -155,13 +155,3 @@ flag { purpose: PURPOSE_BUGFIX } } - -flag { - name: "hearing_devices_input_routing_ui_improvement" - namespace: "accessibility" - description: "UI improvement for hearing device input routing feature" - bug: "397314200" - metadata { - purpose: PURPOSE_BUGFIX - } -} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt index fdb07bdbe7f3..576ff61c4f94 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt @@ -83,7 +83,7 @@ constructor( ViewTransitionRegistry.instance } else { null - } + }, ) : ActivityTransitionAnimator.Controller { override val isLaunching: Boolean = true @@ -313,9 +313,17 @@ constructor( // visibility that is saved by `setShouldBlockVisibilityChanges()` for a later restoration. (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true) - // Create a ghost of the view that will be moving and fading out. This allows to fade out - // the content before fading out the background. - ghostView = GhostView.addGhost(ghostedView, transitionContainer) + try { + // Create a ghost of the view that will be moving and fading out. This allows to fade + // out the content before fading out the background. + ghostView = GhostView.addGhost(ghostedView, transitionContainer) + } catch (e: Exception) { + // It is not 100% clear what conditions cause this exception to happen in practice, and + // we could never reproduce it, but it does show up extremely rarely. We already handle + // the scenario where ghostView is null, so we just avoid crashing and log the error. + // See b/315858472 for an investigation of the issue. + Log.e(TAG, "Failed to create ghostView", e) + } // [GhostView.addGhost], the result of which is our [ghostView], creates a [GhostView], and // adds it first to a [FrameLayout] container. It then adds _that_ container to an diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt index 5f71b19fbc3f..884666854ab4 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt @@ -111,10 +111,6 @@ class ComposedDigitalLayerController(private val clockCtx: ClockContext) : override fun onZenDataChanged(data: ZenData) {} - override fun onFontAxesChanged(axes: ClockAxisStyle) { - view.updateAxes(axes) - } - override var isReactiveTouchInteractionEnabled get() = view.isReactiveTouchInteractionEnabled set(value) { @@ -152,6 +148,13 @@ class ComposedDigitalLayerController(private val clockCtx: ClockContext) : override fun onFidgetTap(x: Float, y: Float) { view.animateFidget(x, y) } + + private var hasFontAxes = false + + override fun onFontAxesChanged(style: ClockAxisStyle) { + view.updateAxes(style, isAnimated = hasFontAxes) + hasFontAxes = true + } } override val faceEvents = diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index 3cfa78d17fe7..450cece8709a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -231,8 +231,6 @@ class DefaultClockController( override fun onAlarmDataChanged(data: AlarmData) {} override fun onZenDataChanged(data: ZenData) {} - - override fun onFontAxesChanged(axes: ClockAxisStyle) {} } open inner class DefaultClockAnimations( @@ -285,6 +283,8 @@ class DefaultClockController( override fun onPositionUpdated(distance: Float, fraction: Float) {} override fun onFidgetTap(x: Float, y: Float) {} + + override fun onFontAxesChanged(style: ClockAxisStyle) {} } inner class LargeClockAnimations( diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt index 8744357a74c9..84f45fcc0532 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt @@ -99,12 +99,6 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController smallClock.events.onZenDataChanged(data) largeClock.events.onZenDataChanged(data) } - - override fun onFontAxesChanged(axes: ClockAxisStyle) { - val fontAxes = ClockAxisStyle(getDefaultAxes(clockCtx.settings).merge(axes)) - smallClock.events.onFontAxesChanged(fontAxes) - largeClock.events.onFontAxesChanged(fontAxes) - } } override fun initialize( @@ -113,10 +107,10 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController foldFraction: Float, clockListener: ClockEventListener?, ) { - events.onFontAxesChanged(clockCtx.settings.axes) smallClock.run { layerController.onViewBoundsChanged = { clockListener?.onBoundsChanged(it) } events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme)) + animations.onFontAxesChanged(clockCtx.settings.axes) animations.doze(dozeFraction) animations.fold(foldFraction) events.onTimeTick() @@ -125,6 +119,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController largeClock.run { layerController.onViewBoundsChanged = { clockListener?.onBoundsChanged(it) } events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme)) + animations.onFontAxesChanged(clockCtx.settings.axes) animations.doze(dozeFraction) animations.fold(foldFraction) events.onTimeTick() diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt index 171a68f72e20..ec7803d0c6d6 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt @@ -31,10 +31,12 @@ import com.android.systemui.plugins.clocks.ClockFaceConfig import com.android.systemui.plugins.clocks.ClockFaceController import com.android.systemui.plugins.clocks.ClockFaceEvents import com.android.systemui.plugins.clocks.ClockFaceLayout +import com.android.systemui.plugins.clocks.ClockFontAxis.Companion.merge import com.android.systemui.plugins.clocks.DefaultClockFaceLayout import com.android.systemui.plugins.clocks.ThemeConfig import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData +import com.android.systemui.shared.clocks.FlexClockController.Companion.getDefaultAxes import com.android.systemui.shared.clocks.FontUtils.get import com.android.systemui.shared.clocks.FontUtils.set import com.android.systemui.shared.clocks.ViewUtils.computeLayoutDiff @@ -131,15 +133,6 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: layerController.faceEvents.onThemeChanged(theme) } - override fun onFontAxesChanged(settings: ClockAxisStyle) { - var axes = ClockAxisStyle(settings) - if (!isLargeClock && axes[GSFAxes.WIDTH] > SMALL_CLOCK_MAX_WDTH) { - axes[GSFAxes.WIDTH] = SMALL_CLOCK_MAX_WDTH - } - - layerController.events.onFontAxesChanged(axes) - } - /** * targetRegion passed to all customized clock applies counter translationY of Keyguard and * keyguard_large_clock_top_margin from default clock @@ -232,6 +225,15 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: override fun onFidgetTap(x: Float, y: Float) { layerController.animations.onFidgetTap(x, y) } + + override fun onFontAxesChanged(style: ClockAxisStyle) { + var axes = ClockAxisStyle(getDefaultAxes(clockCtx.settings).merge(style)) + if (!isLargeClock && axes[GSFAxes.WIDTH] > SMALL_CLOCK_MAX_WDTH) { + axes[GSFAxes.WIDTH] = SMALL_CLOCK_MAX_WDTH + } + + layerController.animations.onFontAxesChanged(axes) + } } companion object { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt index 7be9a936cbd3..eef910c3bd27 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt @@ -171,10 +171,6 @@ open class SimpleDigitalHandLayerController( override fun onAlarmDataChanged(data: AlarmData) {} override fun onZenDataChanged(data: ZenData) {} - - override fun onFontAxesChanged(axes: ClockAxisStyle) { - view.updateAxes(axes) - } } override val animations = @@ -195,6 +191,13 @@ open class SimpleDigitalHandLayerController( view.dozeFraction = fraction } + private var hasFontAxes = false + + override fun onFontAxesChanged(style: ClockAxisStyle) { + view.updateAxes(style, isAnimated = hasFontAxes) + hasFontAxes = true + } + override fun fold(fraction: Float) { applyLayout() refreshTime() diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt index 4531aed0e83d..4a5532b6e462 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt @@ -272,8 +272,8 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) { invalidate() } - fun updateAxes(axes: ClockAxisStyle) { - childViews.forEach { view -> view.updateAxes(axes) } + fun updateAxes(axes: ClockAxisStyle, isAnimated: Boolean) { + childViews.forEach { view -> view.updateAxes(axes, isAnimated) } requestLayout() } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index 377a24c2899b..05c9818f0c57 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -200,11 +200,11 @@ open class SimpleDigitalClockTextView( invalidate() } - fun updateAxes(lsAxes: ClockAxisStyle) { + fun updateAxes(lsAxes: ClockAxisStyle, isAnimated: Boolean) { lsFontVariation = lsAxes.toFVar() aodFontVariation = lsAxes.copyWith(fixedAodAxes).toFVar() fidgetFontVariation = buildFidgetVariation(lsAxes).toFVar() - logger.updateAxes(lsFontVariation, aodFontVariation) + logger.updateAxes(lsFontVariation, aodFontVariation, isAnimated) lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation) typeface = lockScreenPaint.typeface @@ -212,7 +212,15 @@ open class SimpleDigitalClockTextView( textBounds = lockScreenPaint.getTextBounds(text) targetTextBounds = textBounds - textAnimator.setTextStyle(TextAnimator.Style(fVar = lsFontVariation)) + textAnimator.setTextStyle( + TextAnimator.Style(fVar = lsFontVariation), + TextAnimator.Animation( + animate = isAnimated && isAnimationEnabled, + duration = AXIS_CHANGE_ANIMATION_DURATION, + interpolator = aodDozingInterpolator, + ), + ) + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) recomputeMaxSingleDigitSizes() requestLayout() @@ -651,6 +659,7 @@ open class SimpleDigitalClockTextView( .compose() val CHARGE_ANIMATION_DURATION = 500L + val AXIS_CHANGE_ANIMATION_DURATION = 400L val FIDGET_ANIMATION_DURATION = 250L val FIDGET_INTERPOLATOR = PathInterpolator(0.26873f, 0f, 0.45042f, 1f) val FIDGET_DISTS = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java index a58f7f72f08a..ae5cd0196e72 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java @@ -41,6 +41,7 @@ import android.app.NotificationChannel; import android.os.Handler; import android.os.UserHandle; import android.provider.DeviceConfig; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.testing.TestableLooper; @@ -101,52 +102,53 @@ public class AssistantFeedbackControllerTest extends SysuiTestCase { switchFlag("false"); // test flag disables logic with default values assertEquals(STATUS_UNCHANGED, mAssistantFeedbackController.getFeedbackStatus( - getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED))); + getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED).getRanking())); assertNull(mAssistantFeedbackController.getFeedbackIcon( - getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED))); + getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED).getRanking())); // test that the flag disables logic with values that otherwise would return a value assertEquals(STATUS_UNCHANGED, mAssistantFeedbackController.getFeedbackStatus( - getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_PROMOTED))); + getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_PROMOTED).getRanking())); assertNull(mAssistantFeedbackController.getFeedbackIcon( - getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_PROMOTED))); + getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_PROMOTED).getRanking())); } @Test public void testFeedback_noChange() { switchFlag("true"); assertEquals(STATUS_UNCHANGED, mAssistantFeedbackController.getFeedbackStatus( - getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED))); + getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED).getRanking())); assertNull(mAssistantFeedbackController.getFeedbackIcon( - getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED))); + getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED).getRanking())); } @Test public void testFeedback_changedImportance() { switchFlag("true"); - NotificationEntry entry = getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_UNCHANGED); - assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(entry)); - assertNotNull(mAssistantFeedbackController.getFeedbackIcon(entry)); - - entry = getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_LOW, RANKING_UNCHANGED); - assertEquals(STATUS_SILENCED, mAssistantFeedbackController.getFeedbackStatus(entry)); - assertNotNull(mAssistantFeedbackController.getFeedbackIcon(entry)); - - entry = getEntry(IMPORTANCE_LOW, IMPORTANCE_MIN, RANKING_UNCHANGED); - assertEquals(STATUS_DEMOTED, mAssistantFeedbackController.getFeedbackStatus(entry)); - assertNotNull(mAssistantFeedbackController.getFeedbackIcon(entry)); + NotificationListenerService.Ranking ranking = + getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_UNCHANGED).getRanking(); + assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(ranking)); + assertNotNull(mAssistantFeedbackController.getFeedbackIcon(ranking)); + + ranking = getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_LOW, RANKING_UNCHANGED).getRanking(); + assertEquals(STATUS_SILENCED, mAssistantFeedbackController.getFeedbackStatus(ranking)); + assertNotNull(mAssistantFeedbackController.getFeedbackIcon(ranking)); + + ranking = getEntry(IMPORTANCE_LOW, IMPORTANCE_MIN, RANKING_UNCHANGED).getRanking(); + assertEquals(STATUS_DEMOTED, mAssistantFeedbackController.getFeedbackStatus(ranking)); + assertNotNull(mAssistantFeedbackController.getFeedbackIcon(ranking)); } @Test public void testFeedback_changedRanking() { switchFlag("true"); - NotificationEntry entry = - getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_PROMOTED); - assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(entry)); - assertNotNull(mAssistantFeedbackController.getFeedbackIcon(entry)); - - entry = getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_DEMOTED); - assertEquals(STATUS_DEMOTED, mAssistantFeedbackController.getFeedbackStatus(entry)); - assertNotNull(mAssistantFeedbackController.getFeedbackIcon(entry)); + NotificationListenerService.Ranking ranking = + getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_PROMOTED).getRanking(); + assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(ranking)); + assertNotNull(mAssistantFeedbackController.getFeedbackIcon(ranking)); + + ranking = getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_DEMOTED).getRanking(); + assertEquals(STATUS_DEMOTED, mAssistantFeedbackController.getFeedbackStatus(ranking)); + assertNotNull(mAssistantFeedbackController.getFeedbackIcon(ranking)); } @Test @@ -155,7 +157,7 @@ public class AssistantFeedbackControllerTest extends SysuiTestCase { FeedbackIcon expected = new FeedbackIcon(com.android.internal.R.drawable.ic_feedback_uprank, com.android.internal.R.string.notification_feedback_indicator_promoted); assertEquals(expected, mAssistantFeedbackController.getFeedbackIcon( - getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_PROMOTED))); + getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_PROMOTED).getRanking())); } private NotificationEntry getEntry(int oldImportance, int newImportance, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt index 101874807474..52f68bf4d729 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt @@ -25,7 +25,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON +import com.android.systemui.statusbar.notification.row.entryAdapterFactory import com.android.systemui.statusbar.notification.shared.NotificationBundleUi +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule @@ -36,13 +38,15 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @RunWithLooper class BundleEntryAdapterTest : SysuiTestCase() { + private val kosmos = testKosmos() private lateinit var underTest: BundleEntryAdapter @get:Rule val setFlagsRule = SetFlagsRule() + private val factory: EntryAdapterFactory = kosmos.entryAdapterFactory @Before fun setUp() { - underTest = BundleEntryAdapter(BundleEntry("key")) + underTest = factory.create(BundleEntry("key")) as BundleEntryAdapter } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt index 7449064bc65b..02c6a484bd43 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt @@ -29,6 +29,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.res.R import com.android.systemui.statusbar.RankingBuilder +import com.android.systemui.statusbar.notification.collection.coordinator.mockVisualStabilityCoordinator +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.provider.mockHighPriorityProvider import com.android.systemui.statusbar.notification.mockNotificationActivityStarter import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow @@ -41,9 +44,12 @@ import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.eq @SmallTest @RunWith(AndroidJUnit4::class) @@ -257,6 +263,109 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getRanking() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .addAction(Mockito.mock(Notification.Action::class.java)) + .build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.ranking).isEqualTo(entry.ranking) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun endLifetimeExtension() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .addAction(Mockito.mock(Notification.Action::class.java)) + .build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + val callback = + Mockito.mock(NotifLifetimeExtender.OnEndLifetimeExtensionCallback::class.java) + + underTest = factory.create(entry) as NotificationEntryAdapter + underTest.endLifetimeExtension(callback, Mockito.mock(NotifLifetimeExtender::class.java)) + verify(callback).onEndLifetimeExtension(any(), eq(entry)) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun onImportanceChanged() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .addAction(Mockito.mock(Notification.Action::class.java)) + .build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + + underTest = factory.create(entry) as NotificationEntryAdapter + + underTest.onImportanceChanged() + verify(kosmos.mockVisualStabilityCoordinator) + .temporarilyAllowSectionChanges(eq(entry), anyLong()) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun markForUserTriggeredMovement() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .addAction(Mockito.mock(Notification.Action::class.java)) + .build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + underTest = factory.create(entry) as NotificationEntryAdapter + + assertThat(underTest.isMarkedForUserTriggeredMovement) + .isEqualTo(entry.isMarkedForUserTriggeredMovement) + + underTest.markForUserTriggeredMovement() + assertThat(underTest.isMarkedForUserTriggeredMovement) + .isEqualTo(entry.isMarkedForUserTriggeredMovement) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun isHighPriority() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .addAction(Mockito.mock(Notification.Action::class.java)) + .build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + underTest = factory.create(entry) as NotificationEntryAdapter + + underTest.isHighPriority + + verify(kosmos.mockHighPriorityProvider).isHighPriority(entry) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun isBlockable() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .addAction(Mockito.mock(Notification.Action::class.java)) + .build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + underTest = factory.create(entry) as NotificationEntryAdapter + + assertThat(underTest.isBlockable).isEqualTo(entry.isBlockable) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) fun canDragAndDrop() { val pi = mock(PendingIntent::class.java) Mockito.`when`(pi.isActivity).thenReturn(true) @@ -436,7 +545,6 @@ class NotificationEntryAdapterTest : SysuiTestCase() { underTest = factory.create(entry) as NotificationEntryAdapter - underTest.onEntryClicked(row) verify(kosmos.mockNotificationActivityStarter).onNotificationClicked(entry, row) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt index 7fe97d27f273..928b0014dc52 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt @@ -15,12 +15,15 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.statusbar.notification.collection.EntryAdapter import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder @@ -30,6 +33,9 @@ import com.android.systemui.statusbar.notification.collection.render.NotifGutsVi import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager import com.android.systemui.statusbar.notification.row.NotificationGuts import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent +import com.android.systemui.statusbar.notification.row.entryAdapterFactory +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -45,12 +51,16 @@ import org.mockito.MockitoAnnotations.initMocks @RunWith(AndroidJUnit4::class) @RunWithLooper class GutsCoordinatorTest : SysuiTestCase() { + + private val kosmos = testKosmos() private lateinit var coordinator: GutsCoordinator private lateinit var notifLifetimeExtender: NotifLifetimeExtender private lateinit var notifGutsViewListener: NotifGutsViewListener private lateinit var entry1: NotificationEntry private lateinit var entry2: NotificationEntry + private lateinit var entryAdapter1: EntryAdapter + private lateinit var entryAdapter2: EntryAdapter @Mock private lateinit var notifGutsViewManager: NotifGutsViewManager @Mock private lateinit var pipeline: NotifPipeline @@ -73,12 +83,60 @@ class GutsCoordinatorTest : SysuiTestCase() { notifLifetimeExtender.setCallback(lifetimeExtenderCallback) entry1 = NotificationEntryBuilder().setId(1).build() entry2 = NotificationEntryBuilder().setId(2).build() + entryAdapter1 = kosmos.entryAdapterFactory.create(entry1) + entryAdapter2 = kosmos.entryAdapterFactory.create(entry2) whenever(notificationGuts.gutsContent).thenReturn(mock(GutsContent::class.java)) } @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) fun testSimpleLifetimeExtension() { assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + notifGutsViewListener.onGutsOpen(entryAdapter1, notificationGuts) + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() + notifGutsViewListener.onGutsClose(entryAdapter1) + verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun testDoubleOpenLifetimeExtension() { + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + notifGutsViewListener.onGutsOpen(entryAdapter1, notificationGuts) + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() + notifGutsViewListener.onGutsOpen(entryAdapter1, notificationGuts) + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() + notifGutsViewListener.onGutsClose(entryAdapter1) + verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun testTwoEntryLifetimeExtension() { + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse() + notifGutsViewListener.onGutsOpen(entryAdapter1, notificationGuts) + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse() + notifGutsViewListener.onGutsOpen(entryAdapter2, notificationGuts) + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isTrue() + notifGutsViewListener.onGutsClose(entryAdapter1) + verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isTrue() + notifGutsViewListener.onGutsClose(entryAdapter2) + verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry2) + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse() + } + + @Test + @DisableFlags(NotificationBundleUi.FLAG_NAME) + fun testSimpleLifetimeExtension_entry() { + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() notifGutsViewListener.onGutsOpen(entry1, notificationGuts) assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() notifGutsViewListener.onGutsClose(entry1) @@ -87,7 +145,8 @@ class GutsCoordinatorTest : SysuiTestCase() { } @Test - fun testDoubleOpenLifetimeExtension() { + @DisableFlags(NotificationBundleUi.FLAG_NAME) + fun testDoubleOpenLifetimeExtension_entry() { assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() notifGutsViewListener.onGutsOpen(entry1, notificationGuts) assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() @@ -99,7 +158,8 @@ class GutsCoordinatorTest : SysuiTestCase() { } @Test - fun testTwoEntryLifetimeExtension() { + @DisableFlags(NotificationBundleUi.FLAG_NAME) + fun testTwoEntryLifetimeExtension_entry() { assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse() notifGutsViewListener.onGutsOpen(entry1, notificationGuts) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java index 0947cd5aaabb..e808deb1d5ad 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java @@ -44,6 +44,7 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.os.UserHandle; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.view.LayoutInflater; import android.view.View; @@ -132,7 +133,7 @@ public class FeedbackInfoTest extends SysuiTestCase { @Test public void testBindNotification_SetsTextApplicationName() { when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); - mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), + mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry().getRanking(), mMockNotificationRow, mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager); final TextView textView = mFeedbackInfo.findViewById(R.id.pkg_name); @@ -144,7 +145,7 @@ public class FeedbackInfoTest extends SysuiTestCase { final Drawable iconDrawable = mock(Drawable.class); when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class))) .thenReturn(iconDrawable); - mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), + mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry().getRanking(), mMockNotificationRow, mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager); final ImageView iconView = mFeedbackInfo.findViewById(R.id.pkg_icon); @@ -153,9 +154,11 @@ public class FeedbackInfoTest extends SysuiTestCase { @Test public void testPrompt_silenced() { - when(mAssistantFeedbackController.getFeedbackStatus(any(NotificationEntry.class))) + when(mAssistantFeedbackController.getFeedbackStatus( + any(NotificationListenerService.Ranking.class))) .thenReturn(STATUS_SILENCED); - mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), mMockNotificationRow, + mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry().getRanking(), + mMockNotificationRow, mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager); TextView prompt = mFeedbackInfo.findViewById(R.id.prompt); assertEquals("This notification was automatically demoted to Silent by the system. " @@ -165,9 +168,11 @@ public class FeedbackInfoTest extends SysuiTestCase { @Test public void testPrompt_promoted() { - when(mAssistantFeedbackController.getFeedbackStatus(any(NotificationEntry.class))) + when(mAssistantFeedbackController.getFeedbackStatus( + any(NotificationListenerService.Ranking.class))) .thenReturn(STATUS_PROMOTED); - mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), mMockNotificationRow, + mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry().getRanking(), + mMockNotificationRow, mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager); TextView prompt = mFeedbackInfo.findViewById(R.id.prompt); assertEquals("This notification was automatically ranked higher in your shade. " @@ -177,9 +182,11 @@ public class FeedbackInfoTest extends SysuiTestCase { @Test public void testPrompt_alerted() { - when(mAssistantFeedbackController.getFeedbackStatus(any(NotificationEntry.class))) + when(mAssistantFeedbackController.getFeedbackStatus( + any(NotificationListenerService.Ranking.class))) .thenReturn(STATUS_ALERTED); - mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), mMockNotificationRow, + mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry().getRanking(), + mMockNotificationRow, mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager); TextView prompt = mFeedbackInfo.findViewById(R.id.prompt); assertEquals("This notification was automatically promoted to Default by the system. " @@ -189,9 +196,11 @@ public class FeedbackInfoTest extends SysuiTestCase { @Test public void testPrompt_demoted() { - when(mAssistantFeedbackController.getFeedbackStatus(any(NotificationEntry.class))) + when(mAssistantFeedbackController.getFeedbackStatus( + any(NotificationListenerService.Ranking.class))) .thenReturn(STATUS_DEMOTED); - mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), mMockNotificationRow, + mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry().getRanking(), + mMockNotificationRow, mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager); TextView prompt = mFeedbackInfo.findViewById(R.id.prompt); assertEquals("This notification was automatically ranked lower in your shade. " @@ -201,7 +210,8 @@ public class FeedbackInfoTest extends SysuiTestCase { @Test public void testPositiveFeedback() { - mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), mMockNotificationRow, + mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry().getRanking(), + mMockNotificationRow, mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager); final View yes = mFeedbackInfo.findViewById(R.id.yes); @@ -218,7 +228,8 @@ public class FeedbackInfoTest extends SysuiTestCase { any(NotificationMenuRowPlugin.MenuItem.class))) .thenReturn(true); - mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), mMockNotificationRow, + mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry().getRanking(), + mMockNotificationRow, mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager); final View no = mFeedbackInfo.findViewById(R.id.no); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt index 387c62d76083..324487b121bc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt @@ -67,15 +67,19 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.AssistantFeedbackController import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider +import com.android.systemui.statusbar.notification.collection.provider.mockHighPriorityProvider import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.headsup.HeadsUpManager +import com.android.systemui.statusbar.notification.headsup.mockHeadsUpManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor import com.android.systemui.statusbar.notification.row.icon.AppIconProvider import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider import com.android.systemui.statusbar.notification.row.icon.appIconProvider import com.android.systemui.statusbar.notification.row.icon.notificationIconStyleProvider +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.testKosmos @@ -263,7 +267,11 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong()) executor.runAllReady() verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>()) - verify(headsUpManager).setGutsShown(realRow.entry, true) + if (NotificationBundleUi.isEnabled) { + verify(kosmos.mockHeadsUpManager).setGutsShown(any<NotificationEntry>(), eq(true)) + } else { + verify(headsUpManager).setGutsShown(realRow.entry, true) + } assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong()) gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false) @@ -271,7 +279,11 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean()) verify(row, times(1)).setGutsView(any<MenuItem>()) executor.runAllReady() - verify(headsUpManager).setGutsShown(realRow.entry, false) + if (NotificationBundleUi.isEnabled) { + verify(kosmos.mockHeadsUpManager).setGutsShown(any<NotificationEntry>(), eq(false)) + } else { + verify(headsUpManager).setGutsShown(realRow.entry, false) + } } @Test @@ -301,7 +313,11 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( verify(guts).closeControls(anyInt(), anyInt(), eq(false), eq(false)) verify(row, times(1)).setGutsView(any<MenuItem>()) executor.runAllReady() - verify(headsUpManager).setGutsShown(realRow.entry, false) + if (NotificationBundleUi.isEnabled) { + verify(kosmos.mockHeadsUpManager).setGutsShown(any<NotificationEntry>(), eq(false)) + } else { + verify(headsUpManager).setGutsShown(realRow.entry, false) + } } @Test @@ -505,17 +521,22 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( @Throws(Exception::class) fun testInitializeNotificationInfoView_highPriority() { val notificationInfoView: NotificationInfo = mock() - val row = spy(helper.createRow()) + val row = createTestNotificationRow() val entry = row.entry NotificationEntryHelper.modifyRanking(entry) .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE) .setImportance(NotificationManager.IMPORTANCE_HIGH) .build() - whenever(row.canViewBeDismissed()).thenReturn(true) whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true) + whenever(kosmos.mockHighPriorityProvider.isHighPriority(entry)).thenReturn(true) val statusBarNotification = entry.sbn - gutsManager.initializeNotificationInfo(row, notificationInfoView) + gutsManager.initializeNotificationInfo( + row, + statusBarNotification, + entry.ranking, + notificationInfoView, + ) verify(notificationInfoView) .bindNotification( @@ -527,15 +548,17 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( eq(channelEditorDialogController), any<PackageDemotionInteractor>(), eq(statusBarNotification.packageName), - any<NotificationChannel>(), - eq(entry), + eq(entry.ranking), + eq(statusBarNotification), + if (NotificationBundleUi.isEnabled) eq(null) else eq(entry), + if (NotificationBundleUi.isEnabled) eq(row.entryAdapter) else eq(null), any<NotificationInfo.OnSettingsClickListener>(), any<NotificationInfo.OnAppSettingsClickListener>(), any<NotificationInfo.OnFeedbackClickListener>(), any<UiEventLogger>(), /* isDeviceProvisioned = */ eq(false), /* isNonblockable = */ eq(false), - /* isDismissable = */ eq(true), + /* isDismissable = */ eq(false), /* wasShownHighPriority = */ eq(true), eq(assistantFeedbackController), eq(metricsLogger), @@ -547,17 +570,21 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( @Throws(Exception::class) fun testInitializeNotificationInfoView_PassesAlongProvisionedState() { val notificationInfoView: NotificationInfo = mock() - val row = spy(helper.createRow()) + val row = createTestNotificationRow() NotificationEntryHelper.modifyRanking(row.entry) .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE) .build() - whenever(row.canViewBeDismissed()).thenReturn(true) val statusBarNotification = row.entry.sbn val entry = row.entry whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) - gutsManager.initializeNotificationInfo(row, notificationInfoView) + gutsManager.initializeNotificationInfo( + row, + statusBarNotification, + entry.ranking, + notificationInfoView, + ) verify(notificationInfoView) .bindNotification( @@ -569,15 +596,17 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( eq(channelEditorDialogController), any<PackageDemotionInteractor>(), eq(statusBarNotification.packageName), - any<NotificationChannel>(), - eq(entry), + eq(entry.ranking), + eq(statusBarNotification), + if (NotificationBundleUi.isEnabled) eq(null) else eq(entry), + if (NotificationBundleUi.isEnabled) eq(row.entryAdapter) else eq(null), any<NotificationInfo.OnSettingsClickListener>(), any<NotificationInfo.OnAppSettingsClickListener>(), any<NotificationInfo.OnFeedbackClickListener>(), any<UiEventLogger>(), /* isDeviceProvisioned = */ eq(true), /* isNonblockable = */ eq(false), - /* isDismissable = */ eq(true), + /* isDismissable = */ eq(false), /* wasShownHighPriority = */ eq(false), eq(assistantFeedbackController), eq(metricsLogger), @@ -589,15 +618,19 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( @Throws(Exception::class) fun testInitializeNotificationInfoView_withInitialAction() { val notificationInfoView: NotificationInfo = mock() - val row = spy(helper.createRow()) + val row = createTestNotificationRow() NotificationEntryHelper.modifyRanking(row.entry) .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE) .build() - whenever(row.canViewBeDismissed()).thenReturn(true) val statusBarNotification = row.entry.sbn val entry = row.entry - gutsManager.initializeNotificationInfo(row, notificationInfoView) + gutsManager.initializeNotificationInfo( + row, + statusBarNotification, + entry.ranking, + notificationInfoView, + ) verify(notificationInfoView) .bindNotification( @@ -609,15 +642,17 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( eq(channelEditorDialogController), any<PackageDemotionInteractor>(), eq(statusBarNotification.packageName), - any<NotificationChannel>(), - eq(entry), + eq(entry.ranking), + eq(statusBarNotification), + if (NotificationBundleUi.isEnabled) eq(null) else eq(entry), + if (NotificationBundleUi.isEnabled) eq(row.entryAdapter) else eq(null), any<NotificationInfo.OnSettingsClickListener>(), any<NotificationInfo.OnAppSettingsClickListener>(), any<NotificationInfo.OnFeedbackClickListener>(), any<UiEventLogger>(), /* isDeviceProvisioned = */ eq(false), /* isNonblockable = */ eq(false), - /* isDismissable = */ eq(true), + /* isDismissable = */ eq(false), /* wasShownHighPriority = */ eq(false), eq(assistantFeedbackController), eq(metricsLogger), @@ -639,6 +674,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( NotificationEntryHelper.modifyRanking(row.entry) .setChannel(testNotificationChannel) .build() + row.entryAdapter = kosmos.entryAdapterFactory.create(row.entry) return row } catch (e: Exception) { fail() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt index 16663def16a4..b2521b0ce766 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt @@ -62,13 +62,16 @@ import com.android.systemui.kosmos.testCase import com.android.systemui.res.R import com.android.systemui.statusbar.RankingBuilder import com.android.systemui.statusbar.notification.AssistantFeedbackController +import com.android.systemui.statusbar.notification.collection.EntryAdapter import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.coordinator.mockVisualStabilityCoordinator import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor import com.android.systemui.statusbar.notification.row.icon.AppIconProvider import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider import com.android.systemui.statusbar.notification.row.icon.mockAppIconProvider import com.android.systemui.statusbar.notification.row.icon.mockNotificationIconStyleProvider +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.testKosmos import com.android.telecom.telecomManager import com.google.common.truth.Truth.assertThat @@ -100,6 +103,7 @@ class NotificationInfoTest : SysuiTestCase() { private lateinit var classifiedNotificationChannel: NotificationChannel private lateinit var sbn: StatusBarNotification private lateinit var entry: NotificationEntry + private lateinit var entryAdapter: EntryAdapter private val mockPackageManager = kosmos.mockPackageManager private val mockAppIconProvider = kosmos.mockAppIconProvider @@ -189,7 +193,12 @@ class NotificationInfoTest : SysuiTestCase() { null, 0, ) - entry = NotificationEntryBuilder().setSbn(sbn).build() + entry = + NotificationEntryBuilder() + .setSbn(sbn) + .updateRanking { it.setChannel(notificationChannel) } + .build() + entryAdapter = kosmos.entryAdapterFactory.create(entry) whenever(assistantFeedbackController.isFeedbackEnabled).thenReturn(false) whenever(assistantFeedbackController.getInlineDescriptionResource(any())) .thenReturn(R.string.notification_channel_summary_automatic) @@ -263,7 +272,7 @@ class NotificationInfoTest : SysuiTestCase() { .thenReturn(applicationInfo) whenever(mockPackageManager.getApplicationLabel(any())).thenReturn("Other") - val entry = NotificationEntryBuilder().setSbn(sbn).build() + val entry = NotificationEntryBuilder(entry).setSbn(sbn).build() bindNotification(entry = entry) val nameView = underTest.findViewById<TextView>(R.id.delegate_name) assertThat(nameView.visibility).isEqualTo(VISIBLE) @@ -304,6 +313,10 @@ class NotificationInfoTest : SysuiTestCase() { @Test fun testBindNotification_DefaultChannelDoesNotUseChannelName() { + entry = + NotificationEntryBuilder(entry) + .updateRanking { it.setChannel(defaultNotificationChannel) } + .build() bindNotification(notificationChannel = defaultNotificationChannel) val textView = underTest.findViewById<TextView>(R.id.channel_name) assertThat(textView.visibility).isEqualTo(GONE) @@ -320,6 +333,10 @@ class NotificationInfoTest : SysuiTestCase() { ) ) .thenReturn(10) + entry = + NotificationEntryBuilder(entry) + .updateRanking { it.setChannel(notificationChannel) } + .build() bindNotification(notificationChannel = defaultNotificationChannel) val textView = underTest.findViewById<TextView>(R.id.channel_name) assertThat(textView.visibility).isEqualTo(VISIBLE) @@ -747,7 +764,13 @@ class NotificationInfoTest : SysuiTestCase() { underTest.findViewById<View>(R.id.done).performClick() underTest.handleCloseControls(true, false) - verify(onUserInteractionCallback).onImportanceChanged(entry) + if (NotificationBundleUi.isEnabled) { + verify(kosmos.mockVisualStabilityCoordinator) + .temporarilyAllowSectionChanges(eq(entry), any()) + } else { + verify(onUserInteractionCallback).onImportanceChanged(entry) + } + assertThat(underTest.shouldBeSavedOnClose()).isFalse() } @@ -866,7 +889,12 @@ class NotificationInfoTest : SysuiTestCase() { @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) @Throws(RemoteException::class) fun testBindNotification_SetsFeedbackLink_isReservedChannel() { - entry.setRanking(RankingBuilder(entry.ranking).setSummarization("something").build()) + entry.setRanking( + RankingBuilder(entry.ranking) + .setSummarization("something") + .setChannel(classifiedNotificationChannel) + .build() + ) val latch = CountDownLatch(1) bindNotification( notificationChannel = classifiedNotificationChannel, @@ -927,6 +955,7 @@ class NotificationInfoTest : SysuiTestCase() { pkg: String = TEST_PACKAGE_NAME, notificationChannel: NotificationChannel = this.notificationChannel, entry: NotificationEntry = this.entry, + entryAdapter: EntryAdapter = this.entryAdapter, onSettingsClick: NotificationInfo.OnSettingsClickListener? = null, onAppSettingsClick: NotificationInfo.OnAppSettingsClickListener? = null, onFeedbackClickListener: NotificationInfo.OnFeedbackClickListener? = null, @@ -948,8 +977,10 @@ class NotificationInfoTest : SysuiTestCase() { channelEditorDialogController, packageDemotionInteractor, pkg, - notificationChannel, + entry.ranking, + entry.sbn, entry, + entryAdapter, onSettingsClick, onAppSettingsClick, onFeedbackClickListener, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index e6b2c2541447..bda2ba79d6b5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -81,6 +81,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -746,7 +747,9 @@ public class NotificationTestHelper { mock(PeopleNotificationIdentifier.class), mock(NotificationIconStyleProvider.class), mock(VisualStabilityCoordinator.class), - mock(NotificationActionClickManager.class) + mock(NotificationActionClickManager.class), + mock(HighPriorityProvider.class), + mock(HeadsUpManager.class) ).create(entry); row.initialize( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java index 57b0f3f8d8c5..0eb60163766c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java @@ -40,6 +40,7 @@ import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.UserHandle; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.testing.TestableLooper; import android.text.SpannableString; @@ -81,7 +82,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { private TestableLooper mTestableLooper; private PartialConversationInfo mInfo; private NotificationChannel mNotificationChannel; - private NotificationChannel mDefaultNotificationChannel; private StatusBarNotification mSbn; private NotificationEntry mEntry; @@ -141,15 +141,14 @@ public class PartialConversationInfoTest extends SysuiTestCase { // Some test channels. mNotificationChannel = new NotificationChannel( TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW); - mDefaultNotificationChannel = new NotificationChannel( - NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME, - IMPORTANCE_LOW); Notification n = new Notification.Builder(mContext, mNotificationChannel.getId()) .setContentTitle(new SpannableString("title")) .build(); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, n, UserHandle.CURRENT, null, 0); - mEntry = new NotificationEntryBuilder().setSbn(mSbn).build(); + mEntry = new NotificationEntryBuilder().setSbn(mSbn).updateRanking(rankingBuilder -> { + rankingBuilder.setChannel(mNotificationChannel); + }).build(); } @Test @@ -160,8 +159,8 @@ public class PartialConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mChannelEditorDialogController, TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, + mEntry.getRanking(), + mSbn, null, true, false); @@ -178,8 +177,8 @@ public class PartialConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mChannelEditorDialogController, TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, + mEntry.getRanking(), + mSbn, null, true, false); @@ -194,8 +193,8 @@ public class PartialConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mChannelEditorDialogController, TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, + mEntry.getRanking(), + mSbn, null, true, false); @@ -219,8 +218,8 @@ public class PartialConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mChannelEditorDialogController, TEST_PACKAGE_NAME, - mNotificationChannel, - entry, + mEntry.getRanking(), + mSbn, null, true, false); @@ -237,8 +236,8 @@ public class PartialConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mChannelEditorDialogController, TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, + mEntry.getRanking(), + mSbn, (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); latch.countDown(); @@ -260,8 +259,8 @@ public class PartialConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mChannelEditorDialogController, TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, + mEntry.getRanking(), + mSbn, (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); latch.countDown(); @@ -282,8 +281,8 @@ public class PartialConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mChannelEditorDialogController, TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, + mEntry.getRanking(), + mSbn, null, true, false); @@ -298,8 +297,8 @@ public class PartialConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mChannelEditorDialogController, TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, + mEntry.getRanking(), + mSbn, (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); }, @@ -316,8 +315,8 @@ public class PartialConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mChannelEditorDialogController, TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, + mEntry.getRanking(), + mSbn, null, true, true); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java index 209dfb2d2ed6..9b86412fc051 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java @@ -31,6 +31,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.UserHandle; import android.platform.test.annotations.EnableFlags; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.telecom.TelecomManager; import android.testing.TestableLooper; @@ -46,9 +47,16 @@ import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.AssistantFeedbackController; +import com.android.systemui.statusbar.notification.NotificationActivityStarter; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor; +import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.icon.AppIconProvider; import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider; @@ -74,6 +82,8 @@ public class PromotedNotificationInfoTest extends SysuiTestCase { private NotificationChannel mNotificationChannel; private StatusBarNotification mSbn; private NotificationEntry mEntry; + private NotificationListenerService.Ranking mRanking; + private EntryAdapter mEntryAdapter; private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake(); @Rule @@ -110,7 +120,20 @@ public class PromotedNotificationInfoTest extends SysuiTestCase { notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, applicationInfo); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, notification, UserHandle.getUserHandleForUid(TEST_UID), null, 0); - mEntry = new NotificationEntryBuilder().setSbn(mSbn).build(); + mEntry = new NotificationEntryBuilder().setSbn(mSbn).updateRanking(rankingBuilder -> { + rankingBuilder.setChannel(mNotificationChannel); + }).build(); + mEntryAdapter = new EntryAdapterFactoryImpl( + mock(NotificationActivityStarter.class), + mock(MetricsLogger.class), + mock(PeopleNotificationIdentifier.class), + mock(NotificationIconStyleProvider.class), + mock(VisualStabilityCoordinator.class), + mock(NotificationActionClickManager.class), + mock(HighPriorityProvider.class), + mock(HeadsUpManager.class) + ).create(mEntry); + mRanking = mEntry.getRanking(); when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(false); mTestableLooper = TestableLooper.get(this); @@ -143,8 +166,10 @@ public class PromotedNotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, mPackageDemotionInteractor, TEST_PACKAGE_NAME, - mNotificationChannel, + mRanking, + mSbn, mEntry, + mEntryAdapter, null, null, null, diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockAnimations.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockAnimations.kt index 77c18eac289c..894327690b5e 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockAnimations.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockAnimations.kt @@ -61,4 +61,7 @@ interface ClockAnimations { /** Runs when an animation when the view is tapped on the lockscreen */ fun onFidgetTap(x: Float, y: Float) + + /** Update reactive axes for this clock */ + fun onFontAxesChanged(style: ClockAxisStyle) } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt index 0fc3470716fe..7c0e4866b00d 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt @@ -42,7 +42,4 @@ interface ClockEvents { /** Call with zen/dnd information */ fun onZenDataChanged(data: ZenData) - - /** Update reactive axes for this clock */ - fun onFontAxesChanged(axes: ClockAxisStyle) } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt index 5b67edda73cc..4f04aaa33b24 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt @@ -90,10 +90,11 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) : } } - fun updateAxes(lsFVar: String, aodFVar: String) { - i({ "updateAxes(LS = $str1, AOD = $str2)" }) { + fun updateAxes(lsFVar: String, aodFVar: String, isAnimated: Boolean) { + i({ "updateAxes(LS = $str1, AOD = $str2, isAnimated=$bool1)" }) { str1 = lsFVar str2 = aodFVar + bool1 = isAnimated } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index 24b955152093..7f46613936ac 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -327,9 +327,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, setupDeviceListView(dialog, hearingDeviceItemList); setupPairNewDeviceButton(dialog); setupPresetSpinner(dialog, activeHearingDevice); - if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl() - && com.android.systemui.Flags - .hearingDevicesInputRoutingUiImprovement()) { + if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) { setupInputRoutingSpinner(dialog, activeHearingDevice); } if (com.android.settingslib.flags.Flags.hearingDevicesAmbientVolumeControl()) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index b16c416fb9df..dbaa90c10313 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -52,6 +52,7 @@ import android.os.PowerManager; import android.os.Trace; import android.os.VibrationAttributes; import android.os.VibrationEffect; +import android.provider.Settings; import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; @@ -920,12 +921,21 @@ public class UdfpsController implements DozeReceiver, Dumpable { true /* isAod */); }; - if (mScreenOn) { + if (isScreenOffUnlockEnabled() || mScreenOn) { mAodInterruptRunnable.run(); mAodInterruptRunnable = null; } } + private boolean isScreenOffUnlockEnabled() { + return mContext.getResources().getBoolean(R.bool.config_screen_off_udfps_enabled) + && Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.SCREEN_OFF_UNLOCK_UDFPS_ENABLED, + 0, + mContext.getUserId()) != 0; + } + /** * Add a callback for fingerUp and fingerDown events */ diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java index d0c6a3e6a3ef..bf1f971c0f8c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java @@ -580,7 +580,23 @@ public class MediaSwitchingController synchronized (mMediaDevicesLock) { if (!mLocalMediaManager.isPreferenceRouteListingExist()) { attachRangeInfo(devices); - Collections.sort(devices, Comparator.naturalOrder()); + if (Flags.enableOutputSwitcherDeviceGrouping()) { + List<MediaDevice> selectedDevices = new ArrayList<>(); + Set<String> selectedDeviceIds = + getSelectedMediaDevice().stream() + .map(MediaDevice::getId) + .collect(Collectors.toSet()); + for (MediaDevice device : devices) { + if (selectedDeviceIds.contains(device.getId())) { + selectedDevices.add(device); + } + } + devices.removeAll(selectedDevices); + Collections.sort(devices, Comparator.naturalOrder()); + devices.addAll(0, selectedDevices); + } else { + Collections.sort(devices, Comparator.naturalOrder()); + } } if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { // For the first time building list, to make sure the top device is the connected diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java index 917869a66ca4..3e6f234a3663 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java @@ -30,8 +30,10 @@ import android.graphics.Rect; import android.graphics.RenderEffect; import android.graphics.Shader; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Looper; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; import android.view.View; @@ -52,6 +54,9 @@ import java.util.concurrent.Executor; * need to be careful to synchronize when necessary. */ public class ScrimView extends View { + private static final String TAG = "ScrimView"; + private static final boolean isDebugLoggable = Build.isDebuggable() || Log.isLoggable(TAG, + Log.DEBUG); private final Object mColorLock = new Object(); @@ -381,12 +386,21 @@ public class ScrimView extends View { */ public void setBlurRadius(float blurRadius) { if (blurRadius > 0) { + debugLog("Apply blur RenderEffect to ScrimView " + mScrimName + " for radius " + + blurRadius); setRenderEffect(RenderEffect.createBlurEffect( blurRadius, blurRadius, Shader.TileMode.CLAMP)); } else { + debugLog("Resetting blur RenderEffect to ScrimView " + mScrimName); setRenderEffect(null); } } + + private void debugLog(String logMsg) { + if (isDebugLoggable) { + Log.d(TAG, logMsg); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index b90624245cc5..fcfce1281722 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -989,8 +989,10 @@ public final class NotificationPanelViewController implements mBlurConfig.getMaxBlurRadiusPx(), Shader.TileMode.CLAMP); } + debugLog("Applying blur RenderEffect to shade."); mView.setRenderEffect(mBlurRenderEffect); } else { + debugLog("Resetting blur RenderEffect on shade."); mView.setRenderEffect(null); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java index 11ec2edb36f6..e9a6ad26f5e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java @@ -108,13 +108,12 @@ public class AssistantFeedbackController { /** * Get the feedback status according to assistant's adjustments * - * @param entry Notification Entry to show feedback for + * @param ranking Ranking of the notification show feedback for */ - public int getFeedbackStatus(NotificationEntry entry) { + public int getFeedbackStatus(Ranking ranking) { if (!isFeedbackEnabled()) { return STATUS_UNCHANGED; } - Ranking ranking = entry.getRanking(); int oldImportance = ranking.getChannel().getImportance(); int newImportance = ranking.getImportance(); if (oldImportance < NotificationManager.IMPORTANCE_DEFAULT @@ -138,11 +137,11 @@ public class AssistantFeedbackController { * Get the feedback indicator image and content description resources according to assistant's * changes on this notification's rank or importance. * - * @param entry Notification Entry to show feedback for + * @param ranking Ranking of the notification to show feedback for */ @Nullable - public FeedbackIcon getFeedbackIcon(NotificationEntry entry) { - int feedbackStatus = getFeedbackStatus(entry); + public FeedbackIcon getFeedbackIcon(Ranking ranking) { + int feedbackStatus = getFeedbackStatus(ranking); return mIcons.get(feedbackStatus); } @@ -150,10 +149,10 @@ public class AssistantFeedbackController { * Get the inline settings description resource according to assistant's changes on this * notification's rank or importance. * - * @param entry Notification Entry to show feedback for + * @param ranking Ranking of the notification to show feedback for */ - public int getInlineDescriptionResource(NotificationEntry entry) { - int feedbackStatus = getFeedbackStatus(entry); + public int getInlineDescriptionResource(Ranking ranking) { + int feedbackStatus = getFeedbackStatus(ranking); switch (feedbackStatus) { case STATUS_ALERTED: return com.android.systemui.res.R.string.notification_channel_summary_automatic_alerted; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt index be17ae56c315..6a3f8f166c34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt @@ -19,14 +19,20 @@ package com.android.systemui.statusbar.notification.collection import android.app.Notification import android.content.Context import android.os.Build +import android.service.notification.NotificationListenerService import android.service.notification.StatusBarNotification import android.util.Log +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.notification.icon.IconPack import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import kotlinx.coroutines.flow.StateFlow -class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { +class BundleEntryAdapter( + private val highPriorityProvider: HighPriorityProvider, + val entry: BundleEntry, +) : EntryAdapter { /** TODO (b/394483200): convert to PipelineEntry.ROOT_ENTRY when pipeline is migrated? */ override fun getParent(): GroupEntry { return GroupEntry.ROOT_ENTRY @@ -94,6 +100,41 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { return null } + override fun getRanking(): NotificationListenerService.Ranking? { + return null + } + + override fun endLifetimeExtension( + callback: NotifLifetimeExtender.OnEndLifetimeExtensionCallback?, + extender: NotifLifetimeExtender, + ) { + Log.wtf(TAG, "endLifetimeExtension() called") + } + + override fun onImportanceChanged() { + Log.wtf(TAG, "onImportanceChanged() called") + } + + override fun markForUserTriggeredMovement() { + Log.wtf(TAG, "markForUserTriggeredMovement() called") + } + + override fun isMarkedForUserTriggeredMovement(): Boolean { + return false + } + + override fun isHighPriority(): Boolean { + return highPriorityProvider.isHighPriority(entry) + } + + override fun setInlineControlsShown(currentlyVisible: Boolean) { + // nothing to do, yet + } + + override fun isBlockable(): Boolean { + return false + } + override fun canDragAndDrop(): Boolean { return false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java index 3757ebfb9986..16d9c787d435 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java @@ -17,11 +17,13 @@ package com.android.systemui.statusbar.notification.collection; import android.content.Context; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.statusbar.notification.icon.IconPack; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -123,6 +125,40 @@ public interface EntryAdapter { @Nullable StatusBarNotification getSbn(); + /** + * Returns the ranking that backs this row, if present + */ + @Nullable + NotificationListenerService.Ranking getRanking(); + + void endLifetimeExtension( + @Nullable NotifLifetimeExtender.OnEndLifetimeExtensionCallback callback, + @NonNull NotifLifetimeExtender extender); + + + void onImportanceChanged(); + + /** + * Use when a change has been made to the underlying object that will both rerank the object + * in the shade and change something about its appearance to delay the appearance change until + * the ranking reordering is likely to have settled. + */ + void markForUserTriggeredMovement(); + + /** + * Determines whether a row is considered 'high priority'. + * + * Notifications that are high priority are visible on the lock screen/status bar and in the top + * section in the shade. + */ + boolean isHighPriority(); + + boolean isMarkedForUserTriggeredMovement(); + + void setInlineControlsShown(boolean currentlyVisible); + + boolean isBlockable(); + boolean canDragAndDrop(); boolean isBubble(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt index a5169865c3c9..2fce19c2becb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.collection import com.android.internal.logging.MetricsLogger import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.row.NotificationActionClickManager import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider @@ -34,6 +36,8 @@ constructor( private val iconStyleProvider: NotificationIconStyleProvider, private val visualStabilityCoordinator: VisualStabilityCoordinator, private val notificationActionClickManager: NotificationActionClickManager, + private val highPriorityProvider: HighPriorityProvider, + private val headsUpManager: HeadsUpManager, ) : EntryAdapterFactory { override fun create(entry: PipelineEntry): EntryAdapter { return if (entry is NotificationEntry) { @@ -44,10 +48,12 @@ constructor( iconStyleProvider, visualStabilityCoordinator, notificationActionClickManager, + highPriorityProvider, + headsUpManager, entry, ) } else { - BundleEntryAdapter((entry as BundleEntry)) + BundleEntryAdapter(highPriorityProvider, (entry as BundleEntry)) } } } 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 b5ab0920a470..c94289c3befa 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 @@ -1030,7 +1030,7 @@ public final class NotificationEntry extends ListEntry { } /** - * Mark this entry for movement triggered by a user action (ex: changing the priorirty of a + * Mark this entry for movement triggered by a user action (ex: changing the priority of a * conversation). This can then be used for custom animations. */ public void markForUserTriggeredMovement(boolean marked) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt index a23c5a3ea9f2..339a999e1535 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt @@ -17,10 +17,15 @@ package com.android.systemui.statusbar.notification.collection import android.content.Context +import android.os.SystemClock +import android.service.notification.NotificationListenerService import android.service.notification.StatusBarNotification import com.android.internal.logging.MetricsLogger import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.notification.icon.IconPack import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow @@ -35,6 +40,8 @@ class NotificationEntryAdapter( private val iconStyleProvider: NotificationIconStyleProvider, private val visualStabilityCoordinator: VisualStabilityCoordinator, private val notificationActionClickManager: NotificationActionClickManager, + private val highPriorityProvider: HighPriorityProvider, + private val headsUpManager: HeadsUpManager, private val entry: NotificationEntry, ) : EntryAdapter { @@ -110,6 +117,41 @@ class NotificationEntryAdapter( return entry.sbn } + override fun getRanking(): NotificationListenerService.Ranking? { + return entry.ranking + } + + override fun endLifetimeExtension( + callback: NotifLifetimeExtender.OnEndLifetimeExtensionCallback?, + extender: NotifLifetimeExtender, + ) { + callback?.onEndLifetimeExtension(extender, entry) + } + + override fun onImportanceChanged() { + visualStabilityCoordinator.temporarilyAllowSectionChanges(entry, SystemClock.uptimeMillis()) + } + + override fun markForUserTriggeredMovement() { + entry.markForUserTriggeredMovement(true) + } + + override fun isMarkedForUserTriggeredMovement(): Boolean { + return entry.isMarkedForUserTriggeredMovement + } + + override fun isHighPriority(): Boolean { + return highPriorityProvider.isHighPriority(entry) + } + + override fun setInlineControlsShown(currentlyVisible: Boolean) { + headsUpManager.setGutsShown(entry, currentlyVisible) + } + + override fun isBlockable(): Boolean { + return entry.isBlockable + } + override fun canDragAndDrop(): Boolean { val canBubble: Boolean = entry.canBubble() val notif = entry.sbn.notification diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt index 9045aac6ea6f..754f89191684 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt @@ -18,9 +18,10 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.util.ArraySet import com.android.systemui.Dumpable import com.android.systemui.dump.DumpManager -import com.android.systemui.statusbar.notification.collection.PipelineEntry +import com.android.systemui.statusbar.notification.collection.EntryAdapter import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback @@ -28,6 +29,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifGutsVi import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager import com.android.systemui.statusbar.notification.row.NotificationGuts import com.android.systemui.statusbar.notification.row.NotificationGutsManager +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.util.asIndenting import com.android.systemui.util.printCollection import com.android.systemui.util.println @@ -38,23 +40,25 @@ import javax.inject.Inject private const val TAG = "GutsCoordinator" /** - * Coordinates the guts displayed by the [NotificationGutsManager] with the pipeline. - * Specifically, this just adds the lifetime extension necessary to keep guts from disappearing. + * Coordinates the guts displayed by the [NotificationGutsManager] with the pipeline. Specifically, + * this just adds the lifetime extension necessary to keep guts from disappearing. */ @CoordinatorScope -class GutsCoordinator @Inject constructor( +class GutsCoordinator +@Inject +constructor( private val notifGutsViewManager: NotifGutsViewManager, private val logger: GutsCoordinatorLogger, - dumpManager: DumpManager + dumpManager: DumpManager, ) : Coordinator, Dumpable { - /** Keys of any Notifications for which we've been told the guts are open */ + /** Keys of any Notifications for which we've been told the guts are open */ private val notifsWithOpenGuts = ArraySet<String>() - /** Keys of any Notifications we've extended the lifetime for, based on guts */ + /** Keys of any Notifications we've extended the lifetime for, based on guts */ private val notifsExtendingLifetime = ArraySet<String>() - /** Callback for ending lifetime extension */ + /** Callback for ending lifetime extension */ private var onEndLifetimeExtensionCallback: OnEndLifetimeExtensionCallback? = null init { @@ -66,55 +70,77 @@ class GutsCoordinator @Inject constructor( pipeline.addNotificationLifetimeExtender(mLifetimeExtender) } - override fun dump(pw: PrintWriter, args: Array<String>) = pw.asIndenting().run { - withIncreasedIndent { - printCollection("notifsWithOpenGuts", notifsWithOpenGuts) - printCollection("notifsExtendingLifetime", notifsExtendingLifetime) - println("onEndLifetimeExtensionCallback", onEndLifetimeExtensionCallback) + override fun dump(pw: PrintWriter, args: Array<String>) = + pw.asIndenting().run { + withIncreasedIndent { + printCollection("notifsWithOpenGuts", notifsWithOpenGuts) + printCollection("notifsExtendingLifetime", notifsExtendingLifetime) + println("onEndLifetimeExtensionCallback", onEndLifetimeExtensionCallback) + } } - } - private val mLifetimeExtender: NotifLifetimeExtender = object : NotifLifetimeExtender { - override fun getName(): String { - return TAG - } + private val mLifetimeExtender: NotifLifetimeExtender = + object : NotifLifetimeExtender { + override fun getName(): String { + return TAG + } - override fun setCallback(callback: OnEndLifetimeExtensionCallback) { - onEndLifetimeExtensionCallback = callback - } + override fun setCallback(callback: OnEndLifetimeExtensionCallback) { + onEndLifetimeExtensionCallback = callback + } - override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { - val isShowingGuts = isCurrentlyShowingGuts(entry) - if (isShowingGuts) { - notifsExtendingLifetime.add(entry.key) + override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { + val isShowingGuts = isCurrentlyShowingGuts(entry) + if (isShowingGuts) { + notifsExtendingLifetime.add(entry.key) + } + return isShowingGuts } - return isShowingGuts - } - override fun cancelLifetimeExtension(entry: NotificationEntry) { - notifsExtendingLifetime.remove(entry.key) + override fun cancelLifetimeExtension(entry: NotificationEntry) { + notifsExtendingLifetime.remove(entry.key) + } } - } - private val mGutsListener: NotifGutsViewListener = object : NotifGutsViewListener { - override fun onGutsOpen(entry: NotificationEntry, guts: NotificationGuts) { - logger.logGutsOpened(entry.key, guts) - if (guts.isLeavebehind) { - // leave-behind guts should not extend the lifetime of the notification + private val mGutsListener: NotifGutsViewListener = + object : NotifGutsViewListener { + override fun onGutsOpen(entry: NotificationEntry, guts: NotificationGuts) { + NotificationBundleUi.assertInLegacyMode() + logger.logGutsOpened(entry.key, guts) + if (guts.isLeavebehind) { + // leave-behind guts should not extend the lifetime of the notification + closeGutsAndEndLifetimeExtension(entry) + } else { + notifsWithOpenGuts.add(entry.key) + } + } + + override fun onGutsClose(entry: NotificationEntry) { + NotificationBundleUi.assertInLegacyMode() + logger.logGutsClosed(entry.key) closeGutsAndEndLifetimeExtension(entry) - } else { - notifsWithOpenGuts.add(entry.key) } - } - override fun onGutsClose(entry: NotificationEntry) { - logger.logGutsClosed(entry.key) - closeGutsAndEndLifetimeExtension(entry) + override fun onGutsOpen(entryAdapter: EntryAdapter, guts: NotificationGuts) { + NotificationBundleUi.isUnexpectedlyInLegacyMode() + logger.logGutsOpened(entryAdapter.key, guts) + if (guts.isLeavebehind) { + // leave-behind guts should not extend the lifetime of the notification + closeGutsAndEndLifetimeExtension(entryAdapter) + } else { + notifsWithOpenGuts.add(entryAdapter.key) + } + } + + override fun onGutsClose(entryAdapter: EntryAdapter) { + NotificationBundleUi.isUnexpectedlyInLegacyMode() + logger.logGutsClosed(entryAdapter.key) + closeGutsAndEndLifetimeExtension(entryAdapter) + } } - } private fun isCurrentlyShowingGuts(entry: PipelineEntry) = - notifsWithOpenGuts.contains(entry.key) + notifsWithOpenGuts.contains(entry.key) private fun closeGutsAndEndLifetimeExtension(entry: NotificationEntry) { notifsWithOpenGuts.remove(entry.key) @@ -122,4 +148,11 @@ class GutsCoordinator @Inject constructor( onEndLifetimeExtensionCallback?.onEndLifetimeExtension(mLifetimeExtender, entry) } } + + private fun closeGutsAndEndLifetimeExtension(entryAdapter: EntryAdapter) { + notifsWithOpenGuts.remove(entryAdapter.key) + if (notifsExtendingLifetime.remove(entryAdapter.key)) { + entryAdapter.endLifetimeExtension(onEndLifetimeExtensionCallback, mLifetimeExtender) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt index a987c544bb50..341cc09aafc1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt @@ -20,9 +20,9 @@ import android.content.Context import com.android.systemui.res.R import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.notification.AssistantFeedbackController -import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider import com.android.systemui.statusbar.notification.collection.render.NotifRowController @@ -79,6 +79,6 @@ internal constructor( (mAutoExpandFirstNotification && entry == entryToExpand) ) // Show/hide the feedback icon - controller.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry)) + controller.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry.ranking)) } } 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 03b4076ba6fb..ca3d2e2a2a35 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 @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback; import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import javax.inject.Inject; @@ -83,6 +84,7 @@ public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback @Override public void onImportanceChanged(NotificationEntry entry) { + NotificationBundleUi.assertInLegacyMode(); mVisualStabilityCoordinator.temporarilyAllowSectionChanges( entry, UseElapsedRealtimeForCreationTime.getCurrentTime()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt index 129f6b1750e6..9e49083e4d04 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt @@ -15,16 +15,21 @@ */ package com.android.systemui.statusbar.notification.collection.render +import com.android.systemui.statusbar.notification.collection.EntryAdapter import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.NotificationGuts -/** - * Interface for listening to guts open and close events. - */ +/** Interface for listening to guts open and close events. */ interface NotifGutsViewListener { /** A notification's guts are being opened */ fun onGutsOpen(entry: NotificationEntry, guts: NotificationGuts) /** A notification's guts are being closed */ fun onGutsClose(entry: NotificationEntry) + + /** A notification's guts are being opened */ + fun onGutsOpen(entryAdapter: EntryAdapter, guts: NotificationGuts) + + /** A notification's guts are being closed */ + fun onGutsClose(entryAdapter: EntryAdapter) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt index 777392df67cc..fdbd75bd33ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt @@ -226,7 +226,7 @@ private val PromotedNotificationContentModel.layoutResource: Int? private class AODPromotedNotificationViewUpdater(root: View) { private val alertedIcon: ImageView? = root.findViewById(R.id.alerted_icon) private val alternateExpandTarget: View? = root.findViewById(R.id.alternate_expand_target) - private val appNameDivider: View? = root.findViewById(R.id.app_name_divider) + private val appNameDivider: TextView? = root.findViewById(R.id.app_name_divider) private val appNameText: TextView? = root.findViewById(R.id.app_name_text) private val bigPicture: BigPictureNotificationImageView? = root.findViewById(R.id.big_picture) private val bigText: ImageFloatingTextView? = root.findViewById(R.id.big_text) @@ -241,9 +241,9 @@ private class AODPromotedNotificationViewUpdater(root: View) { ) private val expandButton: NotificationExpandButton? = root.findViewById(R.id.expand_button) private val headerText: TextView? = root.findViewById(R.id.header_text) - private val headerTextDivider: View? = root.findViewById(R.id.header_text_divider) + private val headerTextDivider: TextView? = root.findViewById(R.id.header_text_divider) private val headerTextSecondary: TextView? = root.findViewById(R.id.header_text_secondary) - private val headerTextSecondaryDivider: View? = + private val headerTextSecondaryDivider: TextView? = root.findViewById(R.id.header_text_secondary_divider) private val icon: NotificationRowIconView? = root.findViewById(R.id.icon) private val leftIcon: ImageView? = root.findViewById(R.id.left_icon) @@ -256,9 +256,9 @@ private class AODPromotedNotificationViewUpdater(root: View) { private val rightIcon: ImageView? = root.findViewById(R.id.right_icon) private val text: ImageFloatingTextView? = root.findViewById(R.id.text) private val time: DateTimeView? = root.findViewById(R.id.time) - private val timeDivider: View? = root.findViewById(R.id.time_divider) + private val timeDivider: TextView? = root.findViewById(R.id.time_divider) private val title: TextView? = root.findViewById(R.id.title) - private val verificationDivider: View? = root.findViewById(R.id.verification_divider) + private val verificationDivider: TextView? = root.findViewById(R.id.verification_divider) private val verificationIcon: ImageView? = root.findViewById(R.id.verification_icon) private val verificationText: TextView? = root.findViewById(R.id.verification_text) @@ -283,6 +283,12 @@ private class AODPromotedNotificationViewUpdater(root: View) { ?.mutate() ?.setColorFilter(SecondaryText.colorInt, PorterDuff.Mode.SRC_IN) + setTextViewColor(appNameDivider, SecondaryText) + setTextViewColor(headerTextDivider, SecondaryText) + setTextViewColor(headerTextSecondaryDivider, SecondaryText) + setTextViewColor(timeDivider, SecondaryText) + setTextViewColor(verificationDivider, SecondaryText) + if (Flags.notificationsRedesignTemplates()) { (mainColumn?.layoutParams as? MarginLayoutParams)?.let { mainColumnMargins -> mainColumnMargins.topMargin = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java index 40897dae4c44..d80d5e101294 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java @@ -45,7 +45,6 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.AssistantFeedbackController; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.util.Compile; public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsContent { @@ -58,7 +57,6 @@ public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsC private PackageManager mPm; private String mAppName; private String mPkg; - private NotificationEntry mEntry; private IStatusBarService mStatusBarService; private AssistantFeedbackController mFeedbackController; @@ -73,16 +71,15 @@ public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsC public void bindGuts( final PackageManager pm, final StatusBarNotification sbn, - final NotificationEntry entry, + final NotificationListenerService.Ranking ranking, final ExpandableNotificationRow row, final AssistantFeedbackController controller, final IStatusBarService statusBarService, final NotificationGutsManager notificationGutsManager) { mPkg = sbn.getPackageName(); mPm = pm; - mEntry = entry; mExpandableNotificationRow = row; - mRanking = entry.getRanking(); + mRanking = ranking; mFeedbackController = controller; mAppName = mPkg; mStatusBarService = statusBarService; @@ -143,7 +140,7 @@ public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsC @SuppressLint("DefaultLocale") private String getPrompt() { StringBuilder sb = new StringBuilder(); - int status = mFeedbackController.getFeedbackStatus(mEntry); + int status = mFeedbackController.getFeedbackStatus(mRanking); if (DEBUG) { sb.append(String.format( "[DEBUG]: oldImportance=%d, newImportance=%d, ranking=%f\n\n", diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 57ceafcd15c6..f17ae571d3ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -1392,7 +1392,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mPromotedNotificationContentExtractor.extractContent(mEntry, recoveredBuilder, mBindParams.redactionType, imageModelProvider); mLogger.logAsyncTaskProgress(logKey, "extracted promoted notification content: " - + promotedContent); + + (promotedContent != null ? promotedContent.toRedactedString() : null)); result.mPromotedContent = promotedContent; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index 3c7d9ef8e71d..2cf3b14bb8c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -49,6 +49,7 @@ import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.transition.ChangeBounds; @@ -72,7 +73,9 @@ import com.android.systemui.res.R; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.notification.NmSummarizationUiFlag; import com.android.systemui.statusbar.notification.NotificationChannelHelper; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.wmshell.BubblesManager; @@ -104,6 +107,8 @@ public class NotificationConversationInfo extends LinearLayout implements private ShortcutInfo mShortcutInfo; private NotificationEntry mEntry; private StatusBarNotification mSbn; + private EntryAdapter mEntryAdapter; + private NotificationListenerService.Ranking mRanking; @Nullable private Notification.BubbleMetadata mBubbleMetadata; private Context mUserContext; private boolean mIsDeviceProvisioned; @@ -200,9 +205,10 @@ public class NotificationConversationInfo extends LinearLayout implements INotificationManager iNotificationManager, OnUserInteractionCallback onUserInteractionCallback, String pkg, - NotificationChannel notificationChannel, NotificationEntry entry, - Notification.BubbleMetadata bubbleMetadata, + EntryAdapter entryAdapter, + NotificationListenerService.Ranking ranking, + StatusBarNotification sbn, OnSettingsClickListener onSettingsClick, NotificationInfo.OnFeedbackClickListener onFeedbackClickListener, ConversationIconFactory conversationIconFactory, @@ -218,25 +224,27 @@ public class NotificationConversationInfo extends LinearLayout implements mOnUserInteractionCallback = onUserInteractionCallback; mPackageName = pkg; mEntry = entry; - mSbn = entry.getSbn(); + mSbn = sbn; + mRanking = ranking; + mEntryAdapter = entryAdapter; mPm = pm; mUm = um; mAppName = mPackageName; mOnSettingsClickListener = onSettingsClick; - mNotificationChannel = notificationChannel; + mNotificationChannel = ranking.getChannel(); mAppUid = mSbn.getUid(); mDelegatePkg = mSbn.getOpPkg(); mIsDeviceProvisioned = isDeviceProvisioned; mOnConversationSettingsClickListener = onConversationSettingsClickListener; mIconFactory = conversationIconFactory; mUserContext = userContext; - mBubbleMetadata = bubbleMetadata; + mBubbleMetadata = sbn.getNotification().getBubbleMetadata(); mBubblesManagerOptional = bubblesManagerOptional; mShadeController = shadeController; mMainHandler = mainHandler; mBgHandler = bgHandler; mShortcutManager = shortcutManager; - mShortcutInfo = entry.getRanking().getConversationShortcutInfo(); + mShortcutInfo = ranking.getConversationShortcutInfo(); mFeedbackClickListener = onFeedbackClickListener; if (mShortcutInfo == null) { throw new IllegalArgumentException("Does not have required information"); @@ -308,11 +316,11 @@ public class NotificationConversationInfo extends LinearLayout implements private void bindFeedback() { View feedbackButton = findViewById(R.id.feedback); if (!NmSummarizationUiFlag.isEnabled() - || TextUtils.isEmpty(mEntry.getRanking().getSummarization())) { + || TextUtils.isEmpty(mRanking.getSummarization())) { feedbackButton.setVisibility(GONE); } else { Intent intent = NotificationInfo.getAssistantFeedbackIntent( - mINotificationManager, mPm, mEntry); + mINotificationManager, mPm, mSbn.getKey(), mRanking); if (intent == null) { feedbackButton.setVisibility(GONE); } else { @@ -551,10 +559,17 @@ public class NotificationConversationInfo extends LinearLayout implements mBgHandler.post( new UpdateChannelRunnable(mINotificationManager, mPackageName, mAppUid, mSelectedAction, mNotificationChannel)); - mEntry.markForUserTriggeredMovement(true); - mMainHandler.postDelayed( - () -> mOnUserInteractionCallback.onImportanceChanged(mEntry), - StackStateAnimator.ANIMATION_DURATION_STANDARD); + if (NotificationBundleUi.isEnabled()) { + mEntryAdapter.markForUserTriggeredMovement(); + mMainHandler.postDelayed( + () -> mEntryAdapter.onImportanceChanged(), + StackStateAnimator.ANIMATION_DURATION_STANDARD); + } else { + mEntry.markForUserTriggeredMovement(true); + mMainHandler.postDelayed( + () -> mOnUserInteractionCallback.onImportanceChanged(mEntry), + StackStateAnimator.ANIMATION_DURATION_STANDARD); + } } private boolean willBypassDnd() { @@ -658,8 +673,13 @@ public class NotificationConversationInfo extends LinearLayout implements BUBBLE_PREFERENCE_SELECTED); } if (mBubblesManagerOptional.isPresent()) { - post(() -> mBubblesManagerOptional.get() - .onUserSetImportantConversation(mEntry)); + if (NotificationBundleUi.isEnabled()) { + post(() -> mBubblesManagerOptional.get() + .onUserSetImportantConversation(mEntryAdapter)); + } else { + post(() -> mBubblesManagerOptional.get() + .onUserSetImportantConversation(mEntry)); + } } } mChannelToUpdate.setImportance(Math.max( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index f4e01bf718d9..6c7c7a79348f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -33,6 +33,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.IconDrawableFactory; @@ -66,7 +67,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.notification.AssistantFeedbackController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener; import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; @@ -289,10 +289,21 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta @VisibleForTesting protected boolean bindGuts(final ExpandableNotificationRow row, NotificationMenuRowPlugin.MenuItem item) { - NotificationEntry entry = row.getEntry(); + + StatusBarNotification sbn = NotificationBundleUi.isEnabled() + ? row.getEntryAdapter().getSbn() + : row.getEntryLegacy().getSbn(); + NotificationListenerService.Ranking ranking = NotificationBundleUi.isEnabled() + ? row.getEntryAdapter().getRanking() + : row.getEntryLegacy().getRanking(); + + if (sbn == null || ranking == null) { + // only valid for notification rows + return false; + } row.setGutsView(item); - row.setTag(entry.getSbn().getPackageName()); + row.setTag(sbn.getPackageName()); row.getGuts().setClosedListener((NotificationGuts g) -> { row.onGutsClosed(); if (!g.willBeRemoved() && !row.isRemoved()) { @@ -304,28 +315,37 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta mGutsMenuItem = null; } if (mGutsListener != null) { - mGutsListener.onGutsClose(entry); + if (NotificationBundleUi.isEnabled()) { + mGutsListener.onGutsClose(row.getEntryAdapter()); + row.updateBubbleButton(); + } else { + mGutsListener.onGutsClose(row.getEntryLegacy()); + } + } + if(NotificationBundleUi.isEnabled()) { + row.getEntryAdapter().setInlineControlsShown(false); + } else { + mHeadsUpManager.setGutsShown(row.getEntryLegacy(), false); } - mHeadsUpManager.setGutsShown(row.getEntry(), false); }); View gutsView = item.getGutsView(); try { if (gutsView instanceof NotificationSnooze) { - initializeSnoozeView(row, (NotificationSnooze) gutsView); + initializeSnoozeView(row, sbn, ranking, (NotificationSnooze) gutsView); } else if (gutsView instanceof NotificationInfo) { - initializeNotificationInfo(row, (NotificationInfo) gutsView); + initializeNotificationInfo(row, sbn, ranking, (NotificationInfo) gutsView); } else if (gutsView instanceof NotificationConversationInfo) { initializeConversationNotificationInfo( - row, (NotificationConversationInfo) gutsView); + row, sbn, ranking, (NotificationConversationInfo) gutsView); } else if (gutsView instanceof PartialConversationInfo) { - initializePartialConversationNotificationInfo(row, + initializePartialConversationNotificationInfo(row, sbn, ranking, (PartialConversationInfo) gutsView); } else if (gutsView instanceof FeedbackInfo) { - initializeFeedbackInfo(row, (FeedbackInfo) gutsView); + initializeFeedbackInfo(row, sbn, ranking, (FeedbackInfo) gutsView); } else if (gutsView instanceof PromotedPermissionGutsContent) { - initializeDemoteView(row, (PromotedPermissionGutsContent) gutsView); + initializeDemoteView(row, sbn, (PromotedPermissionGutsContent) gutsView); } return true; } catch (Exception e) { @@ -342,13 +362,14 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta */ private void initializeSnoozeView( final ExpandableNotificationRow row, + final StatusBarNotification sbn, + final NotificationListenerService.Ranking ranking, NotificationSnooze notificationSnoozeView) { NotificationGuts guts = row.getGuts(); - StatusBarNotification sbn = row.getEntry().getSbn(); notificationSnoozeView.setSnoozeListener(mListContainer.getSwipeActionHelper()); notificationSnoozeView.setStatusBarNotification(sbn); - notificationSnoozeView.setSnoozeOptions(row.getEntry().getSnoozeCriteria()); + notificationSnoozeView.setSnoozeOptions(ranking.getSnoozeCriteria()); guts.setHeightChangedListener((NotificationGuts g) -> { mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */); }); @@ -362,8 +383,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta */ private void initializeDemoteView( final ExpandableNotificationRow row, + StatusBarNotification sbn, PromotedPermissionGutsContent demoteGuts) { - StatusBarNotification sbn = row.getEntry().getSbn(); demoteGuts.setStatusBarNotification(sbn); demoteGuts.setOnDemoteAction(new View.OnClickListener() { @Override @@ -387,16 +408,17 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta */ private void initializeFeedbackInfo( final ExpandableNotificationRow row, + final StatusBarNotification sbn, + final NotificationListenerService.Ranking ranking, FeedbackInfo feedbackInfo) { - if (mAssistantFeedbackController.getFeedbackIcon(row.getEntry()) == null) { + if (mAssistantFeedbackController.getFeedbackIcon(ranking) == null) { return; } - StatusBarNotification sbn = row.getEntry().getSbn(); UserHandle userHandle = sbn.getUser(); PackageManager pmUser = CentralSurfaces.getPackageManagerForUser(mContext, userHandle.getIdentifier()); - feedbackInfo.bindGuts(pmUser, sbn, row.getEntry(), row, mAssistantFeedbackController, + feedbackInfo.bindGuts(pmUser, sbn, ranking, row, mAssistantFeedbackController, mStatusBarService, this); } @@ -408,9 +430,10 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta @VisibleForTesting void initializeNotificationInfo( final ExpandableNotificationRow row, + final StatusBarNotification sbn, + final NotificationListenerService.Ranking ranking, NotificationInfo notificationInfoView) throws Exception { NotificationGuts guts = row.getGuts(); - StatusBarNotification sbn = row.getEntry().getSbn(); String packageName = sbn.getPackageName(); // Settings link is only valid for notifications that specify a non-system user NotificationInfo.OnSettingsClickListener onSettingsClick = null; @@ -449,18 +472,22 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta mChannelEditorDialogController, mPackageDemotionInteractor, packageName, - row.getEntry().getChannel(), - row.getEntry(), + ranking, + sbn, + NotificationBundleUi.isEnabled() ? null : row.getEntryLegacy(), + NotificationBundleUi.isEnabled() ? row.getEntryAdapter() : null, onSettingsClick, onAppSettingsClick, onNasFeedbackClick, mUiEventLogger, mDeviceProvisionedController.isDeviceProvisioned(), NotificationBundleUi.isEnabled() - ? !row.getEntry().isBlockable() + ? !row.getEntryAdapter().isBlockable() : row.getIsNonblockable(), row.canViewBeDismissed(), - mHighPriorityProvider.isHighPriority(row.getEntry()), + NotificationBundleUi.isEnabled() + ? row.getEntryAdapter().isHighPriority() + : mHighPriorityProvider.isHighPriority(row.getEntryLegacy()), mAssistantFeedbackController, mMetricsLogger, row.getCloseButtonOnClickListener(row)); @@ -474,9 +501,10 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta @VisibleForTesting void initializePartialConversationNotificationInfo( final ExpandableNotificationRow row, + final StatusBarNotification sbn, + final NotificationListenerService.Ranking ranking, PartialConversationInfo notificationInfoView) throws Exception { NotificationGuts guts = row.getGuts(); - StatusBarNotification sbn = row.getEntry().getSbn(); String packageName = sbn.getPackageName(); // Settings link is only valid for notifications that specify a non-system user NotificationInfo.OnSettingsClickListener onSettingsClick = null; @@ -499,12 +527,12 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta mNotificationManager, mChannelEditorDialogController, packageName, - row.getEntry().getChannel(), - row.getEntry(), + ranking, + sbn, onSettingsClick, mDeviceProvisionedController.isDeviceProvisioned(), NotificationBundleUi.isEnabled() - ? !row.getEntry().isBlockable() + ? !row.getEntryAdapter().isBlockable() : row.getIsNonblockable()); } @@ -516,10 +544,10 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta @VisibleForTesting void initializeConversationNotificationInfo( final ExpandableNotificationRow row, + final StatusBarNotification sbn, + final NotificationListenerService.Ranking ranking, NotificationConversationInfo notificationInfoView) throws Exception { NotificationGuts guts = row.getGuts(); - NotificationEntry entry = row.getEntry(); - StatusBarNotification sbn = entry.getSbn(); String packageName = sbn.getPackageName(); // Settings link is only valid for notifications that specify a non-system user NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null; @@ -567,9 +595,10 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta mNotificationManager, mOnUserInteractionCallback, packageName, - entry.getChannel(), - entry, - entry.getBubbleMetadata(), + NotificationBundleUi.isEnabled() ? null : row.getEntryLegacy(), + NotificationBundleUi.isEnabled() ? row.getEntryAdapter() : null, + ranking, + sbn, onSettingsClick, onNasFeedbackClick, iconFactoryLoader, @@ -746,13 +775,21 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta row::onGutsOpened); if (mGutsListener != null) { - mGutsListener.onGutsOpen(row.getEntry(), guts); + if(NotificationBundleUi.isEnabled()) { + mGutsListener.onGutsOpen(row.getEntryAdapter(), guts); + } else { + mGutsListener.onGutsOpen(row.getEntryLegacy(), guts); + } } row.closeRemoteInput(); mListContainer.onHeightChanged(row, true /* needsAnimation */); mGutsMenuItem = menuItem; - mHeadsUpManager.setGutsShown(row.getEntry(), true); + if(NotificationBundleUi.isEnabled()) { + row.getEntryAdapter().setInlineControlsShown(true); + } else { + mHeadsUpManager.setGutsShown(row.getEntryLegacy(), true); + } } }; guts.post(mOpenRunnable); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index b6f4ffce8e00..571006bc21e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -50,6 +50,7 @@ import android.metrics.LogMaker; import android.os.Handler; import android.os.RemoteException; import android.service.notification.NotificationAssistantService; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.text.Html; import android.text.TextUtils; @@ -73,10 +74,12 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.AssistantFeedbackController; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor; import com.android.systemui.statusbar.notification.row.icon.AppIconProvider; import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import java.lang.annotation.Retention; import java.util.List; @@ -124,6 +127,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private boolean mIsDismissable; private NotificationEntry mEntry; private StatusBarNotification mSbn; + private NotificationListenerService.Ranking mRanking; + private EntryAdapter mEntryAdapter; private boolean mIsDeviceProvisioned; private boolean mIsSystemRegisteredCall; @@ -198,8 +203,10 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G ChannelEditorDialogController channelEditorDialogController, PackageDemotionInteractor packageDemotionInteractor, String pkg, - NotificationChannel notificationChannel, + NotificationListenerService.Ranking ranking, + StatusBarNotification sbn, NotificationEntry entry, + EntryAdapter entryAdapter, OnSettingsClickListener onSettingsClick, OnAppSettingsClickListener onAppSettingsClick, OnFeedbackClickListener onFeedbackClickListener, @@ -220,14 +227,16 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mChannelEditorDialogController = channelEditorDialogController; mAssistantFeedbackController = assistantFeedbackController; mPackageName = pkg; + mSbn = sbn; + mRanking = ranking; mEntry = entry; - mSbn = entry.getSbn(); + mEntryAdapter = entryAdapter; mPm = pm; mAppSettingsClickListener = onAppSettingsClick; mFeedbackClickListener = onFeedbackClickListener; mAppName = mPackageName; mOnSettingsClickListener = onSettingsClick; - mSingleNotificationChannel = notificationChannel; + mSingleNotificationChannel = ranking.getChannel(); mStartingChannelImportance = mSingleNotificationChannel.getImportance(); mWasShownHighPriority = wasShownHighPriority; mIsNonblockable = isNonblockable; @@ -301,7 +310,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G View automatic = findViewById(R.id.automatic); if (mShowAutomaticSetting) { mAutomaticDescriptionView.setText(Html.fromHtml(mContext.getText( - mAssistantFeedbackController.getInlineDescriptionResource(mEntry)).toString())); + mAssistantFeedbackController.getInlineDescriptionResource(mRanking)) + .toString())); automatic.setVisibility(VISIBLE); automatic.setOnClickListener(mOnAutomatic); } else { @@ -381,7 +391,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private void bindFeedback() { View feedbackButton = findViewById(R.id.feedback); - Intent intent = getAssistantFeedbackIntent(mINotificationManager, mPm, mEntry); + Intent intent = getAssistantFeedbackIntent( + mINotificationManager, mPm, mSbn.getKey(), mRanking); if (!android.app.Flags.notificationClassificationUi() || intent == null) { feedbackButton.setVisibility(GONE); } else { @@ -395,7 +406,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } public static Intent getAssistantFeedbackIntent(INotificationManager inm, PackageManager pm, - NotificationEntry entry) { + String key, NotificationListenerService.Ranking ranking) { try { ComponentName assistant = inm.getAllowedNotificationAssistant(); if (assistant == null) { @@ -414,9 +425,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo; intent.setClassName(activityInfo.packageName, activityInfo.name); - intent.putExtra(NotificationAssistantService.EXTRA_NOTIFICATION_KEY, entry.getKey()); + intent.putExtra(NotificationAssistantService.EXTRA_NOTIFICATION_KEY, key); intent.putExtra(NotificationAssistantService.EXTRA_NOTIFICATION_ADJUSTMENT, - entry.getRanking().getSummarization() != null ? KEY_SUMMARIZATION : KEY_TYPE); + ranking.getSummarization() != null ? KEY_SUMMARIZATION : KEY_TYPE); return intent; } catch (Exception e) { Slog.d(TAG, "no assistant?", e); @@ -526,7 +537,11 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid, mSingleNotificationChannel, mStartingChannelImportance, newImportance, mIsAutomaticChosen)); - mOnUserInteractionCallback.onImportanceChanged(mEntry); + if (NotificationBundleUi.isEnabled()) { + mEntryAdapter.onImportanceChanged(); + } else { + mOnUserInteractionCallback.onImportanceChanged(mEntry); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt index 4f1b90544403..efc90c91092a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt @@ -710,7 +710,7 @@ constructor( .also { logger.logAsyncTaskProgress( entry.logKey, - "extracted promoted notification content: $it", + "extracted promoted notification content: ${it?.toRedactedString()}", ) } } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java index 60e98a5c317a..3b795796020c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java @@ -24,6 +24,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.os.RemoteException; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.AttributeSet; @@ -35,6 +36,7 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.res.R; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** @@ -79,18 +81,18 @@ public class PartialConversationInfo extends LinearLayout implements INotificationManager iNotificationManager, ChannelEditorDialogController channelEditorDialogController, String pkg, - NotificationChannel notificationChannel, - NotificationEntry entry, + NotificationListenerService.Ranking ranking, + StatusBarNotification sbn, NotificationInfo.OnSettingsClickListener onSettingsClick, boolean isDeviceProvisioned, boolean isNonBlockable) { mINotificationManager = iNotificationManager; mPackageName = pkg; - mSbn = entry.getSbn(); + mSbn = sbn; mPm = pm; mAppName = mPackageName; mOnSettingsClickListener = onSettingsClick; - mNotificationChannel = notificationChannel; + mNotificationChannel = ranking.getChannel(); mAppUid = mSbn.getUid(); mDelegatePkg = mSbn.getOpPkg(); mIsDeviceProvisioned = isDeviceProvisioned; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java index 769f0b5a4fa4..2cb208932943 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java @@ -21,6 +21,7 @@ import android.app.NotificationChannel; import android.content.Context; import android.content.pm.PackageManager; import android.os.RemoteException; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.Log; @@ -30,6 +31,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.AssistantFeedbackController; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor; import com.android.systemui.statusbar.notification.row.icon.AppIconProvider; @@ -60,8 +62,10 @@ public class PromotedNotificationInfo extends NotificationInfo { ChannelEditorDialogController channelEditorDialogController, PackageDemotionInteractor packageDemotionInteractor, String pkg, - NotificationChannel notificationChannel, + NotificationListenerService.Ranking ranking, + StatusBarNotification sbn, NotificationEntry entry, + EntryAdapter entryAdapter, OnSettingsClickListener onSettingsClick, OnAppSettingsClickListener onAppSettingsClick, OnFeedbackClickListener feedbackClickListener, @@ -73,17 +77,17 @@ public class PromotedNotificationInfo extends NotificationInfo { AssistantFeedbackController assistantFeedbackController, MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException { super.bindNotification(pm, iNotificationManager, appIconProvider, iconStyleProvider, - onUserInteractionCallback, channelEditorDialogController, packageDemotionInteractor, - pkg, notificationChannel, - entry, onSettingsClick, onAppSettingsClick, feedbackClickListener, uiEventLogger, - isDeviceProvisioned, isNonblockable, isDismissable, wasShownHighPriority, - assistantFeedbackController, metricsLogger, onCloseClick); + onUserInteractionCallback, channelEditorDialogController, + packageDemotionInteractor,pkg, ranking, sbn, + entry, entryAdapter, onSettingsClick, onAppSettingsClick, feedbackClickListener, + uiEventLogger, isDeviceProvisioned, isDismissable, isNonblockable, + wasShownHighPriority, assistantFeedbackController, metricsLogger, onCloseClick); mNotificationManager = iNotificationManager; mPackageDemotionInteractor = packageDemotionInteractor; - bindDemote(entry.getSbn(), pkg); + bindDemote(sbn, pkg); } protected void bindDemote(StatusBarNotification sbn, String packageName) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 503256accff0..afa988dd8e89 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -6076,7 +6076,9 @@ public class NotificationStackScrollLayout if (mBlurRadius > 0) { mBlurEffect = RenderEffect.createBlurEffect(mBlurRadius, mBlurRadius, Shader.TileMode.CLAMP); + spewLog("Setting up blur RenderEffect for NotificationStackScrollLayout"); } else { + spewLog("Clearing the blur RenderEffect setup for NotificationStackScrollLayout"); mBlurEffect = null; } } @@ -6252,6 +6254,7 @@ public class NotificationStackScrollLayout @Override protected void dispatchDraw(@NonNull Canvas canvas) { if (mBlurEffect != null) { + spewLog("Applying blur RenderEffect to NotificationStackScrollLayout"); // reuse the cached RenderNode to blur mBlurNode.setPosition(0, 0, canvas.getWidth(), canvas.getHeight()); mBlurNode.setRenderEffect(mBlurEffect); @@ -7025,4 +7028,10 @@ public class NotificationStackScrollLayout SceneContainerFlag.assertInLegacyMode(); mMaxTopPadding = maxTopPadding; } + + private void spewLog(String logMsg) { + if (SPEW) { + Log.v(TAG, logMsg); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 612c19fc6696..66c9b17ef235 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -420,10 +420,15 @@ public class NotificationStackScrollLayoutController implements Dumpable { return; } if (view instanceof ExpandableNotificationRow row) { - mMetricsLogger.write(row.getEntry().getSbn().getLogMaker() - .setCategory(MetricsEvent.ACTION_TOUCH_GEAR) - .setType(MetricsEvent.TYPE_ACTION) - ); + StatusBarNotification sbn = NotificationBundleUi.isEnabled() + ? row.getEntryAdapter().getSbn() + : row.getEntryLegacy().getSbn(); + if (sbn != null) { + mMetricsLogger.write(row.getEntry().getSbn().getLogMaker() + .setCategory(MetricsEvent.ACTION_TOUCH_GEAR) + .setType(MetricsEvent.TYPE_ACTION) + ); + } } mNotificationGutsManager.openGuts(view, x, y, item); } @@ -440,9 +445,14 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onMenuShown(View row) { if (row instanceof ExpandableNotificationRow notificationRow) { - mMetricsLogger.write(notificationRow.getEntry().getSbn().getLogMaker() - .setCategory(MetricsEvent.ACTION_REVEAL_GEAR) - .setType(MetricsEvent.TYPE_ACTION)); + StatusBarNotification sbn = NotificationBundleUi.isEnabled() + ? notificationRow.getEntryAdapter().getSbn() + : notificationRow.getEntryLegacy().getSbn(); + if (sbn != null) { + mMetricsLogger.write(notificationRow.getEntry().getSbn().getLogMaker() + .setCategory(MetricsEvent.ACTION_REVEAL_GEAR) + .setType(MetricsEvent.TYPE_ACTION)); + } mSwipeHelper.onMenuShown(row); mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, @@ -1355,11 +1365,15 @@ public class NotificationStackScrollLayoutController implements Dumpable { */ public void setBlurRadius(float blurRadius) { if (blurRadius > 0.0f) { + debugLog( + "Setting blur RenderEffect for NotificationStackScrollLayoutController with " + + "radius " + blurRadius); mView.setRenderEffect(RenderEffect.createBlurEffect( blurRadius, blurRadius, Shader.TileMode.CLAMP)); } else { + debugLog("Resetting blur RenderEffect for NotificationStackScrollLayoutController"); mView.setRenderEffect(null); } } @@ -2175,4 +2189,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { && !mSwipeHelper.isSwiping(); } } + + private void debugLog(String msg) { + if (DEBUG) { + Log.d(TAG, msg); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 8a447f7395d4..60f1777e80bc 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -64,6 +64,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationChannelHelper; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -618,6 +619,28 @@ public class BubblesManager { } /** + * When a notification is set as important, make it a bubble + * + * @param entryAdapter the important notification. + */ + public void onUserSetImportantConversation(EntryAdapter entryAdapter) { + if (entryAdapter.getSbn() != null + && entryAdapter.getSbn().getNotification().getBubbleMetadata() == null) { + // No bubble metadata, nothing to do. + return; + } + try { + int flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + mBarService.onNotificationBubbleChanged(entryAdapter.getKey(), true, flags); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + mShadeController.collapseShade(true); + // NotificationGutsManager will refresh the ENR when the guts close and update the + // bubble button if needed + } + + /** * Called when a user has indicated that an active notification should be shown as a bubble. * <p> * This method will collapse the shade, create the bubble without a flyout or dot, and suppress diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java index 798aa428e73e..eb72acc0dade 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java @@ -109,6 +109,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @@ -151,8 +152,10 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { private MediaDevice mMediaDevice1; @Mock private MediaDevice mMediaDevice2; - @Mock - private NearbyDevice mNearbyDevice1; + @Mock private MediaDevice mMediaDevice3; + @Mock private MediaDevice mMediaDevice4; + @Mock private MediaDevice mMediaDevice5; + @Mock private NearbyDevice mNearbyDevice1; @Mock private NearbyDevice mNearbyDevice2; @Mock @@ -1550,6 +1553,89 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { assertThat(items.get(1).getMediaDevice().get()).isEqualTo(mMediaDevice2); } + @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING) + @Test + public void selectedDevicesAddedInSameOrderWhenRlpDoesNotExist() { + setUpSelectedDevicesAndOrdering(); + + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); + + List<MediaDevice> devices = + mMediaSwitchingController.getMediaItemList().stream() + .filter(item -> item.getMediaDevice().isPresent()) + .map(item -> item.getMediaDevice().orElse(null)) + .collect(Collectors.toList()); + assertThat(devices) + .containsExactly( + mMediaDevice4, + mMediaDevice3, + mMediaDevice5, + mMediaDevice1, + mMediaDevice2) + .inOrder(); + } + + @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING) + @Test + public void selectedDevicesAddedInSortedOrderWhenRlpDoesNotExist() { + setUpSelectedDevicesAndOrdering(); + + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); + + List<MediaDevice> devices = + mMediaSwitchingController.getMediaItemList().stream() + .filter(item -> item.getMediaDevice().isPresent()) + .map(item -> item.getMediaDevice().orElse(null)) + .collect(Collectors.toList()); + + assertThat(devices) + .containsExactly( + mMediaDevice5, + mMediaDevice4, + mMediaDevice3, + mMediaDevice1, + mMediaDevice2) + .inOrder(); + } + + private void setUpSelectedDevicesAndOrdering() { + when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_1_ID); + when(mMediaDevice2.getId()).thenReturn(TEST_DEVICE_2_ID); + when(mMediaDevice3.getId()).thenReturn(TEST_DEVICE_3_ID); + when(mMediaDevice4.getId()).thenReturn(TEST_DEVICE_4_ID); + when(mMediaDevice5.getId()).thenReturn(TEST_DEVICE_5_ID); + mMediaDevices.clear(); + Collections.addAll( + mMediaDevices, + mMediaDevice2, + mMediaDevice1, + mMediaDevice4, + mMediaDevice3, + mMediaDevice5); + List<MediaDevice> selectedMediaDevices = new ArrayList<>(); + Collections.addAll(selectedMediaDevices, mMediaDevice3, mMediaDevice4, mMediaDevice5); + doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice(); + // Sort the media devices in the order they appear in the deviceOrder list + List<MediaDevice> deviceOrder = new ArrayList<>(); + Collections.addAll( + deviceOrder, + mMediaDevice1, + mMediaDevice2, + mMediaDevice3, + mMediaDevice4, + mMediaDevice5); + for (int i = 0; i < deviceOrder.size(); i++) { + for (int j = i + 1; j < deviceOrder.size(); j++) { + when(deviceOrder.get(i).compareTo(deviceOrder.get(j))).thenReturn(-1); + when(deviceOrder.get(j).compareTo(deviceOrder.get(i))).thenReturn(1); + } + } + when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false); + mMediaSwitchingController.start(mCb); + reset(mCb); + mMediaSwitchingController.getMediaItemList().clear(); + } + @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING) @Test public void selectedDevicesAddedInReverseOrder() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index bc7ab9d4fe3c..eae23e70027b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -78,6 +78,8 @@ import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryAdapter; import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.headsup.PinnedStatus; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi; @@ -1023,6 +1025,8 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { mock(NotificationIconStyleProvider.class), mock(VisualStabilityCoordinator.class), mock(NotificationActionClickManager.class), + mock(HighPriorityProvider.class), + mock(HeadsUpManager.class), entry); row.setEntryAdapter(entryAdapter); } else { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index 3061842c386c..2c800bd87ef5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -89,8 +89,16 @@ import com.android.systemui.res.R; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; +import com.android.systemui.statusbar.notification.NotificationActivityStarter; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; +import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider; import com.android.systemui.wmshell.BubblesManager; import com.android.systemui.wmshell.BubblesTestActivity; @@ -127,6 +135,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { private NotificationChannel mConversationChannel; private StatusBarNotification mSbn; private NotificationEntry mEntry; + private EntryAdapter mEntryAdapter; private StatusBarNotification mBubbleSbn; private NotificationEntry mBubbleEntry; @Mock @@ -228,7 +237,21 @@ public class NotificationConversationInfoTest extends SysuiTestCase { notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, applicationInfo); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, notification, UserHandle.CURRENT, null, 0); - mEntry = new NotificationEntryBuilder().setSbn(mSbn).setShortcutInfo(mShortcutInfo).build(); + mEntry = new NotificationEntryBuilder().setSbn(mSbn).setShortcutInfo(mShortcutInfo) + .updateRanking(rankingBuilder -> { + rankingBuilder.setChannel(mNotificationChannel); + }) + .build(); + mEntryAdapter = new EntryAdapterFactoryImpl( + mock(NotificationActivityStarter.class), + mock(MetricsLogger.class), + mock(PeopleNotificationIdentifier.class), + mock(NotificationIconStyleProvider.class), + mock(VisualStabilityCoordinator.class), + mock(NotificationActionClickManager.class), + mock(HighPriorityProvider.class), + mock(HeadsUpManager.class) + ).create(mEntry); PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, BubblesTestActivity.class), @@ -264,9 +287,10 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mOnUserInteractionCallback, TEST_PACKAGE_NAME, - mNotificationChannel, mEntry, - mBubbleMetadata, + mEntryAdapter, + mEntry.getRanking(), + mSbn, null, null, mIconFactory, @@ -367,9 +391,10 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mOnUserInteractionCallback, TEST_PACKAGE_NAME, - mNotificationChannel, - entry, - mBubbleMetadata, + mEntry, + mEntryAdapter, + mEntry.getRanking(), + mSbn, null, null, mIconFactory, @@ -404,9 +429,10 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mOnUserInteractionCallback, TEST_PACKAGE_NAME, - mNotificationChannel, mEntry, - mBubbleMetadata, + mEntryAdapter, + mEntry.getRanking(), + mSbn, null, (View v, Intent intent) -> { latch.countDown(); @@ -444,9 +470,10 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mOnUserInteractionCallback, TEST_PACKAGE_NAME, - mNotificationChannel, mEntry, - mBubbleMetadata, + mEntryAdapter, + mEntry.getRanking(), + mSbn, (View v, NotificationChannel c, int appUid) -> { assertEquals(mConversationChannel, c); latch.countDown(); @@ -483,9 +510,10 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mOnUserInteractionCallback, TEST_PACKAGE_NAME, - mNotificationChannel, mEntry, - mBubbleMetadata, + mEntryAdapter, + mEntry.getRanking(), + mSbn, (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); latch.countDown(); @@ -553,6 +581,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mConversationChannel.setImportance(IMPORTANCE_HIGH); mConversationChannel.setImportantConversation(false); mConversationChannel.setAllowBubbles(false); + mSbn.getNotification().setBubbleMetadata(null); mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, @@ -561,9 +590,10 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mOnUserInteractionCallback, TEST_PACKAGE_NAME, - mNotificationChannel, mEntry, - null, + mEntryAdapter, + mEntry.getRanking(), + mSbn, null, null, mIconFactory, @@ -583,6 +613,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mConversationChannel.setImportance(IMPORTANCE_HIGH); mConversationChannel.setImportantConversation(false); mConversationChannel.setAllowBubbles(false); + mSbn.getNotification().setBubbleMetadata(null); mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, @@ -591,9 +622,10 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mMockINotificationManager, mOnUserInteractionCallback, TEST_PACKAGE_NAME, - mNotificationChannel, mEntry, - null, + mEntryAdapter, + mEntry.getRanking(), + mSbn, null, null, mIconFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt index 10de86644015..49ebc8c83ea7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt @@ -63,13 +63,16 @@ import com.android.systemui.statusbar.NotificationEntryHelper import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.AssistantFeedbackController import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider +import com.android.systemui.statusbar.notification.collection.provider.mockHighPriorityProvider import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.headsup.mockHeadsUpManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor import com.android.systemui.statusbar.notification.row.icon.appIconProvider import com.android.systemui.statusbar.notification.row.icon.notificationIconStyleProvider +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.notificationLockscreenUserManager import com.android.systemui.statusbar.policy.deviceProvisionedController @@ -233,14 +236,22 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong()) executor.runAllReady() verify(guts).openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>()) - verify(headsUpManager).setGutsShown(realRow!!.entry, true) + if (NotificationBundleUi.isEnabled) { + verify(kosmos.mockHeadsUpManager).setGutsShown(any<NotificationEntry>(), eq(true)) + } else { + verify(headsUpManager).setGutsShown(realRow!!.entry, true) + } assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong()) gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false) verify(guts) .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>()) verify(row, times(1)).setGutsView(any()) executor.runAllReady() - verify(headsUpManager).setGutsShown(realRow.entry, false) + if (NotificationBundleUi.isEnabled) { + verify(kosmos.mockHeadsUpManager).setGutsShown(any<NotificationEntry>(), eq(false)) + } else { + verify(headsUpManager).setGutsShown(realRow!!.entry, false) + } } @Test @@ -385,16 +396,23 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { @Throws(Exception::class) fun testInitializeNotificationInfoView_highPriority() { val notificationInfoView = mock<NotificationInfo>() - val row = spy(helper.createRow()) - val entry = row.entry + val row = createTestNotificationRow() + val entry = row!!.entry NotificationEntryHelper.modifyRanking(entry) .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) .setImportance(NotificationManager.IMPORTANCE_HIGH) .build() - whenever(row.canViewBeDismissed()).thenReturn(true) + whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true) + whenever(kosmos.mockHighPriorityProvider.isHighPriority(entry)).thenReturn(true) val statusBarNotification = entry.sbn - gutsManager.initializeNotificationInfo(row, notificationInfoView) + + gutsManager.initializeNotificationInfo( + row, + statusBarNotification, + entry.ranking, + notificationInfoView, + ) verify(notificationInfoView) .bindNotification( any<PackageManager>(), @@ -405,15 +423,17 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { eq(channelEditorDialogController), eq(packageDemotionInteractor), eq(statusBarNotification.packageName), - any<NotificationChannel>(), - eq(entry), + eq(entry.ranking), + eq(statusBarNotification), + if (NotificationBundleUi.isEnabled) eq(null) else eq(entry), + if (NotificationBundleUi.isEnabled) eq(row.entryAdapter) else eq(null), any<NotificationInfo.OnSettingsClickListener>(), any<NotificationInfo.OnAppSettingsClickListener>(), any<NotificationInfo.OnFeedbackClickListener>(), any<UiEventLogger>(), eq(true), eq(false), - eq(true), + eq(false), eq(true), eq(assistantFeedbackController), any<MetricsLogger>(), @@ -425,14 +445,19 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { @Throws(Exception::class) fun testInitializeNotificationInfoView_PassesAlongProvisionedState() { val notificationInfoView = mock<NotificationInfo>() - val row = spy(helper.createRow()) + val row = createTestNotificationRow() NotificationEntryHelper.modifyRanking(row.entry) .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) .build() - whenever(row.canViewBeDismissed()).thenReturn(true) val statusBarNotification = row.entry.sbn val entry = row.entry - gutsManager.initializeNotificationInfo(row, notificationInfoView) + + gutsManager.initializeNotificationInfo( + row, + statusBarNotification, + entry.ranking, + notificationInfoView, + ) verify(notificationInfoView) .bindNotification( any<PackageManager>(), @@ -443,15 +468,17 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { eq(channelEditorDialogController), eq(packageDemotionInteractor), eq(statusBarNotification.packageName), - any<NotificationChannel>(), - eq(entry), + eq(entry.ranking), + eq(statusBarNotification), + if (NotificationBundleUi.isEnabled) eq(null) else eq(entry), + if (NotificationBundleUi.isEnabled) eq(row.entryAdapter) else eq(null), any<NotificationInfo.OnSettingsClickListener>(), any<NotificationInfo.OnAppSettingsClickListener>(), any<NotificationInfo.OnFeedbackClickListener>(), any<UiEventLogger>(), eq(true), eq(false), - eq(true), /* wasShownHighPriority */ + eq(false), /* wasShownHighPriority */ eq(false), eq(assistantFeedbackController), any<MetricsLogger>(), @@ -470,7 +497,15 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { whenever(row.canViewBeDismissed()).thenReturn(true) val statusBarNotification = row.entry.sbn val entry = row.entry - gutsManager.initializeNotificationInfo(row, notificationInfoView) + val entryAdapter = kosmos.entryAdapterFactory.create(entry) + row.entryAdapter = entryAdapter + + gutsManager.initializeNotificationInfo( + row, + statusBarNotification, + entry.ranking, + notificationInfoView, + ) verify(notificationInfoView) .bindNotification( any<PackageManager>(), @@ -481,8 +516,10 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { eq(channelEditorDialogController), eq(packageDemotionInteractor), eq(statusBarNotification.packageName), - any<NotificationChannel>(), - eq(entry), + eq(entry.ranking), + eq(statusBarNotification), + if (NotificationBundleUi.isEnabled) eq(null) else eq(entry), + if (NotificationBundleUi.isEnabled) eq(entryAdapter) else eq(null), any<NotificationInfo.OnSettingsClickListener>(), any<NotificationInfo.OnAppSettingsClickListener>(), any<NotificationInfo.OnFeedbackClickListener>(), @@ -497,7 +534,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { ) } - private fun createTestNotificationRow(): ExpandableNotificationRow? { + private fun createTestNotificationRow(): ExpandableNotificationRow { val nb = Notification.Builder(mContext, testNotificationChannel.id) .setContentTitle("foo") @@ -505,16 +542,10 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { .setColor(Color.RED) .setFlag(Notification.FLAG_CAN_COLORIZE, true) .setSmallIcon(R.drawable.sym_def_app_icon) - return try { - val row = helper.createRow(nb.build()) - NotificationEntryHelper.modifyRanking(row.entry) - .setChannel(testNotificationChannel) - .build() - row - } catch (_: Exception) { - Assert.fail() - null - } + val row = helper.createRow(nb.build()) + NotificationEntryHelper.modifyRanking(row.entry).setChannel(testNotificationChannel).build() + row.entryAdapter = kosmos.entryAdapterFactory.create(row.entry) + return row } private fun setIsLockscreenOrShadeVisible(isVisible: Boolean) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt index c86ba6ccf47f..ebf89e9e3889 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt @@ -19,14 +19,40 @@ package com.android.systemui import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.useStandardTestDispatcher +import com.android.systemui.kosmos.useUnconfinedTestDispatcher -fun SysuiTestCase.testKosmos(): Kosmos = Kosmos().apply { testCase = this@testKosmos } +/** + * This definition, which uses standard dispatcher, is eventually going away. + * + * If you are calling this method, and want the new default behavior, call `testKosmosNew`, and you + * will be migrated to the new behavior (unconfined dispatcher). If you want to maintain the old + * behavior, directly call testKosmosNew().useStandardTestDispatcher(). + * + * The migration will proceed in multiple steps: + * 1. All calls to testKosmos will be converted to testKosmosLegacy, maybe over several CLs. + * 2. When there are zero references to testKosmos, it will be briefly deleted + * 3. A new testKosmos will be introduced that uses unconfined test dispatcher + * 4. All callers to testKosmosNew that have been introduced since step 1 will be migrated to this + * new definition of testKosmos + * 5. testKosmosNew will be deleted + * 6. Over time, test authors will be encouraged to migrate away from testKosmosLegacy + * + * For details, see go/thetiger + */ +// TODO(b/342622417) +fun SysuiTestCase.testKosmos(): Kosmos = testKosmosLegacy() + +/** + * Create a new Kosmos instance using the unconfined test dispatcher. See migration notes on + * [testKosmos] + */ +fun SysuiTestCase.testKosmosNew(): Kosmos = + Kosmos().apply { testCase = this@testKosmosNew }.useUnconfinedTestDispatcher() /** * This should not be called directly. Instead, you can use: - * - testKosmos() to use the default dispatcher (which will soon be unconfined, see go/thetiger) - * - testKosmos().useStandardTestDispatcher() to explicitly choose the standard dispatcher - * - testKosmos().useUnconfinedTestDispatcher() to explicitly choose the unconfined dispatcher + * - testKosmosNew().useStandardTestDispatcher() to explicitly choose the standard dispatcher + * - testKosmosNew() to explicitly choose the unconfined dispatcher (which is the new sysui default) * * For details, see go/thetiger */ diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt index 1a5c61a04c9f..aba4942b44e4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.notification.stack.data.repository.headsUp import com.android.systemui.statusbar.notification.visibilityLocationProvider import com.android.systemui.statusbar.policy.keyguardStateController import com.android.systemui.util.kotlin.JavaAdapter +import org.mockito.kotlin.mock var Kosmos.visualStabilityCoordinator: VisualStabilityCoordinator by Kosmos.Fixture { @@ -54,3 +55,5 @@ var Kosmos.visualStabilityCoordinator: VisualStabilityCoordinator by visualStabilityCoordinatorLogger, ) } + +var Kosmos.mockVisualStabilityCoordinator: VisualStabilityCoordinator by Kosmos.Fixture { mock() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProviderKosmos.kt new file mode 100644 index 000000000000..aa30828d4375 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProviderKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.provider + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.mockHighPriorityProvider: HighPriorityProvider by Kosmos.Fixture { mock() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt index 067e420b89c3..4b699702d54f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt @@ -19,7 +19,9 @@ package com.android.systemui.statusbar.notification.row import com.android.internal.logging.metricsLogger import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl -import com.android.systemui.statusbar.notification.collection.coordinator.visualStabilityCoordinator +import com.android.systemui.statusbar.notification.collection.coordinator.mockVisualStabilityCoordinator +import com.android.systemui.statusbar.notification.collection.provider.mockHighPriorityProvider +import com.android.systemui.statusbar.notification.headsup.mockHeadsUpManager import com.android.systemui.statusbar.notification.mockNotificationActivityStarter import com.android.systemui.statusbar.notification.people.peopleNotificationIdentifier import com.android.systemui.statusbar.notification.row.icon.notificationIconStyleProvider @@ -31,7 +33,9 @@ val Kosmos.entryAdapterFactory by metricsLogger, peopleNotificationIdentifier, notificationIconStyleProvider, - visualStabilityCoordinator, + mockVisualStabilityCoordinator, mockNotificationActionClickManager, + mockHighPriorityProvider, + mockHeadsUpManager, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt index 6a674ca29ca4..b6acf477b660 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt @@ -60,6 +60,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl @@ -376,6 +377,8 @@ class ExpandableNotificationRowBuilder( Mockito.mock(NotificationIconStyleProvider::class.java), Mockito.mock(VisualStabilityCoordinator::class.java), Mockito.mock(NotificationActionClickManager::class.java), + Mockito.mock(HighPriorityProvider::class.java), + Mockito.mock(HeadsUpManager::class.java), ) .create(entry) diff --git a/ravenwood/tools/hoststubgen/README.md b/ravenwood/tools/hoststubgen/README.md index 615e7671bea1..9f72611953c2 100644 --- a/ravenwood/tools/hoststubgen/README.md +++ b/ravenwood/tools/hoststubgen/README.md @@ -17,62 +17,3 @@ AndroidHeuristicsFilter has hardcoded heuristics to detect AIDL generated classe - More Android specific build files and code are stored in `frameworks/base/Ravenwood.bp` `frameworks/base/ravenwood`. - -## Directories and files - -- `src/` - - HostStubGen tool source code. - -- `annotations-src/` See `Android.bp`. -- `helper-framework-buildtime-src/` See `Android.bp`. -- `helper-framework-runtime-src/` See `Android.bp`. -- `helper-runtime-src/` See `Android.bp`. - -- `test-tiny-framework/` See `README.md` in it. - -- `scripts` - - `dump-jar.sh` - - A script to dump the content of `*.class` and `*.jar` files. - - - `run-all-tests.sh` - - Run all tests. Many tests may fail, but at least this should run til the end. - (It should print `run-all-tests.sh finished` at the end) - -## Build and run - -### Building `HostStubGen` binary - -``` -m hoststubgen -``` - -### Run the tests - -- Run all relevant tests and test scripts. All of it is expected to pass, and it'll print - "Ready to submit" at the end. - - However, because some of the script it executes depend on internal file paths to Soong's - intermediate directory, some of it might fail when something changes in the build system. - - We need proper build system integration to fix them. -``` -$ ./scripts/run-all-tests.sh -``` - -- See also `README.md` in `test-*` directories. - -## TODOs, etc - - - Make sure the parent's visibility is not smaller than the member's. - -- @HostSideTestNativeSubstitutionClass should automatically add class-keep to the substitute class. - (or at least check it.) - - - The `HostStubGenTest-framework-test-host-test-lib` jar somehow contain all ASM classes? Figure out where the dependency is coming from. - -- At some point, we can move or delete all Android specific code to `frameworks/base/ravenwood`. - - `helper-framework-*-src` should be moved to `frameworks/base/ravenwood` - - `test-framework` should be deleted. diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt index f8bb526d0a86..760999f5e129 100644 --- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt @@ -60,7 +60,7 @@ class ClassWidePolicyPropagatingFilter( } return p.withReason(policy.reason) - .wrapReason("class-wide in $className") + .wrapReason("class-wide in $className", policy.statsLabelOverride) } // If the class's policy is remove, then remove it. if (policy.policy == FilterPolicy.Remove) { diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt index 7358a0bfb3e6..e082bbb0a119 100644 --- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt @@ -41,7 +41,7 @@ enum class StatsLabel(val statValue: Int, val label: String) { data class FilterPolicyWithReason ( val policy: FilterPolicy, val reason: String = "", - private val statsLabelOverride: StatsLabel? = null + val statsLabelOverride: StatsLabel? = null ) { /** * Return a new [FilterPolicy] with an updated reason, while keeping the original reason @@ -51,7 +51,7 @@ data class FilterPolicyWithReason ( return FilterPolicyWithReason( policy, "$reason [inner-reason: ${this.reason}]", - statsLabelOverride = statsLabelOverride, + statsLabelOverride = statsLabelOverride ?: this.statsLabelOverride, ) } diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt index 97fc35302528..cdcea4c15820 100644 --- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt @@ -528,7 +528,8 @@ class TextFileFilterPolicyParser { ) } val p = policy.withReason( - "$FILTER_REASON (special-class AIDL)" + "$FILTER_REASON (special-class AIDL)", + StatsLabel.SupportedButBoring, ) processor.onSpecialClassPolicy(classType, p) aidlPolicy = p @@ -541,7 +542,8 @@ class TextFileFilterPolicyParser { ) } val p = policy.withReason( - "$FILTER_REASON (special-class feature flags)" + "$FILTER_REASON (special-class feature flags)", + StatsLabel.SupportedButBoring, ) processor.onSpecialClassPolicy(classType, p) featureFlagsPolicy = p @@ -554,7 +556,8 @@ class TextFileFilterPolicyParser { ) } val p = policy.withReason( - "$FILTER_REASON (special-class sysprops)" + "$FILTER_REASON (special-class sysprops)", + StatsLabel.SupportedButBoring, ) processor.onSpecialClassPolicy(classType, p) syspropsPolicy = p @@ -567,7 +570,8 @@ class TextFileFilterPolicyParser { ) } val p = policy.withReason( - "$FILTER_REASON (special-class R file)" + "$FILTER_REASON (special-class R file)", + StatsLabel.SupportedButBoring, ) processor.onSpecialClassPolicy(classType, p) rFilePolicy = p diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/README.md b/ravenwood/tools/hoststubgen/test-tiny-framework/README.md deleted file mode 100644 index 344b4e953b23..000000000000 --- a/ravenwood/tools/hoststubgen/test-tiny-framework/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# HostStubGen: tiny-framework test - -This directory contains a small classes that "simulates" framework.jar, and tests against it. - -This test is agnostic to Android, and it doesn't use any android framework code or knowledge. - -## How to run - -- With `atest`. This is the proper way to run it, but `atest` has known problems that may - affect the result. If you see weird problems, try the next `run-ravenwood-test` command. - -``` -$ atest hoststubgen-test-tiny-test -``` - -- `run-test-manually.sh` also run the test, but it builds the stub/impl jars and the test without - using the build system. This is useful for debugging the tool. - -``` -$ ./run-test-manually.sh -```
\ No newline at end of file diff --git a/services/Android.bp b/services/Android.bp index efd35ce8f1a3..8657bfc79316 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -181,7 +181,7 @@ art_profile_java_defaults { conditions_default: { dex_preopt: { app_image: true, - profile: "art-profile", + profile: ":art-profile-combined", }, }, }, @@ -391,9 +391,14 @@ platform_compat_config { src: ":services", } -filegroup { - name: "art-profile", - srcs: ["art-profile"], +genrule { + name: "art-profile-combined", + srcs: [ + "art-profile", + "art-profile-extra", + ], + out: ["art-profile-combined"], + cmd: "cat $(location art-profile) $(location art-profile-extra) > $(location art-profile-combined)", } // API stub diff --git a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java index edcf5748a8fc..8cf94b464a1a 100644 --- a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java @@ -160,24 +160,16 @@ public class HearingDevicePhoneCallNotificationController { mHearingDevice = null; } if (state == TelephonyManager.CALL_STATE_OFFHOOK) { - if (com.android.server.accessibility.Flags.hearingInputChangeWhenCommDevice()) { - AudioDeviceInfo commDevice = mAudioManager.getCommunicationDevice(); - if (commDevice == null) { - return; - } - mHearingDevice = getSupportedInputHearingDeviceInfo(List.of(commDevice)); - if (mHearingDevice != null) { - showNotificationIfNeeded(); - } else { - addOnCommunicationDeviceChangedListenerIfNeeded(mCommDeviceChangedExecutor, - mCommDeviceChangedListener); - } + AudioDeviceInfo commDevice = mAudioManager.getCommunicationDevice(); + if (commDevice == null) { + return; + } + mHearingDevice = getSupportedInputHearingDeviceInfo(List.of(commDevice)); + if (mHearingDevice != null) { + showNotificationIfNeeded(); } else { - mHearingDevice = getSupportedInputHearingDeviceInfo( - mAudioManager.getAvailableCommunicationDevices()); - if (mHearingDevice != null) { - showNotificationIfNeeded(); - } + addOnCommunicationDeviceChangedListenerIfNeeded(mCommDeviceChangedExecutor, + mCommDeviceChangedListener); } } } diff --git a/services/art-profile-extra b/services/art-profile-extra new file mode 100644 index 000000000000..54362411e5ea --- /dev/null +++ b/services/art-profile-extra @@ -0,0 +1 @@ +HSPLcom/android/server/am/ActivityManagerService$LocalService;->checkContentProviderAccess(Ljava/lang/String;I)Ljava/lang/String; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 5395d2a914ec..c15915ba39a4 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -790,7 +790,7 @@ public final class ActiveServices { "SHORT_FGS_TIMEOUT"); this.mServiceFGAnrTimer = new ServiceAnrTimer(service, ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, - "SERVICE_FOREGROUND_TIMEOUT"); + "SERVICE_FOREGROUND_TIMEOUT", new AnrTimer.Args().extend(true)); } void systemServicesReady() { @@ -7702,6 +7702,11 @@ public final class ActiveServices { super(Objects.requireNonNull(am).mHandler, msg, label); } + ServiceAnrTimer(ActivityManagerService am, int msg, String label, + @NonNull AnrTimer.Args args) { + super(Objects.requireNonNull(am).mHandler, msg, label, args); + } + @Override public int getPid(@NonNull ServiceRecord service) { return (service.app != null) ? service.app.getPid() : 0; diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index 225c7ca2ca9e..83db027e1b41 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -2264,8 +2264,8 @@ public class AppProfiler { final int idleTime = mProcessCpuTracker.getLastIdleTime(); bstats.addCpuStatsLocked(totalUTime, totalSTime, userTime, systemTime, iowaitTime, irqTime, softIrqTime, idleTime); + bstats.finishAddingCpuStatsLocked(); } - bstats.finishAddingCpuStatsLocked(); } if (mLastWriteTime < (now - BATTERY_STATS_TIME)) { diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 5ff6999e40b3..6b4a99cc4fae 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -211,7 +211,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub private static final int POWER_STATS_QUERY_TIMEOUT_MILLIS = 2000; private static final String DEVICE_CONFIG_NAMESPACE = "backstage_power"; private static final String MIN_CONSUMED_POWER_THRESHOLD_KEY = "min_consumed_power_threshold"; - private static final String EMPTY = "Empty"; private final HandlerThread mHandlerThread; private final Handler mHandler; @@ -336,55 +335,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } - @Override - public String getSubsystemLowPowerStats() { - synchronized (mPowerStatsLock) { - if (mPowerStatsInternal == null || mEntityNames.isEmpty() || mStateNames.isEmpty()) { - return EMPTY; - } - } - - final StateResidencyResult[] results; - try { - results = mPowerStatsInternal.getStateResidencyAsync(new int[0]) - .get(POWER_STATS_QUERY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - } catch (Exception e) { - Slog.e(TAG, "Failed to getStateResidencyAsync", e); - return EMPTY; - } - - if (results == null || results.length == 0) return EMPTY; - - int charsLeft = MAX_LOW_POWER_STATS_SIZE; - StringBuilder builder = new StringBuilder("SubsystemPowerState"); - for (int i = 0; i < results.length; i++) { - final StateResidencyResult result = results[i]; - StringBuilder subsystemBuilder = new StringBuilder(); - subsystemBuilder.append(" subsystem_" + i); - subsystemBuilder.append(" name=" + mEntityNames.get(result.id)); - - for (int j = 0; j < result.stateResidencyData.length; j++) { - final StateResidency stateResidency = result.stateResidencyData[j]; - subsystemBuilder.append(" state_" + j); - subsystemBuilder.append(" name=" + mStateNames.get(result.id).get( - stateResidency.id)); - subsystemBuilder.append(" time=" + stateResidency.totalTimeInStateMs); - subsystemBuilder.append(" count=" + stateResidency.totalStateEntryCount); - subsystemBuilder.append(" last entry=" + stateResidency.lastEntryTimestampMs); - } - - if (subsystemBuilder.length() <= charsLeft) { - charsLeft -= subsystemBuilder.length(); - builder.append(subsystemBuilder); - } else { - Slog.e(TAG, "getSubsystemLowPowerStats: buffer not enough"); - break; - } - } - - return builder.toString(); - } - private ConnectivityManager.NetworkCallback mNetworkCallback = new ConnectivityManager.NetworkCallback() { @Override diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index c2ed4d557e69..4eadab27aa26 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -317,8 +317,6 @@ public class SettingsToPropertiesMapper { private final String[] mDeviceConfigScopes; - private final String[] mDeviceConfigAconfigScopes; - private final ContentResolver mContentResolver; @VisibleForTesting @@ -329,7 +327,6 @@ public class SettingsToPropertiesMapper { mContentResolver = contentResolver; mGlobalSettings = globalSettings; mDeviceConfigScopes = deviceConfigScopes; - mDeviceConfigAconfigScopes = deviceConfigAconfigScopes; } @VisibleForTesting @@ -375,36 +372,6 @@ public class SettingsToPropertiesMapper { return; } setProperty(propertyName, properties.getString(key, null)); - - // for legacy namespaces, they can also be used for trunk stable - // purposes. so push flag also into trunk stable slot in sys prop, - // later all legacy usage will be refactored and the sync to old - // sys prop slot can be removed. - String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key); - if (aconfigPropertyName == null) { - logErr("unable to construct system property for " + scope + "/" - + key); - return; - } - setProperty(aconfigPropertyName, properties.getString(key, null)); - } - }); - } - - for (String deviceConfigAconfigScope : mDeviceConfigAconfigScopes) { - DeviceConfig.addOnPropertiesChangedListener( - deviceConfigAconfigScope, - AsyncTask.THREAD_POOL_EXECUTOR, - (DeviceConfig.Properties properties) -> { - String scope = properties.getNamespace(); - for (String key : properties.getKeyset()) { - String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key); - if (aconfigPropertyName == null) { - logErr("unable to construct system property for " + scope + "/" - + key); - return; - } - setProperty(aconfigPropertyName, properties.getString(key, null)); } }); } @@ -420,34 +387,6 @@ public class SettingsToPropertiesMapper { stageFlagsInNewStorage(properties); return; } - - for (String flagName : properties.getKeyset()) { - String flagValue = properties.getString(flagName, null); - if (flagName == null || flagValue == null) { - continue; - } - - int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER); - if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { - logErr("invalid staged flag: " + flagName); - continue; - } - - String actualNamespace = flagName.substring(0, idx); - String actualFlagName = flagName.substring(idx+1); - String propertyName = "next_boot." + makeAconfigFlagPropertyName( - actualNamespace, actualFlagName); - - if (Flags.supportLocalOverridesSysprops()) { - // Don't propagate if there is a local override. - String overrideName = actualNamespace + ":" + actualFlagName; - if (DeviceConfig.getProperty(NAMESPACE_LOCAL_OVERRIDES, overrideName) != null) { - continue; - } - } - setProperty(propertyName, flagValue); - } - }); // add prop sync callback for flag local overrides @@ -459,42 +398,6 @@ public class SettingsToPropertiesMapper { setLocalOverridesInNewStorage(properties); return; } - - if (Flags.supportLocalOverridesSysprops()) { - String overridesNamespace = properties.getNamespace(); - for (String key : properties.getKeyset()) { - String realNamespace = key.split(":")[0]; - String realFlagName = key.split(":")[1]; - String aconfigPropertyName = - makeAconfigFlagPropertyName(realNamespace, realFlagName); - if (aconfigPropertyName == null) { - logErr("unable to construct system property for " + realNamespace + "/" - + key); - return; - } - - if (properties.getString(key, null) == null) { - String deviceConfigValue = - DeviceConfig.getProperty(realNamespace, realFlagName); - String stagedDeviceConfigValue = - DeviceConfig.getProperty(NAMESPACE_REBOOT_STAGING, - realNamespace + "*" + realFlagName); - - setProperty(aconfigPropertyName, deviceConfigValue); - if (stagedDeviceConfigValue == null) { - setProperty("next_boot." + aconfigPropertyName, deviceConfigValue); - } else { - setProperty("next_boot." + aconfigPropertyName, stagedDeviceConfigValue); - } - } else { - // Otherwise, propagate the override to sysprops. - setProperty(aconfigPropertyName, properties.getString(key, null)); - // If there's a staged value, make sure it's the override value. - setProperty("next_boot." + aconfigPropertyName, - properties.getString(key, null)); - } - } - } }); } @@ -822,28 +725,6 @@ public class SettingsToPropertiesMapper { sendAconfigdRequests(requests); } - /** - * system property name constructing rule for aconfig flags: - * "persist.device_config.aconfig_flags.[category_name].[flag_name]". - * If the name contains invalid characters or substrings for system property name, - * will return null. - * @param categoryName - * @param flagName - * @return - */ - @VisibleForTesting - static String makeAconfigFlagPropertyName(String categoryName, String flagName) { - String propertyName = SYSTEM_PROPERTY_PREFIX + "aconfig_flags." + - categoryName + "." + flagName; - - if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX) - || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) { - return null; - } - - return propertyName; - } - private void setProperty(String key, String value) { // Check if need to clear the property if (value == null) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 5ecac2253b49..2e229ca9d10f 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -55,7 +55,6 @@ import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS; import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE; -import static android.app.AppOpsManager.UID_STATE_NONEXISTENT; import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES; import static android.app.AppOpsManager._NUM_OP; import static android.app.AppOpsManager.extractFlagsFromKey; @@ -464,7 +463,19 @@ public class AppOpsService extends IAppOpsService.Stub { Clock.SYSTEM_CLOCK, mConstants); mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler), - this::onUidStateChanged); + new AppOpsUidStateTracker.UidStateChangedCallback() { + @Override + public void onUidStateChanged(int uid, int uidState, + boolean foregroundModeMayChange) { + AppOpsService.this + .onUidStateChanged(uid, uidState, foregroundModeMayChange); + } + + @Override + public void onUidProcessDeath(int uid) { + AppOpsService.this.onUidProcessDeath(uid); + } + }); } return mUidStateTracker; } @@ -1500,9 +1511,6 @@ public class AppOpsService extends IAppOpsService.Stub { // The callback method from AppOpsUidStateTracker private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) { synchronized (this) { - if (state == UID_STATE_NONEXISTENT) { - onUidProcessDeathLocked(uid); - } UidState uidState = getUidStateLocked(uid, false); boolean hasForegroundWatchers = false; @@ -1590,11 +1598,6 @@ public class AppOpsService extends IAppOpsService.Stub { } } - if (state == UID_STATE_NONEXISTENT) { - // For UID_STATE_NONEXISTENT, we don't call onUidStateChanged for AttributedOps - return; - } - if (uidState != null) { int numPkgs = uidState.pkgOps.size(); for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { @@ -1619,31 +1622,32 @@ public class AppOpsService extends IAppOpsService.Stub { } } - @GuardedBy("this") - private void onUidProcessDeathLocked(int uid) { - if (!mUidStates.contains(uid) || !Flags.finishRunningOpsForKilledPackages()) { - return; - } - final SparseLongArray chainsToFinish = new SparseLongArray(); - doForAllAttributedOpsInUidLocked(uid, (attributedOp) -> { - attributedOp.doForAllInProgressStartOpEvents((event) -> { - if (event == null) { - return; - } - int chainId = event.getAttributionChainId(); - if (chainId != ATTRIBUTION_CHAIN_ID_NONE) { - long currentEarliestStartTime = - chainsToFinish.get(chainId, Long.MAX_VALUE); - if (event.getStartTime() < currentEarliestStartTime) { - // Store the earliest chain link we're finishing, so that we can go back - // and finish any links in the chain that started after this one - chainsToFinish.put(chainId, event.getStartTime()); + private void onUidProcessDeath(int uid) { + synchronized (this) { + if (!mUidStates.contains(uid) || !Flags.finishRunningOpsForKilledPackages()) { + return; + } + final SparseLongArray chainsToFinish = new SparseLongArray(); + doForAllAttributedOpsInUidLocked(uid, (attributedOp) -> { + attributedOp.doForAllInProgressStartOpEvents((event) -> { + if (event == null) { + return; } - } - attributedOp.finished(event.getClientId()); + int chainId = event.getAttributionChainId(); + if (chainId != ATTRIBUTION_CHAIN_ID_NONE) { + long currentEarliestStartTime = + chainsToFinish.get(chainId, Long.MAX_VALUE); + if (event.getStartTime() < currentEarliestStartTime) { + // Store the earliest chain link we're finishing, so that we can go back + // and finish any links in the chain that started after this one + chainsToFinish.put(chainId, event.getStartTime()); + } + } + attributedOp.finished(event.getClientId()); + }); }); - }); - finishChainsLocked(chainsToFinish); + finishChainsLocked(chainsToFinish); + } } @GuardedBy("this") diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java index 268b286d8fe1..9bd72990f7b7 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java @@ -19,6 +19,7 @@ package com.android.server.appop; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; import static android.app.ActivityManager.PROCESS_STATE_TOP; @@ -27,8 +28,10 @@ import static android.app.AppOpsManager.UID_STATE_BACKGROUND; import static android.app.AppOpsManager.UID_STATE_CACHED; import static android.app.AppOpsManager.UID_STATE_FOREGROUND; import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; +import static android.app.AppOpsManager.UID_STATE_NONEXISTENT; import static android.app.AppOpsManager.UID_STATE_PERSISTENT; import static android.app.AppOpsManager.UID_STATE_TOP; +import static android.permission.flags.Flags.finishRunningOpsForKilledPackages; import android.annotation.CallbackExecutor; import android.util.SparseArray; @@ -68,6 +71,14 @@ interface AppOpsUidStateTracker { return UID_STATE_BACKGROUND; } + if (finishRunningOpsForKilledPackages()) { + if (procState < PROCESS_STATE_NONEXISTENT) { + return UID_STATE_CACHED; + } + + return UID_STATE_NONEXISTENT; + } + // UID_STATE_NONEXISTENT is deliberately excluded here return UID_STATE_CACHED; } @@ -119,6 +130,8 @@ interface AppOpsUidStateTracker { * evaluated result may have changed. */ void onUidStateChanged(int uid, int uidState, boolean foregroundModeMayChange); + + void onUidProcessDeath(int uid); } void dumpUidState(PrintWriter pw, int uid, long nowElapsed); diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java index 6f8c241a86ae..1a1077ad0e7b 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java @@ -21,7 +21,6 @@ import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; -import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManager.ProcessCapability; import static android.app.AppOpsManager.MIN_PRIORITY_UID_STATE; import static android.app.AppOpsManager.MODE_ALLOWED; @@ -32,6 +31,7 @@ import static android.app.AppOpsManager.OP_CONTROL_AUDIO; import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.UID_STATE_CACHED; import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; import static android.app.AppOpsManager.UID_STATE_NONEXISTENT; @@ -75,7 +75,6 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { private SparseBooleanArray mAppWidgetVisible = new SparseBooleanArray(); private SparseBooleanArray mPendingAppWidgetVisible = new SparseBooleanArray(); private SparseLongArray mPendingCommitTime = new SparseLongArray(); - private SparseBooleanArray mPendingGone = new SparseBooleanArray(); private ArrayMap<UidStateChangedCallback, Executor> mUidStateChangedCallbacks = new ArrayMap<>(); @@ -221,11 +220,12 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { public void updateUidProcState(int uid, int procState, int capability) { int uidState = processStateToUidState(procState); - int prevUidState = mUidStates.get(uid, AppOpsManager.MIN_PRIORITY_UID_STATE); + int prevUidState = mUidStates.get(uid, AppOpsManager.UID_STATE_NONEXISTENT); int prevCapability = mCapability.get(uid, PROCESS_CAPABILITY_NONE); - int pendingUidState = mPendingUidStates.get(uid, MIN_PRIORITY_UID_STATE); + int pendingUidState = mPendingUidStates.get(uid, UID_STATE_NONEXISTENT); int pendingCapability = mPendingCapability.get(uid, PROCESS_CAPABILITY_NONE); long pendingStateCommitTime = mPendingCommitTime.get(uid, 0); + if ((pendingStateCommitTime == 0 && (uidState != prevUidState || capability != prevCapability)) || (pendingStateCommitTime != 0 @@ -239,8 +239,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { boolean hasLostCapability = (prevCapability & ~capability) != 0; - if (procState == PROCESS_STATE_NONEXISTENT) { - mPendingGone.put(uid, true); + if (uidState == UID_STATE_NONEXISTENT) { commitUidPendingState(uid); } else if (uidState < prevUidState) { // We are moving to a more important state, or the new state may be in the @@ -342,7 +341,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { private void commitUidPendingState(int uid) { - int uidState = mUidStates.get(uid, MIN_PRIORITY_UID_STATE); + int uidState = mUidStates.get(uid, UID_STATE_NONEXISTENT); int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE); boolean appWidgetVisible = mAppWidgetVisible.get(uid, false); @@ -350,18 +349,23 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { int pendingCapability = mPendingCapability.get(uid, capability); boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid, appWidgetVisible); - boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED - != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + // UID_STATE_NONEXISTENT is a state that isn't used outside of this class, nonexistent + // processes have always been represented as CACHED + int externalUidState = Math.min(uidState, UID_STATE_CACHED); + int externalPendingUidState = Math.min(pendingUidState, UID_STATE_CACHED); + + boolean foregroundChange = externalUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + != externalPendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED || capability != pendingCapability || appWidgetVisible != pendingAppWidgetVisible; - if (uidState != pendingUidState + if (externalUidState != externalPendingUidState || capability != pendingCapability || appWidgetVisible != pendingAppWidgetVisible) { if (foregroundChange) { // To save on memory usage, log only interesting changes. - mEventLog.logCommitUidState(uid, pendingUidState, pendingCapability, + mEventLog.logCommitUidState(uid, externalPendingUidState, pendingCapability, pendingAppWidgetVisible, appWidgetVisible != pendingAppWidgetVisible); } @@ -370,24 +374,23 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { Executor executor = mUidStateChangedCallbacks.valueAt(i); executor.execute(PooledLambda.obtainRunnable( - UidStateChangedCallback::onUidStateChanged, cb, uid, pendingUidState, - foregroundChange)); + UidStateChangedCallback::onUidStateChanged, cb, uid, + externalPendingUidState, foregroundChange)); } } - if (mPendingGone.get(uid, false)) { + if (pendingUidState == UID_STATE_NONEXISTENT && uidState != pendingUidState) { mUidStates.delete(uid); mCapability.delete(uid); mAppWidgetVisible.delete(uid); - mPendingGone.delete(uid); if (finishRunningOpsForKilledPackages()) { for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) { UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i); Executor executor = mUidStateChangedCallbacks.valueAt(i); + // If foregroundness changed it should be handled in earlier callback invocation executor.execute(PooledLambda.obtainRunnable( - UidStateChangedCallback::onUidStateChanged, cb, uid, - UID_STATE_NONEXISTENT, foregroundChange)); + UidStateChangedCallback::onUidProcessDeath, cb, uid)); } } } else { diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java index b4df1f76dccb..569a426b80d5 100644 --- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java @@ -111,13 +111,14 @@ class DisplayTopologyCoordinator { * @param info The display info */ void onDisplayAdded(DisplayInfo info) { - if (!isDisplayAllowedInTopology(info)) { + if (!isDisplayAllowedInTopology(info, /* shouldLog= */ true)) { return; } synchronized (mSyncRoot) { addDisplayIdMappingLocked(info); mDensities.put(info.displayId, info.logicalDensityDpi); mTopology.addDisplay(info.displayId, getWidth(info), getHeight(info)); + Slog.i(TAG, "Display " + info.displayId + " added, new topology: " + mTopology); restoreTopologyLocked(); sendTopologyUpdateLocked(); } @@ -128,7 +129,7 @@ class DisplayTopologyCoordinator { * @param info The new display info */ void onDisplayChanged(DisplayInfo info) { - if (!isDisplayAllowedInTopology(info)) { + if (!isDisplayAllowedInTopology(info, /* shouldLog= */ false)) { return; } synchronized (mSyncRoot) { @@ -149,6 +150,7 @@ class DisplayTopologyCoordinator { synchronized (mSyncRoot) { mDensities.delete(displayId); if (mTopology.removeDisplay(displayId)) { + Slog.i(TAG, "Display " + displayId + " removed, new topology: " + mTopology); removeDisplayIdMappingLocked(displayId); restoreTopologyLocked(); sendTopologyUpdateLocked(); @@ -249,22 +251,28 @@ class DisplayTopologyCoordinator { return pxToDp(info.logicalHeight, info.logicalDensityDpi); } - private boolean isDisplayAllowedInTopology(DisplayInfo info) { + private boolean isDisplayAllowedInTopology(DisplayInfo info, boolean shouldLog) { if (info.type != Display.TYPE_INTERNAL && info.type != Display.TYPE_EXTERNAL && info.type != Display.TYPE_OVERLAY) { - Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because " - + "type is not INTERNAL, EXTERNAL or OVERLAY"); + if (shouldLog) { + Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because " + + "type is not INTERNAL, EXTERNAL or OVERLAY"); + } return false; } if (info.type == Display.TYPE_INTERNAL && info.displayId != Display.DEFAULT_DISPLAY) { - Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because " - + "it is a non-default internal display"); + if (shouldLog) { + Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because " + + "it is a non-default internal display"); + } return false; } if ((info.type == Display.TYPE_EXTERNAL || info.type == Display.TYPE_OVERLAY) && !mIsExtendedDisplayAllowed.getAsBoolean()) { - Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because " - + "type is EXTERNAL or OVERLAY and !mIsExtendedDisplayAllowed"); + if (shouldLog) { + Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because " + + "type is EXTERNAL or OVERLAY and !mIsExtendedDisplayAllowed"); + } return false; } return true; diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index 60b7fca99e7b..228e6f1c4ddb 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -130,6 +130,14 @@ final class OverlayDisplayAdapter extends DisplayAdapter { private static final String OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE = "fixed_content_mode"; + /** + * When this flag is set, disables support for moving and resizing the overlay window. + * As the window is made non-touchable, this also makes it possible to directly interact with + * the content underneath. + */ + private static final String OVERLAY_DISPLAY_FLAG_DISABLE_WINDOW_INTERACTION = + "disable_window_interaction"; + // Gravity flags to decide where the overlay should be shown. private static final String GRAVITY_TOP_LEFT = "gravity_top_left"; private static final String GRAVITY_BOTTOM_RIGHT = "gravity_bottom_right"; @@ -571,9 +579,9 @@ final class OverlayDisplayAdapter extends DisplayAdapter { @Override public void run() { OverlayMode mode = mModes.get(mActiveMode); - OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(), - mName, mode.mWidth, mode.mHeight, mode.mDensityDpi, mGravity, - mFlags.mSecure, OverlayDisplayHandle.this); + OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(), mName, + mode.mWidth, mode.mHeight, mode.mDensityDpi, mGravity, mFlags.mSecure, + mFlags.mDisableWindowInteraction, OverlayDisplayHandle.this); window.show(); synchronized (getSyncRoot()) { @@ -655,6 +663,9 @@ final class OverlayDisplayAdapter extends DisplayAdapter { /** See {@link #OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE}. */ final boolean mFixedContentMode; + /** See {@link #OVERLAY_DISPLAY_FLAG_DISABLE_WINDOW_INTERACTION}. */ + final boolean mDisableWindowInteraction; + final int mGravity; OverlayFlags( @@ -662,11 +673,13 @@ final class OverlayDisplayAdapter extends DisplayAdapter { boolean ownContentOnly, boolean shouldShowSystemDecorations, boolean fixedContentMode, + boolean disableWindowInteraction, int gravity) { mSecure = secure; mOwnContentOnly = ownContentOnly; mShouldShowSystemDecorations = shouldShowSystemDecorations; mFixedContentMode = fixedContentMode; + mDisableWindowInteraction = disableWindowInteraction; mGravity = gravity; } @@ -677,6 +690,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { false /* ownContentOnly */, false /* shouldShowSystemDecorations */, false /* fixedContentMode */, + false /* disableWindowInteraction */, Gravity.NO_GRAVITY); } @@ -684,6 +698,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { boolean ownContentOnly = false; boolean shouldShowSystemDecorations = false; boolean fixedContentMode = false; + boolean disableWindowInteraction = false; int gravity = Gravity.NO_GRAVITY; for (String flag: flagString.split(FLAG_SPLITTER)) { if (OVERLAY_DISPLAY_FLAG_SECURE.equals(flag)) { @@ -694,12 +709,14 @@ final class OverlayDisplayAdapter extends DisplayAdapter { shouldShowSystemDecorations = true; } else if (OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE.equals(flag)) { fixedContentMode = true; + } else if (OVERLAY_DISPLAY_FLAG_DISABLE_WINDOW_INTERACTION.equals(flag)) { + disableWindowInteraction = true; } else { gravity = parseOverlayGravity(flag); } } return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations, - fixedContentMode, gravity); + fixedContentMode, disableWindowInteraction, gravity); } @Override @@ -709,6 +726,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { .append(", ownContentOnly=").append(mOwnContentOnly) .append(", shouldShowSystemDecorations=").append(mShouldShowSystemDecorations) .append(", fixedContentMode=").append(mFixedContentMode) + .append(", disableWindowInteraction=").append(mDisableWindowInteraction) .append(", gravity").append(Gravity.toString(mGravity)) .append("}") .toString(); diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java index 3fd58e8641c3..523bbfa7d69a 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java +++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java @@ -69,6 +69,7 @@ final class OverlayDisplayWindow implements DumpUtils.Dump { private int mDensityDpi; private final int mGravity; private final boolean mSecure; + private final boolean mDisableWindowInteraction; private final Listener mListener; private String mTitle; @@ -96,15 +97,15 @@ final class OverlayDisplayWindow implements DumpUtils.Dump { private float mLiveTranslationY; private float mLiveScale = 1.0f; - public OverlayDisplayWindow(Context context, String name, - int width, int height, int densityDpi, int gravity, boolean secure, - Listener listener) { + OverlayDisplayWindow(Context context, String name, int width, int height, int densityDpi, + int gravity, boolean secure, boolean disableWindowInteraction, Listener listener) { // Workaround device freeze (b/38372997) ThreadedRenderer.disableVsync(); mContext = context; mName = name; mGravity = gravity; mSecure = secure; + mDisableWindowInteraction = disableWindowInteraction; mListener = listener; mDisplayManager = (DisplayManager)context.getSystemService( @@ -226,8 +227,10 @@ final class OverlayDisplayWindow implements DumpUtils.Dump { if (mSecure) { mWindowParams.flags |= WindowManager.LayoutParams.FLAG_SECURE; } - if (DISABLE_MOVE_AND_RESIZE) { + if (DISABLE_MOVE_AND_RESIZE || mDisableWindowInteraction) { mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + mWindowParams.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; } mWindowParams.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED; diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index 52ddb800fa40..695bf612ccc3 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -82,4 +82,11 @@ public interface NotificationManagerInternal { byte[] getBackupPayload(int user, BackupRestoreEventLogger logger); void applyRestore(byte[] payload, int user, BackupRestoreEventLogger logger); + + /** + * Notifies NotificationManager that the system decorations should be removed from the display. + * + * @param displayId display ID + */ + void onDisplayRemoveSystemDecorations(int displayId); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 06fc9b083086..6ce1746ed3f6 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -8214,6 +8214,18 @@ public class NotificationManagerService extends SystemService { // This can also throw IllegalStateException if called too late. mZenModeHelper.setDeviceEffectsApplier(applier); } + + @Override + public void onDisplayRemoveSystemDecorations(int displayId) { + synchronized (mToastQueue) { + for (int i = mToastQueue.size() - 1; i >= 0; i--) { + final ToastRecord toast = mToastQueue.get(i); + if (toast.displayId == displayId) { + cancelToastLocked(i); + } + } + } + } }; private static boolean isBigPictureWithBitmapOrIcon(Notification n) { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 2dd679818ada..5160319c8cf6 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -379,9 +379,10 @@ public class LauncherAppsService extends SystemService { public List<UserHandle> getUserProfiles() { int[] userIds; if (!canAccessHiddenProfile(getCallingUid(), getCallingPid())) { - userIds = mUm.getProfileIdsExcludingHidden(getCallingUserId(), /* enabled= */ true); + userIds = mUserManagerInternal.getProfileIdsExcludingHidden(getCallingUserId(), + /* enabled= */ true); } else { - userIds = mUm.getEnabledProfileIds(getCallingUserId()); + userIds = mUserManagerInternal.getProfileIds(getCallingUserId(), true); } final List<UserHandle> result = new ArrayList<>(userIds.length); for (int userId : userIds) { @@ -398,9 +399,10 @@ public class LauncherAppsService extends SystemService { int[] userIds; if (!canAccessHiddenProfile(callingUid, Binder.getCallingPid())) { - userIds = mUm.getProfileIdsExcludingHidden(getCallingUserId(), /* enabled= */ true); + userIds = mUserManagerInternal.getProfileIdsExcludingHidden(getCallingUserId(), + /* enabled= */ true); } else { - userIds = mUm.getEnabledProfileIds(getCallingUserId()); + userIds = mUserManagerInternal.getProfileIds(getCallingUserId(), true); } final long token = Binder.clearCallingIdentity(); @@ -503,16 +505,11 @@ public class LauncherAppsService extends SystemService { return true; } - long ident = injectClearCallingIdentity(); - try { - final UserInfo callingUserInfo = mUm.getUserInfo(callingUserId); - if (callingUserInfo != null && callingUserInfo.isProfile()) { - Slog.w(TAG, message + " for another profile " - + targetUserId + " from " + callingUserId + " not allowed"); - return false; - } - } finally { - injectRestoreCallingIdentity(ident); + final UserInfo callingUserInfo = mUserManagerInternal.getUserInfo(callingUserId); + if (callingUserInfo != null && callingUserInfo.isProfile()) { + Slog.w(TAG, message + " for another profile " + + targetUserId + " from " + callingUserId + " not allowed"); + return false; } if (isHiddenProfile(UserHandle.of(targetUserId)) @@ -529,9 +526,9 @@ public class LauncherAppsService extends SystemService { return false; } - long identity = injectClearCallingIdentity(); try { - UserProperties properties = mUm.getUserProperties(targetUser); + UserProperties properties = mUserManagerInternal + .getUserProperties(targetUser.getIdentifier()); if (properties == null) { return false; } @@ -540,8 +537,6 @@ public class LauncherAppsService extends SystemService { == UserProperties.PROFILE_API_VISIBILITY_HIDDEN; } catch (IllegalArgumentException e) { return false; - } finally { - injectRestoreCallingIdentity(identity); } } @@ -686,7 +681,7 @@ public class LauncherAppsService extends SystemService { final int callingUid = injectBinderCallingUid(); final long ident = injectClearCallingIdentity(); try { - if (mUm.getUserInfo(user.getIdentifier()).isManagedProfile()) { + if (mUserManagerInternal.getUserInfo(user.getIdentifier()).isManagedProfile()) { // Managed profile should not show hidden apps return launcherActivities; } @@ -1713,7 +1708,7 @@ public class LauncherAppsService extends SystemService { } final long identity = Binder.clearCallingIdentity(); try { - String userType = mUm.getUserInfo(user.getIdentifier()).userType; + String userType = mUserManagerInternal.getUserInfo(user.getIdentifier()).userType; Set<String> preInstalledPackages = mUm.getPreInstallableSystemPackages(userType); if (preInstalledPackages == null) { return new ArrayList<>(); diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index e98176b0e82b..41ce4fa81668 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -368,6 +368,21 @@ public abstract class UserManagerInternal { public abstract @NonNull int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly); /** + * Returns a list of the users that are associated with the specified user, including the user + * itself. This includes the user, its profiles, its parent, and its parent's other profiles, + * as applicable. + * + * <p>Note that this includes only profile types that are not hidden. + * + * @param userId id of the user to return profiles for + * @param enabledOnly whether return only {@link UserInfo#isEnabled() enabled} profiles + * @return A non-empty array of ids of profiles associated with the specified user if the user + * exists. Otherwise, an empty array. + */ + public abstract @NonNull int[] getProfileIdsExcludingHidden(@UserIdInt int userId, + boolean enabledOnly); + + /** * Checks if the {@code callingUserId} and {@code targetUserId} are same or in same group * and that the {@code callingUserId} is not a profile and {@code targetUserId} is enabled. * diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 053b4aae90dd..0ea9af4b9c38 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -8049,6 +8049,14 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public int[] getProfileIdsExcludingHidden(@UserIdInt int userId, boolean enabledOnly) { + synchronized (mUsersLock) { + return getProfileIdsLU(userId, null /* userType */, enabledOnly, /* excludeHidden */ + true).toArray(); + } + } + + @Override public @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId) { UserInfo userInfo; synchronized (mUsersLock) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index e9f522d08328..8cf0481b1dc3 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4633,6 +4633,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { @SuppressLint("MissingPermission") private void injectBackGesture(long downtime) { + if (mActivityTaskManagerInternal.requestBackGesture()) { + return; + } // Create and inject down event KeyEvent downEvent = new KeyEvent(downtime, downtime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */, diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 3eac4b54cd2b..bef505867a14 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -130,12 +130,12 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; import com.android.server.LockGuard; +import com.android.server.PackageWatchdog; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.UiThread; import com.android.server.Watchdog; import com.android.server.am.BatteryStatsService; -import com.android.server.crashrecovery.CrashRecoveryHelper; import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; @@ -4113,7 +4113,7 @@ public final class PowerManagerService extends SystemService } } if (mHandler == null || !mSystemReady) { - if (CrashRecoveryHelper.isRecoveryTriggeredReboot()) { + if (PackageWatchdog.isRecoveryTriggeredReboot()) { // If we're stuck in a really low-level reboot loop, and a // rescue party is trying to prompt the user for a factory data // reset, we must GET TO DA CHOPPA! diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index d209ea90f3ca..4ae1b4e119fa 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -59,7 +59,7 @@ import android.view.SurfaceControl; import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.crashrecovery.CrashRecoveryHelper; +import com.android.server.PackageWatchdog; import com.android.server.LocalServices; import com.android.server.statusbar.StatusBarManagerInternal; @@ -339,7 +339,7 @@ public final class ShutdownThread extends Thread { com.android.internal.R.string.reboot_to_update_reboot)); } } else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) { - if (CrashRecoveryHelper.isRecoveryTriggeredReboot()) { + if (PackageWatchdog.isRecoveryTriggeredReboot()) { // We're not actually doing a factory reset yet; we're rebooting // to ask the user if they'd like to reset, so give them a less // scary dialog message. diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java index 5ee9b7d09fdd..46c497d04f9e 100644 --- a/services/core/java/com/android/server/power/ThermalManagerService.java +++ b/services/core/java/com/android/server/power/ThermalManagerService.java @@ -176,7 +176,9 @@ public class ThermalManagerService extends SystemService { try { final HeadroomCallbackData data; synchronized (mTemperatureWatcher.mSamples) { - Slog.d(TAG, "Updating skin threshold: " + threshold); + if (DEBUG) { + Slog.d(TAG, "Updating skin threshold: " + threshold); + } mTemperatureWatcher.updateTemperatureThresholdLocked(threshold, true); data = mTemperatureWatcher.getHeadroomCallbackDataLocked(); } @@ -454,7 +456,9 @@ public class ThermalManagerService extends SystemService { && temperature.getType() == Temperature.TYPE_SKIN) { final HeadroomCallbackData data; synchronized (mTemperatureWatcher.mSamples) { - Slog.d(TAG, "Updating new temperature: " + temperature); + if (DEBUG) { + Slog.d(TAG, "Updating new temperature: " + temperature); + } mTemperatureWatcher.updateTemperatureSampleLocked(System.currentTimeMillis(), temperature); mTemperatureWatcher.mCachedHeadrooms.clear(); @@ -1878,6 +1882,7 @@ public class ThermalManagerService extends SystemService { @VisibleForTesting long mInactivityThresholdMillis = INACTIVITY_THRESHOLD_MILLIS; + @GuardedBy("mSamples") private final Handler mHandler = BackgroundThread.getHandler(); /** @@ -1900,6 +1905,9 @@ public class ThermalManagerService extends SystemService { @GuardedBy("mSamples") private long mLastForecastCallTimeMillis = 0; + private final Runnable mGetAndUpdateTemperatureSamplesRunnable = + this::getAndUpdateTemperatureSamples; + void getAndUpdateThresholds() { List<TemperatureThreshold> thresholds = mHalWrapper.getTemperatureThresholds(true, Temperature.TYPE_SKIN); @@ -1930,7 +1938,9 @@ public class ThermalManagerService extends SystemService { return; } if (override) { - Slog.d(TAG, "Headroom cache cleared on threshold update " + threshold); + if (DEBUG) { + Slog.d(TAG, "Headroom cache cleared on threshold update " + threshold); + } mCachedHeadrooms.clear(); Arrays.fill(mHeadroomThresholds, Float.NaN); } @@ -1962,7 +1972,7 @@ public class ThermalManagerService extends SystemService { < mInactivityThresholdMillis) { // Trigger this again after a second as long as forecast has been called more // recently than the inactivity timeout - mHandler.postDelayed(this::getAndUpdateTemperatureSamples, 1000); + mHandler.postDelayed(mGetAndUpdateTemperatureSamplesRunnable, 1000); } else { // Otherwise, we've been idle for at least 10 seconds, so we should // shut down @@ -1974,6 +1984,9 @@ public class ThermalManagerService extends SystemService { long now = SystemClock.elapsedRealtime(); final List<Temperature> temperatures = mHalWrapper.getCurrentTemperatures(true, Temperature.TYPE_SKIN); + if (DEBUG) { + Slog.d(TAG, "Thermal HAL getCurrentTemperatures result: " + temperatures); + } for (Temperature temperature : temperatures) { updateTemperatureSampleLocked(now, temperature); } @@ -2080,10 +2093,16 @@ public class ThermalManagerService extends SystemService { } synchronized (mSamples) { mLastForecastCallTimeMillis = SystemClock.elapsedRealtime(); - if (mSamples.isEmpty()) { + if (!mHandler.hasCallbacks(mGetAndUpdateTemperatureSamplesRunnable)) { + if (DEBUG) { + Slog.d(TAG, "No temperature update callback, scheduling one"); + } getAndUpdateTemperatureSamples(); + } else { + if (DEBUG) { + Slog.d(TAG, "Temperature update callback already exists"); + } } - // If somehow things take much longer than expected or there are no temperatures // to sample, return early if (mSamples.isEmpty()) { @@ -2103,8 +2122,11 @@ public class ThermalManagerService extends SystemService { Binder.getCallingUid(), FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__SUCCESS, headroom, forecastSeconds); - Slog.d(TAG, "Headroom forecast in " + forecastSeconds + "s served from cache: " - + headroom); + if (DEBUG) { + Slog.d(TAG, + "Headroom forecast in " + forecastSeconds + "s served from cache: " + + headroom); + } return headroom; } @@ -2133,7 +2155,10 @@ public class ThermalManagerService extends SystemService { Binder.getCallingUid(), FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__SUCCESS, headroom, 0); - Slog.d(TAG, "Headroom forecast in 0s served from cache: " + headroom); + if (DEBUG) { + Slog.d(TAG, + "Headroom forecast in 0s served from cache: " + headroom); + } return headroom; } // Don't try to forecast, just use the latest one we have @@ -2182,7 +2207,9 @@ public class ThermalManagerService extends SystemService { getForecast(DEFAULT_FORECAST_SECONDS), DEFAULT_FORECAST_SECONDS, Arrays.copyOf(mHeadroomThresholds, mHeadroomThresholds.length)); - Slog.d(TAG, "New headroom callback data: " + data); + if (DEBUG) { + Slog.d(TAG, "New headroom callback data: " + data); + } return data; } diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java index f90da644c0ce..a9896e96a08f 100644 --- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java @@ -272,15 +272,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat mHandler.removeMessages(SYNC_WAKELOCK_CHANGE); } - @Override - public void scheduleSyncDueToBatteryLevelChange(long delayMillis) { - synchronized (BatteryExternalStatsWorker.this) { - scheduleDelayedSyncLocked(SYNC_BATTERY_LEVEL_CHANGE, - () -> scheduleSync("battery-level", UPDATE_ALL), - delayMillis); - } - } - @GuardedBy("this") private void cancelSyncDueToBatteryLevelChangeLocked() { mHandler.removeMessages(SYNC_BATTERY_LEVEL_CHANGE); diff --git a/services/core/java/com/android/server/power/stats/BatteryHistoryStepDetailsProvider.java b/services/core/java/com/android/server/power/stats/BatteryHistoryStepDetailsProvider.java new file mode 100644 index 000000000000..cd7612523b3e --- /dev/null +++ b/services/core/java/com/android/server/power/stats/BatteryHistoryStepDetailsProvider.java @@ -0,0 +1,276 @@ +/* + * 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.server.power.stats; + +import android.hardware.power.stats.PowerEntity; +import android.hardware.power.stats.State; +import android.hardware.power.stats.StateResidency; +import android.hardware.power.stats.StateResidencyResult; +import android.os.BatteryStats; +import android.power.PowerStatsInternal; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.server.LocalServices; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +class BatteryHistoryStepDetailsProvider { + public static final String TAG = "BatteryHistoryStepDetails"; + private static final boolean DEBUG = false; + + private static final int POWER_STATS_QUERY_TIMEOUT_MILLIS = 2000; + private static final int MAX_LOW_POWER_STATS_SIZE = 32768; + + private final BatteryStatsImpl mBatteryStats; + + private final BatteryStats.HistoryStepDetails mDetails = new BatteryStats.HistoryStepDetails(); + + private boolean mHasHistoryStepDetails; + + /** + * Total time (in milliseconds) spent executing in user code. + */ + private long mLastStepCpuUserTimeMs; + private long mCurStepCpuUserTimeMs; + /** + * Total time (in milliseconds) spent executing in kernel code. + */ + private long mLastStepCpuSystemTimeMs; + private long mCurStepCpuSystemTimeMs; + /** + * Times from /proc/stat (but measured in milliseconds). + */ + private long mLastStepStatUserTimeMs; + private long mLastStepStatSystemTimeMs; + private long mLastStepStatIOWaitTimeMs; + private long mLastStepStatIrqTimeMs; + private long mLastStepStatSoftIrqTimeMs; + private long mLastStepStatIdleTimeMs; + private long mCurStepStatUserTimeMs; + private long mCurStepStatSystemTimeMs; + private long mCurStepStatIOWaitTimeMs; + private long mCurStepStatIrqTimeMs; + private long mCurStepStatSoftIrqTimeMs; + private long mCurStepStatIdleTimeMs; + + private PowerStatsInternal mPowerStatsInternal; + private final Map<Integer, String> mEntityNames = new HashMap<>(); + private final Map<Integer, Map<Integer, String>> mStateNames = new HashMap<>(); + + BatteryHistoryStepDetailsProvider(BatteryStatsImpl batteryStats) { + mBatteryStats = batteryStats; + } + + void onSystemReady() { + mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class); + if (mPowerStatsInternal != null) { + populatePowerEntityMaps(); + } + } + + void requestUpdate() { + mBatteryStats.mHandler.post(this::update); + } + + void update() { + mHasHistoryStepDetails = false; + mBatteryStats.updateCpuDetails(); + calculateHistoryStepDetails(); + updateStateResidency(); + mBatteryStats.getHistory().recordHistoryStepDetails(mDetails, + mBatteryStats.mClock.elapsedRealtime(), + mBatteryStats.mClock.uptimeMillis()); + } + + private void calculateHistoryStepDetails() { + if (!mHasHistoryStepDetails) { + return; + } + + if (DEBUG) { + Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTimeMs + " sys=" + + mLastStepStatSystemTimeMs + " io=" + mLastStepStatIOWaitTimeMs + + " irq=" + mLastStepStatIrqTimeMs + " sirq=" + + mLastStepStatSoftIrqTimeMs + " idle=" + mLastStepStatIdleTimeMs); + Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTimeMs + " sys=" + + mCurStepStatSystemTimeMs + " io=" + mCurStepStatIOWaitTimeMs + + " irq=" + mCurStepStatIrqTimeMs + " sirq=" + + mCurStepStatSoftIrqTimeMs + " idle=" + mCurStepStatIdleTimeMs); + } + mDetails.userTime = (int) (mCurStepCpuUserTimeMs - mLastStepCpuUserTimeMs); + mDetails.systemTime = (int) (mCurStepCpuSystemTimeMs - mLastStepCpuSystemTimeMs); + mDetails.statUserTime = (int) (mCurStepStatUserTimeMs - mLastStepStatUserTimeMs); + mDetails.statSystemTime = + (int) (mCurStepStatSystemTimeMs - mLastStepStatSystemTimeMs); + mDetails.statIOWaitTime = + (int) (mCurStepStatIOWaitTimeMs - mLastStepStatIOWaitTimeMs); + mDetails.statIrqTime = (int) (mCurStepStatIrqTimeMs - mLastStepStatIrqTimeMs); + mDetails.statSoftIrqTime = + (int) (mCurStepStatSoftIrqTimeMs - mLastStepStatSoftIrqTimeMs); + mDetails.statIdlTime = (int) (mCurStepStatIdleTimeMs - mLastStepStatIdleTimeMs); + mDetails.appCpuUid1 = mDetails.appCpuUid2 = mDetails.appCpuUid3 = -1; + mDetails.appCpuUTime1 = mDetails.appCpuUTime2 = mDetails.appCpuUTime3 = 0; + mDetails.appCpuSTime1 = mDetails.appCpuSTime2 = mDetails.appCpuSTime3 = 0; + SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats(); + final int uidCount = uidStats.size(); + for (int i = 0; i < uidCount; i++) { + final BatteryStatsImpl.Uid uid = (BatteryStatsImpl.Uid) uidStats.valueAt(i); + final int totalUTimeMs = + (int) (uid.mCurStepUserTimeMs - uid.mLastStepUserTimeMs); + final int totalSTimeMs = + (int) (uid.mCurStepSystemTimeMs - uid.mLastStepSystemTimeMs); + final int totalTimeMs = totalUTimeMs + totalSTimeMs; + uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs; + uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs; + if (totalTimeMs <= (mDetails.appCpuUTime3 + mDetails.appCpuSTime3)) { + continue; + } + if (totalTimeMs <= (mDetails.appCpuUTime2 + mDetails.appCpuSTime2)) { + mDetails.appCpuUid3 = uid.mUid; + mDetails.appCpuUTime3 = totalUTimeMs; + mDetails.appCpuSTime3 = totalSTimeMs; + } else { + mDetails.appCpuUid3 = mDetails.appCpuUid2; + mDetails.appCpuUTime3 = mDetails.appCpuUTime2; + mDetails.appCpuSTime3 = mDetails.appCpuSTime2; + if (totalTimeMs <= (mDetails.appCpuUTime1 + mDetails.appCpuSTime1)) { + mDetails.appCpuUid2 = uid.mUid; + mDetails.appCpuUTime2 = totalUTimeMs; + mDetails.appCpuSTime2 = totalSTimeMs; + } else { + mDetails.appCpuUid2 = mDetails.appCpuUid1; + mDetails.appCpuUTime2 = mDetails.appCpuUTime1; + mDetails.appCpuSTime2 = mDetails.appCpuSTime1; + mDetails.appCpuUid1 = uid.mUid; + mDetails.appCpuUTime1 = totalUTimeMs; + mDetails.appCpuSTime1 = totalSTimeMs; + } + } + } + mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs; + mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs; + mLastStepStatUserTimeMs = mCurStepStatUserTimeMs; + mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs; + mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs; + mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs; + mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs; + mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs; + } + + public void addCpuStats(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs, + int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs, + int statSoftIrqTimeMs, int statIdleTimeMs) { + if (DEBUG) { + Slog.d(TAG, "Adding cpu: tuser=" + totalUTimeMs + " tsys=" + totalSTimeMs + + " user=" + statUserTimeMs + " sys=" + statSystemTimeMs + + " io=" + statIOWaitTimeMs + " irq=" + statIrqTimeMs + + " sirq=" + statSoftIrqTimeMs + " idle=" + statIdleTimeMs); + } + mCurStepCpuUserTimeMs += totalUTimeMs; + mCurStepCpuSystemTimeMs += totalSTimeMs; + mCurStepStatUserTimeMs += statUserTimeMs; + mCurStepStatSystemTimeMs += statSystemTimeMs; + mCurStepStatIOWaitTimeMs += statIOWaitTimeMs; + mCurStepStatIrqTimeMs += statIrqTimeMs; + mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs; + mCurStepStatIdleTimeMs += statIdleTimeMs; + } + + public void finishAddingCpuLocked() { + mHasHistoryStepDetails = true; + } + + public void reset() { + mHasHistoryStepDetails = false; + mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs = 0; + mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs = 0; + mLastStepStatUserTimeMs = mCurStepStatUserTimeMs = 0; + mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs = 0; + mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs = 0; + mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs = 0; + mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs = 0; + mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs = 0; + } + + private void updateStateResidency() { + mDetails.statSubsystemPowerState = null; + + if (mPowerStatsInternal == null || mEntityNames.isEmpty() || mStateNames.isEmpty()) { + return; + } + + final StateResidencyResult[] results; + try { + results = mPowerStatsInternal.getStateResidencyAsync(new int[0]) + .get(POWER_STATS_QUERY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } catch (Exception e) { + Slog.e(TAG, "Failed to getStateResidencyAsync", e); + return; + } + + if (results == null || results.length == 0) { + return; + } + + StringBuilder builder = new StringBuilder("SubsystemPowerState"); + for (int i = 0; i < results.length; i++) { + final StateResidencyResult result = results[i]; + int length = builder.length(); + builder.append(" subsystem_").append(i); + builder.append(" name=").append(mEntityNames.get(result.id)); + + for (int j = 0; j < result.stateResidencyData.length; j++) { + final StateResidency stateResidency = result.stateResidencyData[j]; + builder.append(" state_").append(j); + builder.append(" name=").append(mStateNames.get(result.id).get( + stateResidency.id)); + builder.append(" time=").append(stateResidency.totalTimeInStateMs); + builder.append(" count=").append(stateResidency.totalStateEntryCount); + builder.append(" last entry=").append(stateResidency.lastEntryTimestampMs); + } + + if (builder.length() > MAX_LOW_POWER_STATS_SIZE) { + Slog.e(TAG, "updateStateResidency: buffer not enough"); + builder.setLength(length); + break; + } + } + + mDetails.statSubsystemPowerState = builder.toString(); + } + + private void populatePowerEntityMaps() { + PowerEntity[] entities = mPowerStatsInternal.getPowerEntityInfo(); + if (entities == null) { + return; + } + + for (final PowerEntity entity : entities) { + Map<Integer, String> states = new HashMap<>(); + for (int j = 0; j < entity.states.length; j++) { + final State state = entity.states[j]; + states.put(state.id, state.name); + } + + mEntityNames.put(entity.id, entity.name); + mStateNames.put(entity.id, states); + } + } +} diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 2cf6b7efcb48..0af50805d756 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -117,7 +117,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsHistory; -import com.android.internal.os.BatteryStatsHistory.HistoryStepDetailsCalculator; import com.android.internal.os.BatteryStatsHistoryIterator; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.BinderTransactionNameResolver; @@ -653,7 +652,6 @@ public class BatteryStatsImpl extends BatteryStats { public interface PlatformIdleStateCallback { public void fillLowPowerStats(RpmStats rpmStats); - public String getSubsystemLowPowerStats(); } /** interface to update rail information for power monitor */ @@ -1065,10 +1063,6 @@ public class BatteryStatsImpl extends BatteryStats { */ void cancelCpuSyncDueToWakelockChange(); - /** - * Schedules a sync caused by the battery level change - */ - void scheduleSyncDueToBatteryLevelChange(long delayMillis); /** Schedule removal of UIDs corresponding to a removed user */ void scheduleCleanupDueToRemovedUser(int userId); /** Schedule a sync because of a process state change */ @@ -1131,8 +1125,8 @@ public class BatteryStatsImpl extends BatteryStats { private boolean mShuttingDown; private final HistoryEventTracker mActiveEvents = new HistoryEventTracker(); - private final HistoryStepDetailsCalculatorImpl mStepDetailsCalculator = - new HistoryStepDetailsCalculatorImpl(); + private final BatteryHistoryStepDetailsProvider mStepDetailsProvider = + new BatteryHistoryStepDetailsProvider(this); private boolean mHaveBatteryLevel = false; private boolean mBatteryPluggedIn; @@ -4553,184 +4547,6 @@ public class BatteryStatsImpl extends BatteryStats { return kmt; } - private class HistoryStepDetailsCalculatorImpl implements HistoryStepDetailsCalculator { - private final HistoryStepDetails mDetails = new HistoryStepDetails(); - - private boolean mHasHistoryStepDetails; - private boolean mUpdateRequested; - - /** - * Total time (in milliseconds) spent executing in user code. - */ - private long mLastStepCpuUserTimeMs; - private long mCurStepCpuUserTimeMs; - /** - * Total time (in milliseconds) spent executing in kernel code. - */ - private long mLastStepCpuSystemTimeMs; - private long mCurStepCpuSystemTimeMs; - /** - * Times from /proc/stat (but measured in milliseconds). - */ - private long mLastStepStatUserTimeMs; - private long mLastStepStatSystemTimeMs; - private long mLastStepStatIOWaitTimeMs; - private long mLastStepStatIrqTimeMs; - private long mLastStepStatSoftIrqTimeMs; - private long mLastStepStatIdleTimeMs; - private long mCurStepStatUserTimeMs; - private long mCurStepStatSystemTimeMs; - private long mCurStepStatIOWaitTimeMs; - private long mCurStepStatIrqTimeMs; - private long mCurStepStatSoftIrqTimeMs; - private long mCurStepStatIdleTimeMs; - - @Override - public HistoryStepDetails getHistoryStepDetails() { - if (!mUpdateRequested) { - mUpdateRequested = true; - // Perform a CPU update right after we do this collection, so we have started - // collecting good data for the next step. - requestImmediateCpuUpdate(); - - if (mPlatformIdleStateCallback != null) { - mDetails.statSubsystemPowerState = - mPlatformIdleStateCallback.getSubsystemLowPowerStats(); - if (DEBUG) { - Slog.i(TAG, - "WRITE SubsystemPowerState:" + mDetails.statSubsystemPowerState); - } - } - } - - if (!mHasHistoryStepDetails) { - // We are not generating a delta, so all we need to do is reset the stats - // we will later be doing a delta from. - final int uidCount = mUidStats.size(); - for (int i = 0; i < uidCount; i++) { - final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i); - uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs; - uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs; - } - mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs; - mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs; - mLastStepStatUserTimeMs = mCurStepStatUserTimeMs; - mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs; - mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs; - mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs; - mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs; - mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs; - return null; - } else { - if (DEBUG) { - Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTimeMs + " sys=" - + mLastStepStatSystemTimeMs + " io=" + mLastStepStatIOWaitTimeMs - + " irq=" + mLastStepStatIrqTimeMs + " sirq=" - + mLastStepStatSoftIrqTimeMs + " idle=" + mLastStepStatIdleTimeMs); - Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTimeMs + " sys=" - + mCurStepStatSystemTimeMs + " io=" + mCurStepStatIOWaitTimeMs - + " irq=" + mCurStepStatIrqTimeMs + " sirq=" - + mCurStepStatSoftIrqTimeMs + " idle=" + mCurStepStatIdleTimeMs); - } - mDetails.userTime = (int) (mCurStepCpuUserTimeMs - mLastStepCpuUserTimeMs); - mDetails.systemTime = (int) (mCurStepCpuSystemTimeMs - mLastStepCpuSystemTimeMs); - mDetails.statUserTime = (int) (mCurStepStatUserTimeMs - mLastStepStatUserTimeMs); - mDetails.statSystemTime = - (int) (mCurStepStatSystemTimeMs - mLastStepStatSystemTimeMs); - mDetails.statIOWaitTime = - (int) (mCurStepStatIOWaitTimeMs - mLastStepStatIOWaitTimeMs); - mDetails.statIrqTime = (int) (mCurStepStatIrqTimeMs - mLastStepStatIrqTimeMs); - mDetails.statSoftIrqTime = - (int) (mCurStepStatSoftIrqTimeMs - mLastStepStatSoftIrqTimeMs); - mDetails.statIdlTime = (int) (mCurStepStatIdleTimeMs - mLastStepStatIdleTimeMs); - mDetails.appCpuUid1 = mDetails.appCpuUid2 = mDetails.appCpuUid3 = -1; - mDetails.appCpuUTime1 = mDetails.appCpuUTime2 = mDetails.appCpuUTime3 = 0; - mDetails.appCpuSTime1 = mDetails.appCpuSTime2 = mDetails.appCpuSTime3 = 0; - final int uidCount = mUidStats.size(); - for (int i = 0; i < uidCount; i++) { - final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i); - final int totalUTimeMs = - (int) (uid.mCurStepUserTimeMs - uid.mLastStepUserTimeMs); - final int totalSTimeMs = - (int) (uid.mCurStepSystemTimeMs - uid.mLastStepSystemTimeMs); - final int totalTimeMs = totalUTimeMs + totalSTimeMs; - uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs; - uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs; - if (totalTimeMs <= (mDetails.appCpuUTime3 + mDetails.appCpuSTime3)) { - continue; - } - if (totalTimeMs <= (mDetails.appCpuUTime2 + mDetails.appCpuSTime2)) { - mDetails.appCpuUid3 = uid.mUid; - mDetails.appCpuUTime3 = totalUTimeMs; - mDetails.appCpuSTime3 = totalSTimeMs; - } else { - mDetails.appCpuUid3 = mDetails.appCpuUid2; - mDetails.appCpuUTime3 = mDetails.appCpuUTime2; - mDetails.appCpuSTime3 = mDetails.appCpuSTime2; - if (totalTimeMs <= (mDetails.appCpuUTime1 + mDetails.appCpuSTime1)) { - mDetails.appCpuUid2 = uid.mUid; - mDetails.appCpuUTime2 = totalUTimeMs; - mDetails.appCpuSTime2 = totalSTimeMs; - } else { - mDetails.appCpuUid2 = mDetails.appCpuUid1; - mDetails.appCpuUTime2 = mDetails.appCpuUTime1; - mDetails.appCpuSTime2 = mDetails.appCpuSTime1; - mDetails.appCpuUid1 = uid.mUid; - mDetails.appCpuUTime1 = totalUTimeMs; - mDetails.appCpuSTime1 = totalSTimeMs; - } - } - } - mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs; - mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs; - mLastStepStatUserTimeMs = mCurStepStatUserTimeMs; - mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs; - mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs; - mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs; - mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs; - mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs; - return mDetails; - } - } - - public void addCpuStats(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs, - int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs, - int statSoftIrqTimeMs, int statIdleTimeMs) { - if (DEBUG) { - Slog.d(TAG, "Adding cpu: tuser=" + totalUTimeMs + " tsys=" + totalSTimeMs - + " user=" + statUserTimeMs + " sys=" + statSystemTimeMs - + " io=" + statIOWaitTimeMs + " irq=" + statIrqTimeMs - + " sirq=" + statSoftIrqTimeMs + " idle=" + statIdleTimeMs); - } - mCurStepCpuUserTimeMs += totalUTimeMs; - mCurStepCpuSystemTimeMs += totalSTimeMs; - mCurStepStatUserTimeMs += statUserTimeMs; - mCurStepStatSystemTimeMs += statSystemTimeMs; - mCurStepStatIOWaitTimeMs += statIOWaitTimeMs; - mCurStepStatIrqTimeMs += statIrqTimeMs; - mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs; - mCurStepStatIdleTimeMs += statIdleTimeMs; - } - - public void finishAddingCpuLocked() { - mHasHistoryStepDetails = true; - mUpdateRequested = false; - } - - @Override - public void clear() { - mHasHistoryStepDetails = false; - mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs = 0; - mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs = 0; - mLastStepStatUserTimeMs = mCurStepStatUserTimeMs = 0; - mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs = 0; - mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs = 0; - mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs = 0; - mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs = 0; - mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs = 0; - } - } - @GuardedBy("this") @Override public void commitCurrentHistoryBatchLocked() { @@ -5557,7 +5373,7 @@ public class BatteryStatsImpl extends BatteryStats { public void addCpuStatsLocked(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs, int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs, int statSoftIrqTimeMs, int statIdleTimeMs) { - mStepDetailsCalculator.addCpuStats(totalUTimeMs, totalSTimeMs, statUserTimeMs, + mStepDetailsProvider.addCpuStats(totalUTimeMs, totalSTimeMs, statUserTimeMs, statSystemTimeMs, statIOWaitTimeMs, statIrqTimeMs, statSoftIrqTimeMs, statIdleTimeMs); } @@ -5567,7 +5383,7 @@ public class BatteryStatsImpl extends BatteryStats { */ @GuardedBy("this") public void finishAddingCpuStatsLocked() { - mStepDetailsCalculator.finishAddingCpuLocked(); + mStepDetailsProvider.finishAddingCpuLocked(); } public void noteProcessDiedLocked(int uid, int pid) { @@ -11515,8 +11331,7 @@ public class BatteryStatsImpl extends BatteryStats { mBatteryHistoryDirectory = batteryHistoryDirectory; mHistory = new BatteryStatsHistory(null /* historyBuffer */, mConstants.MAX_HISTORY_BUFFER, - mBatteryHistoryDirectory, mStepDetailsCalculator, mClock, mMonotonicClock, - traceDelegate, eventLogger); + mBatteryHistoryDirectory, mClock, mMonotonicClock, traceDelegate, eventLogger); mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector); mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); @@ -11723,6 +11538,12 @@ public class BatteryStatsImpl extends BatteryStats { mCallback = cb; } + void updateCpuDetails() { + if (mCallback != null) { + mCallback.batteryNeedsCpuUpdate(); + } + } + public void setRadioScanningTimeoutLocked(long timeoutUs) { if (mPhoneSignalScanningTimer != null) { mPhoneSignalScanningTimer.setTimeout(timeoutUs); @@ -12343,6 +12164,8 @@ public class BatteryStatsImpl extends BatteryStats { mWakeupReasonStats.clear(); } + mStepDetailsProvider.reset(); + if (mTmpRailStats != null) { mTmpRailStats.reset(); } @@ -14948,6 +14771,8 @@ public class BatteryStatsImpl extends BatteryStats { mCpuUidFreqTimeReader.onSystemReady(); } + mStepDetailsProvider.onSystemReady(); + mPowerStatsCollectorInjector.setContext(context); mCpuPowerStatsCollector.setEnabled( @@ -15270,6 +15095,7 @@ public class BatteryStatsImpl extends BatteryStats { reportChangesToStatsLog(status, plugType, level); + boolean requestStepDetails = false; final boolean onBattery = isOnBattery(plugType, status); if (!mHaveBatteryLevel) { mHaveBatteryLevel = true; @@ -15289,6 +15115,7 @@ public class BatteryStatsImpl extends BatteryStats { mMaxChargeStepLevel = mMinDischargeStepLevel = mLastChargeStepLevel = mLastDischargeStepLevel = level; + requestStepDetails = true; } else if (mBatteryLevel != level || mOnBattery != onBattery) { recordDailyStatsIfNeededLocked(level >= 100 && onBattery, currentTimeMs); } @@ -15332,16 +15159,13 @@ public class BatteryStatsImpl extends BatteryStats { } mBatteryChargeUah = chargeUah; setOnBatteryLocked(elapsedRealtimeMs, uptimeMs, onBattery, oldStatus, level, chargeUah); + requestStepDetails = true; } else { boolean changed = false; if (mBatteryLevel != level) { mBatteryLevel = level; changed = true; - - // TODO(adamlesinski): Schedule the creation of a HistoryStepDetails record - // which will pull external stats. - mExternalSync.scheduleSyncDueToBatteryLevelChange( - mConstants.BATTERY_LEVEL_COLLECTION_DELAY_MS); + requestStepDetails = true; } if (mBatteryStatus != status) { mBatteryStatus = status; @@ -15462,6 +15286,9 @@ public class BatteryStatsImpl extends BatteryStats { if (mAccumulateBatteryUsageStats) { mBatteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(this, mHandler); } + if (requestStepDetails) { + mStepDetailsProvider.requestUpdate(); + } } public static boolean isOnBattery(int plugType, int status) { diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java index 8c3b7c606f04..3ece07c84080 100644 --- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java +++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java @@ -17,6 +17,7 @@ package com.android.server.security.advancedprotection; import static android.provider.Settings.Secure.ADVANCED_PROTECTION_MODE; +import static android.provider.Settings.Secure.AAPM_USB_DATA_PROTECTION; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.Manifest; @@ -69,6 +70,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Set; /** @hide */ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub { @@ -129,7 +131,10 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub Slog.e(TAG, "Failed to initialize DisallowCellular2g", e); } } - if (android.security.Flags.aapmFeatureUsbDataProtection()) { + if (android.security.Flags.aapmFeatureUsbDataProtection() + // Usb data protection is enabled by default + && mStore.retrieveInt(AAPM_USB_DATA_PROTECTION, AdvancedProtectionStore.ON) + == AdvancedProtectionStore.ON) { try { mHooks.add(new UsbDataAdvancedProtectionHook(mContext, enabled)); } catch (Exception e) { @@ -183,7 +188,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub // Without permission check private boolean isAdvancedProtectionEnabledInternal() { - return mStore.retrieve(); + return mStore.retrieveAdvancedProtectionModeEnabled(); } @Override @@ -217,7 +222,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub try { synchronized (mCallbacks) { if (enabled != isAdvancedProtectionEnabledInternal()) { - mStore.store(enabled); + mStore.storeAdvancedProtectionModeEnabled(enabled); sendModeChanged(enabled); logAdvancedProtectionEnabled(enabled); } @@ -227,6 +232,34 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub } } + public void setUsbDataProtectionEnabled(boolean enabled) { + int value = enabled ? AdvancedProtectionStore.ON + : AdvancedProtectionStore.OFF; + setAdvancedProtectionSubSettingInt(AAPM_USB_DATA_PROTECTION, value); + } + + private void setAdvancedProtectionSubSettingInt(String key, int value) { + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mCallbacks) { + mStore.storeInt(key, value); + Slog.i(TAG, "Advanced protection: subsetting" + key + " is " + value); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + public boolean isUsbDataProtectionEnabled() { + final long identity = Binder.clearCallingIdentity(); + try { + return mStore.retrieveInt(AAPM_USB_DATA_PROTECTION, AdvancedProtectionStore.ON) + == AdvancedProtectionStore.ON; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override @EnforcePermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE) public void logDialogShown(@FeatureId int featureId, @SupportDialogType int type, @@ -419,8 +452,8 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub @VisibleForTesting static class AdvancedProtectionStore { private final Context mContext; - private static final int APM_ON = 1; - private static final int APM_OFF = 0; + static final int ON = 1; + static final int OFF = 0; private final UserManagerInternal mUserManager; AdvancedProtectionStore(@NonNull Context context) { @@ -428,15 +461,26 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub mUserManager = LocalServices.getService(UserManagerInternal.class); } - void store(boolean enabled) { + void storeAdvancedProtectionModeEnabled(boolean enabled) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + ADVANCED_PROTECTION_MODE, enabled ? ON : OFF, + mUserManager.getMainUserId()); + } + + boolean retrieveAdvancedProtectionModeEnabled() { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + ADVANCED_PROTECTION_MODE, OFF, mUserManager.getMainUserId()) == ON; + } + + void storeInt(String key, int value) { Settings.Secure.putIntForUser(mContext.getContentResolver(), - ADVANCED_PROTECTION_MODE, enabled ? APM_ON : APM_OFF, + key, value, mUserManager.getMainUserId()); } - boolean retrieve() { + int retrieveInt(String key, int defaultValue) { return Settings.Secure.getIntForUser(mContext.getContentResolver(), - ADVANCED_PROTECTION_MODE, APM_OFF, mUserManager.getMainUserId()) == APM_ON; + key, defaultValue, mUserManager.getMainUserId()); } } diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java index 42505ad2de3f..ae17a459010b 100644 --- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java +++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java @@ -45,6 +45,10 @@ class AdvancedProtectionShellCommand extends ShellCommand { return setProtectionEnabled(); case "is-protection-enabled": return isProtectionEnabled(pw); + case "set-usb-data-protection-enabled": + return setUsbDataProtectedEnabled(); + case "is-usb-data-protection-enabled": + return isUsbDataProtectedEnabled(pw); } } catch (RemoteException e) { pw.println("Remote exception: " + e); @@ -64,6 +68,10 @@ class AdvancedProtectionShellCommand extends ShellCommand { pw.println(" Print this help text."); pw.println(" set-protection-enabled [true|false]"); pw.println(" is-protection-enabled"); + if(android.security.Flags.aapmFeatureUsbDataProtection()) { + pw.println(" set-usb-data-protection-enabled [true|false]"); + pw.println(" is-usb-data-protection-enabled"); + } } @SuppressLint("AndroidFrameworkRequiresPermission") @@ -79,4 +87,22 @@ class AdvancedProtectionShellCommand extends ShellCommand { pw.println(protectionMode); return 0; } + + @SuppressLint("AndroidFrameworkRequiresPermission") + private int setUsbDataProtectedEnabled() throws RemoteException { + if(android.security.Flags.aapmFeatureUsbDataProtection()) { + String protectionMode = getNextArgRequired(); + mService.setUsbDataProtectionEnabled(Boolean.parseBoolean(protectionMode)); + } + return 0; + } + + @SuppressLint("AndroidFrameworkRequiresPermission") + private int isUsbDataProtectedEnabled(@NonNull PrintWriter pw) throws RemoteException { + if(android.security.Flags.aapmFeatureUsbDataProtection()) { + boolean protectionMode = mService.isUsbDataProtectionEnabled(); + pw.println(protectionMode); + } + return 0; + } } diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java index 70fc6bace868..ee173a2a6a35 100644 --- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java +++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java @@ -131,6 +131,10 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, mCache = cache; } + void setSnapshotReleaser(Consumer<HardwareBuffer> releaser) { + mCache.setSafeSnapshotReleaser(releaser); + } + void setSnapshotEnabled(boolean enabled) { mSnapshotEnabled = enabled; } diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java index ed07afd2eab5..a0e537231071 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java @@ -31,6 +31,7 @@ class ActivitySnapshotCache extends SnapshotCache<ActivityRecord> { void putSnapshot(ActivityRecord ar, TaskSnapshot snapshot) { final int hasCode = System.identityHashCode(ar); snapshot.addReference(TaskSnapshot.REFERENCE_CACHE); + snapshot.setSafeRelease(mSafeSnapshotReleaser); synchronized (mLock) { final CacheEntry entry = mRunningCache.get(hasCode); if (entry != null) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 21b730e13585..7123a7c1160f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -803,4 +803,10 @@ public abstract class ActivityTaskManagerInternal { /** Returns whether assist data is allowed. */ public abstract boolean isAssistDataAllowed(); + + /** + * Delegate back gesture request from shell. + * Returns true if the back gesture request was successful, false otherwise. + */ + public abstract boolean requestBackGesture(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 46d24b0d3201..a0c38dd82037 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1892,6 +1892,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + @Override + public void registerBackGestureDelegate(RemoteCallback requestObserver) { + mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS, + "registerBackGestureDelegate()"); + final long origId = Binder.clearCallingIdentity(); + try { + mBackNavigationController.registerBackGestureDelegate(requestObserver); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + /** * Public API to check if the client is allowed to start an activity on specified display. * @@ -7571,6 +7583,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public boolean isAssistDataAllowed() { return ActivityTaskManagerService.this.isAssistDataAllowed(); } + + @Override + public boolean requestBackGesture() { + return mBackNavigationController.requestBackGesture(); + } } /** Cache the return value for {@link #isPip2ExperimentEnabled()} */ diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index dfe323c43abb..819e4abaa9d3 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -85,6 +85,7 @@ class BackNavigationController { private boolean mShowWallpaper; private Runnable mPendingAnimation; private final NavigationMonitor mNavigationMonitor = new NavigationMonitor(); + private RemoteCallback mGestureRequest; private AnimationHandler mAnimationHandler; @@ -113,6 +114,35 @@ class BackNavigationController { mNavigationMonitor.onEmbeddedWindowGestureTransferred(host); } + void registerBackGestureDelegate(@NonNull RemoteCallback requestObserver) { + if (!sPredictBackEnable) { + return; + } + synchronized (mWindowManagerService.mGlobalLock) { + mGestureRequest = requestObserver; + try { + requestObserver.getInterface().asBinder().linkToDeath(() -> { + synchronized (mWindowManagerService.mGlobalLock) { + mGestureRequest = null; + } + }, 0 /* flags */); + } catch (RemoteException r) { + Slog.e(TAG, "Failed to link to death"); + mGestureRequest = null; + } + } + } + + boolean requestBackGesture() { + synchronized (mWindowManagerService.mGlobalLock) { + if (mGestureRequest == null) { + return false; + } + mGestureRequest.sendResult(null); + return true; + } + } + /** * Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming * back gesture animation. diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 313b77ed4b20..f9eb0574d87c 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -137,6 +137,7 @@ import com.android.internal.view.AppearanceRegion; import com.android.internal.widget.PointerLocationView; import com.android.server.LocalServices; import com.android.server.UiThread; +import com.android.server.notification.NotificationManagerInternal; import com.android.server.policy.WindowManagerPolicy.ScreenOnListener; import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs; import com.android.server.statusbar.StatusBarManagerInternal; @@ -1925,6 +1926,11 @@ public class DisplayPolicy { if (wpMgr != null) { wpMgr.onDisplayRemoveSystemDecorations(displayId); } + final NotificationManagerInternal notificationManager = + LocalServices.getService(NotificationManagerInternal.class); + if (notificationManager != null) { + notificationManager.onDisplayRemoveSystemDecorations(displayId); + } }); } diff --git a/services/core/java/com/android/server/wm/SnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java index 9812a88bfa5a..685bd3b355ed 100644 --- a/services/core/java/com/android/server/wm/SnapshotCache.java +++ b/services/core/java/com/android/server/wm/SnapshotCache.java @@ -16,12 +16,14 @@ package com.android.server.wm; import android.annotation.Nullable; +import android.hardware.HardwareBuffer; import android.util.ArrayMap; import android.window.TaskSnapshot; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; +import java.util.function.Consumer; /** * Base class for an app snapshot cache @@ -38,12 +40,18 @@ abstract class SnapshotCache<TYPE extends WindowContainer> { @GuardedBy("mLock") protected final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>(); + protected Consumer<HardwareBuffer> mSafeSnapshotReleaser; + SnapshotCache(String name) { mName = name; } abstract void putSnapshot(TYPE window, TaskSnapshot snapshot); + void setSafeSnapshotReleaser(Consumer<HardwareBuffer> safeSnapshotReleaser) { + mSafeSnapshotReleaser = safeSnapshotReleaser; + } + void clearRunningCache() { synchronized (mLock) { mRunningCache.clear(); diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java index 3a7222ae6d51..48f0959214c1 100644 --- a/services/core/java/com/android/server/wm/SnapshotController.java +++ b/services/core/java/com/android/server/wm/SnapshotController.java @@ -26,13 +26,16 @@ import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import android.hardware.HardwareBuffer; import android.os.Trace; import android.util.ArrayMap; import android.view.WindowManager; import android.window.TaskSnapshot; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.function.Consumer; /** * Integrates common functionality from TaskSnapshotController and ActivitySnapshotController. @@ -41,11 +44,30 @@ class SnapshotController { private final SnapshotPersistQueue mSnapshotPersistQueue; final TaskSnapshotController mTaskSnapshotController; final ActivitySnapshotController mActivitySnapshotController; + private final WindowManagerService mService; + private final ArrayList<WeakReference<HardwareBuffer>> mObsoleteSnapshots = new ArrayList<>(); SnapshotController(WindowManagerService wms) { + mService = wms; mSnapshotPersistQueue = new SnapshotPersistQueue(); mTaskSnapshotController = new TaskSnapshotController(wms, mSnapshotPersistQueue); mActivitySnapshotController = new ActivitySnapshotController(wms, mSnapshotPersistQueue); + final Consumer<HardwareBuffer> releaser = hb -> { + mService.mH.post(() -> { + synchronized (mService.mGlobalLock) { + if (hb.isClosed()) { + return; + } + if (mService.mAtmService.getTransitionController().inTransition()) { + mObsoleteSnapshots.add(new WeakReference<>(hb)); + } else { + hb.close(); + } + } + }); + }; + mTaskSnapshotController.setSnapshotReleaser(releaser); + mActivitySnapshotController.setSnapshotReleaser(releaser); } void systemReady() { @@ -168,6 +190,7 @@ class SnapshotController { final boolean isTransitionClose = isTransitionClose(type); if (!isTransitionOpen && !isTransitionClose && type < TRANSIT_FIRST_CUSTOM || (changeInfos.isEmpty())) { + closeObsoleteSnapshots(); return; } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SnapshotController_analysis"); @@ -195,9 +218,22 @@ class SnapshotController { } } } + closeObsoleteSnapshots(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + private void closeObsoleteSnapshots() { + if (mObsoleteSnapshots.isEmpty()) { + return; + } + for (int i = mObsoleteSnapshots.size() - 1; i >= 0; --i) { + final HardwareBuffer hb = mObsoleteSnapshots.remove(i).get(); + if (hb != null && !hb.isClosed()) { + hb.close(); + } + } + } + private static boolean isTransitionOpen(int type) { return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT || type == TRANSIT_PREPARE_BACK_NAVIGATION; diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java index cc957bd9ee42..1988f2630b6e 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java @@ -36,6 +36,7 @@ class TaskSnapshotCache extends SnapshotCache<Task> { void putSnapshot(Task task, TaskSnapshot snapshot) { synchronized (mLock) { snapshot.addReference(TaskSnapshot.REFERENCE_CACHE); + snapshot.setSafeRelease(mSafeSnapshotReleaser); final CacheEntry entry = mRunningCache.get(task.mTaskId); if (entry != null) { mAppIdMap.remove(entry.topApp); diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java index 026e72f117b4..d0226805224e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java @@ -26,15 +26,12 @@ import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUD import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_WIFI_SCAN; import static android.app.AppOpsManager.UID_STATE_BACKGROUND; -import static android.app.AppOpsManager.UID_STATE_CACHED; import static android.app.AppOpsManager.UID_STATE_FOREGROUND; import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; import static android.app.AppOpsManager.UID_STATE_TOP; import static android.permission.flags.Flags.delayUidStateChangesFromCapabilityUpdates; -import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -42,7 +39,6 @@ import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -64,7 +60,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.quality.Strictness; import java.util.PriorityQueue; @@ -94,7 +89,6 @@ public class AppOpsUidStateTrackerTest { public void setUp() { mSession = ExtendedMockito.mockitoSession() .initMocks(this) - .strictness(Strictness.LENIENT) .startMocking(); mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L; mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L; @@ -591,221 +585,6 @@ public class AppOpsUidStateTrackerTest { } @Test - public void testUidStateChangedCallbackCachedToBackground() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, - ActivityManager.PROCESS_STATE_RECEIVER); - } - - @Test - public void testUidStateChangedCallbackCachedToForeground() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, - ActivityManager.PROCESS_STATE_BOUND_TOP); - } - - @Test - public void testUidStateChangedCallbackCachedToForegroundService() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); - } - - @Test - public void testUidStateChangedCallbackCachedToTop() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, - ActivityManager.PROCESS_STATE_TOP); - } - - @Test - public void testUidStateChangedCallbackBackgroundToCached() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_RECEIVER, - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); - } - - @Test - public void testUidStateChangedCallbackBackgroundToForeground() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_RECEIVER, - ActivityManager.PROCESS_STATE_BOUND_TOP); - } - - @Test - public void testUidStateChangedCallbackBackgroundToForegroundService() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_RECEIVER, - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); - } - - @Test - public void testUidStateChangedCallbackBackgroundToTop() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_RECEIVER, - ActivityManager.PROCESS_STATE_TOP); - } - - @Test - public void testUidStateChangedCallbackForegroundToCached() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_BOUND_TOP, - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); - } - - @Test - public void testUidStateChangedCallbackForegroundToBackground() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_BOUND_TOP, - ActivityManager.PROCESS_STATE_RECEIVER); - } - - @Test - public void testUidStateChangedCallbackForegroundToForegroundService() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_BOUND_TOP, - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); - } - - @Test - public void testUidStateChangedCallbackForegroundToTop() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_BOUND_TOP, - ActivityManager.PROCESS_STATE_TOP); - } - - @Test - public void testUidStateChangedCallbackForegroundServiceToCached() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); - } - - @Test - public void testUidStateChangedCallbackForegroundServiceToBackground() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_STATE_RECEIVER); - } - - @Test - public void testUidStateChangedCallbackForegroundServiceToForeground() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_STATE_BOUND_TOP); - } - - @Test - public void testUidStateChangedCallbackForegroundServiceToTop() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_STATE_TOP); - } - - @Test - public void testUidStateChangedCallbackTopToCached() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_TOP, - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); - } - - @Test - public void testUidStateChangedCallbackTopToBackground() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_TOP, - ActivityManager.PROCESS_STATE_RECEIVER); - } - - @Test - public void testUidStateChangedCallbackTopToForeground() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_TOP, - ActivityManager.PROCESS_STATE_BOUND_TOP); - } - - @Test - public void testUidStateChangedCallbackTopToForegroundService() { - testUidStateChangedCallback( - ActivityManager.PROCESS_STATE_TOP, - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); - } - - @Test - public void testUidStateChangedCallbackCachedToNonexistent() { - UidStateChangedCallback cb = addUidStateChangeCallback(); - - procStateBuilder(UID) - .cachedState() - .update(); - - procStateBuilder(UID) - .nonExistentState() - .update(); - - verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean()); - } - - @Test - public void testUidStateChangedCallbackBackgroundToNonexistent() { - UidStateChangedCallback cb = addUidStateChangeCallback(); - - procStateBuilder(UID) - .backgroundState() - .update(); - - procStateBuilder(UID) - .nonExistentState() - .update(); - - verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(false)); - } - - @Test - public void testUidStateChangedCallbackForegroundToNonexistent() { - UidStateChangedCallback cb = addUidStateChangeCallback(); - - procStateBuilder(UID) - .foregroundState() - .update(); - - procStateBuilder(UID) - .nonExistentState() - .update(); - - verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); - } - - @Test - public void testUidStateChangedCallbackForegroundServiceToNonexistent() { - UidStateChangedCallback cb = addUidStateChangeCallback(); - - procStateBuilder(UID) - .foregroundServiceState() - .update(); - - procStateBuilder(UID) - .nonExistentState() - .update(); - - verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); - } - - @Test - public void testUidStateChangedCallbackTopToNonexistent() { - UidStateChangedCallback cb = addUidStateChangeCallback(); - - procStateBuilder(UID) - .topState() - .update(); - - procStateBuilder(UID) - .nonExistentState() - .update(); - - verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); - } - - @Test public void testUidStateChangedBackgroundThenForegroundImmediately() { procStateBuilder(UID) .topState() @@ -882,32 +661,6 @@ public class AppOpsUidStateTrackerTest { assertEquals(UID_STATE_TOP, mIntf.getUidState(UID)); } - public void testUidStateChangedCallback(int initialState, int finalState) { - int initialUidState = processStateToUidState(initialState); - int finalUidState = processStateToUidState(finalState); - boolean foregroundChange = initialUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED - != finalUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED; - boolean finalUidStateIsBackgroundAndLessImportant = - finalUidState > UID_STATE_MAX_LAST_NON_RESTRICTED - && finalUidState > initialUidState; - - UidStateChangedCallback cb = addUidStateChangeCallback(); - - procStateBuilder(UID) - .setState(initialState) - .update(); - - procStateBuilder(UID) - .setState(finalState) - .update(); - - if (finalUidStateIsBackgroundAndLessImportant) { - mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1); - } - - verify(cb, atLeastOnce()) - .onUidStateChanged(eq(UID), eq(finalUidState), eq(foregroundChange)); - } private UidStateChangedCallback addUidStateChangeCallback() { UidStateChangedCallback cb = diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTransitionCallbackTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTransitionCallbackTest.java new file mode 100644 index 000000000000..60cdfee0834c --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTransitionCallbackTest.java @@ -0,0 +1,260 @@ +/* + * 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.server.appop; + +import static android.app.AppOpsManager.UID_STATE_CACHED; +import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; +import static android.app.AppOpsManager.UID_STATE_NONEXISTENT; +import static android.permission.flags.Flags.finishRunningOpsForKilledPackages; + +import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.internal.os.Clock; +import com.android.server.appop.AppOpsUidStateTracker.UidStateChangedCallback; +import com.android.server.appop.AppOpsUidStateTrackerImpl.DelayableExecutor; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.mockito.Mock; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.PriorityQueue; + +@RunWith(Parameterized.class) +public class AppOpsUidStateTrackerTransitionCallbackTest { + + private static final int UID = 10001; + + private static final int STATE_TOP = ActivityManager.PROCESS_STATE_TOP; + private static final int STATE_FOREGROUND_SERVICE = + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + private static final int STATE_FOREGROUND = ActivityManager.PROCESS_STATE_BOUND_TOP; + private static final int STATE_BACKGROUND = ActivityManager.PROCESS_STATE_SERVICE; + private static final int STATE_CACHED = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; + private static final int STATE_NONEXISTENT = ActivityManager.PROCESS_STATE_NONEXISTENT; + + private static final int[] STATES = {STATE_TOP, STATE_FOREGROUND_SERVICE, STATE_FOREGROUND, + STATE_BACKGROUND, STATE_CACHED, STATE_NONEXISTENT}; + private static final String[] STATES_NAMES = {"TOP", "FOREGROUND_SERVICE", "FOREGROUND", + "BACKGROUND", "CACHED", "NONEXISTENT"}; + + @Mock + ActivityManagerInternal mAmi; + + @Mock + AppOpsService.Constants mConstants; + + AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor(); + + AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock(mExecutor); + + AppOpsUidStateTracker mIntf; + + StaticMockitoSession mSession; + + private final int mInitialState; + private final int mMiddleState; + private final int mFinalState; + + @Parameterized.Parameters(name = "{3} -> {4} -> {5}") + public static Collection<Object[]> getParameters() { + ArrayList<Object[]> parameters = new ArrayList<>(); + + for (int i = 0; i < STATES.length; i++) { + for (int j = 0; j < STATES.length; j++) { + for (int k = 0; k < STATES.length; k++) { + parameters + .add(new Object[]{STATES[i], STATES[j], STATES[k], STATES_NAMES[i], + STATES_NAMES[j], STATES_NAMES[k]}); + } + } + } + + return parameters; + } + + public AppOpsUidStateTrackerTransitionCallbackTest(int initialState, int middleState, + int finalState, String ignoredInitialStateName, String ignoredMiddleStateName, + String ignoredFinalStateName) { + mInitialState = initialState; + mMiddleState = middleState; + mFinalState = finalState; + } + + @Before + public void setUp() { + mSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .startMocking(); + mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L; + mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L; + mConstants.BG_STATE_SETTLE_TIME = 1 * 1000L; + mIntf = new AppOpsUidStateTrackerImpl(mAmi, mExecutor, mClock, mConstants, + Thread.currentThread()); + } + + @After + public void tearDown() { + mSession.finishMocking(); + } + + @Test + public void testUidStateChangedCallback() { + testUidStateTransition(mInitialState, mMiddleState, true); + testUidStateTransition(mMiddleState, mFinalState, false); + } + + private void testUidStateTransition(int initialState, int finalState, + boolean initializeState) { + int initialUidState = processStateToUidState(initialState); + int finalUidState = processStateToUidState(finalState); + + boolean expectUidProcessDeath = + finalUidState == UID_STATE_NONEXISTENT + && initialUidState != UID_STATE_NONEXISTENT + && finishRunningOpsForKilledPackages(); + + boolean foregroundChange = initialUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + != finalUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED; + boolean finalUidStateIsBackgroundAndLessImportant = + finalUidState > UID_STATE_MAX_LAST_NON_RESTRICTED + && finalUidState > initialUidState; + + if (initializeState) { + mIntf.updateUidProcState(UID, initialState, ActivityManager.PROCESS_CAPABILITY_NONE); + } + + UidStateChangedCallback cb = addUidStateChangeCallback(); + + mIntf.updateUidProcState(UID, finalState, ActivityManager.PROCESS_CAPABILITY_NONE); + + if (finalUidStateIsBackgroundAndLessImportant) { + mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1); + } + + int expectedInitialUidState = initialUidState == UID_STATE_NONEXISTENT + ? UID_STATE_CACHED : initialUidState; + int expectedFinalUidState = finalUidState == UID_STATE_NONEXISTENT + ? UID_STATE_CACHED : finalUidState; + + if (expectedInitialUidState != expectedFinalUidState) { + verify(cb, times(1)) + .onUidStateChanged(eq(UID), eq(expectedFinalUidState), eq(foregroundChange)); + verify(cb, times(1)) + .onUidStateChanged(anyInt(), anyInt(), anyBoolean()); + } else { + verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean()); + } + if (expectUidProcessDeath) { + verify(cb, times(1)).onUidProcessDeath(eq(UID)); + verify(cb, times(1)).onUidProcessDeath(anyInt()); + } else { + verify(cb, never()).onUidProcessDeath(anyInt()); + } + } + + private UidStateChangedCallback addUidStateChangeCallback() { + UidStateChangedCallback cb = + Mockito.mock(UidStateChangedCallback.class); + mIntf.addUidStateChangedCallback(r -> r.run(), cb); + return cb; + } + + private static class AppOpsUidStateTrackerTestClock extends Clock { + + private AppOpsUidStateTrackerTestExecutor mExecutor; + long mElapsedRealTime = 0x5f3759df; + + AppOpsUidStateTrackerTestClock(AppOpsUidStateTrackerTestExecutor executor) { + mExecutor = executor; + executor.setUptime(mElapsedRealTime); + } + + @Override + public long elapsedRealtime() { + return mElapsedRealTime; + } + + void advanceTime(long time) { + mElapsedRealTime += time; + mExecutor.setUptime(mElapsedRealTime); // assume uptime == elapsedtime + } + } + + private static class AppOpsUidStateTrackerTestExecutor implements DelayableExecutor { + + private static class QueueElement implements Comparable<QueueElement> { + + private long mExecutionTime; + private Runnable mRunnable; + + private QueueElement(long executionTime, Runnable runnable) { + mExecutionTime = executionTime; + mRunnable = runnable; + } + + @Override + public int compareTo(QueueElement queueElement) { + return Long.compare(mExecutionTime, queueElement.mExecutionTime); + } + } + + private long mUptime = 0; + + private PriorityQueue<QueueElement> mDelayedMessages = new PriorityQueue(); + + @Override + public void execute(Runnable runnable) { + runnable.run(); + } + + @Override + public void executeDelayed(Runnable runnable, long delay) { + if (delay <= 0) { + execute(runnable); + } + + mDelayedMessages.add(new QueueElement(mUptime + delay, runnable)); + } + + private void setUptime(long uptime) { + while (!mDelayedMessages.isEmpty() + && mDelayedMessages.peek().mExecutionTime <= uptime) { + mDelayedMessages.poll().mRunnable.run(); + } + + mUptime = uptime; + } + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java index 2b152315eec4..a1cf94b994ab 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java @@ -26,16 +26,10 @@ import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync. import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import android.content.Context; -import android.hardware.power.stats.Channel; -import android.hardware.power.stats.EnergyConsumer; import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; -import android.hardware.power.stats.EnergyMeasurement; -import android.hardware.power.stats.PowerEntity; -import android.hardware.power.stats.StateResidencyResult; import android.os.Handler; import android.os.Looper; import android.os.connectivity.WifiActivityEnergyInfo; @@ -54,7 +48,6 @@ import org.junit.Before; import org.junit.Test; import java.util.Arrays; -import java.util.concurrent.CompletableFuture; /** * Tests for {@link BatteryExternalStatsWorker}. @@ -66,7 +59,7 @@ import java.util.concurrent.CompletableFuture; @android.platform.test.annotations.DisabledOnRavenwood public class BatteryExternalStatsWorkerTest { private BatteryExternalStatsWorker mBatteryExternalStatsWorker; - private TestPowerStatsInternal mPowerStatsInternal; + private MockPowerStatsInternal mPowerStatsInternal; private Handler mHandler; @Before @@ -80,7 +73,7 @@ public class BatteryExternalStatsWorkerTest { mHandler, null, null, null, new PowerProfile(context, true /* forTest */), buildScalingPolicies(), new PowerStatsUidResolver()); - mPowerStatsInternal = new TestPowerStatsInternal(); + mPowerStatsInternal = new MockPowerStatsInternal(); mBatteryExternalStatsWorker = new BatteryExternalStatsWorker(new TestInjector(context), batteryStats, mHandler); } @@ -260,102 +253,4 @@ public class BatteryExternalStatsWorkerTest { freqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000}); return new CpuScalingPolicies(freqsByPolicy, freqsByPolicy); } - - private static class TestPowerStatsInternal extends PowerStatsInternal { - private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray(); - private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults = new SparseArray(); - private final int mTimeSinceBoot = 0; - - @Override - public EnergyConsumer[] getEnergyConsumerInfo() { - final int size = mEnergyConsumers.size(); - final EnergyConsumer[] consumers = new EnergyConsumer[size]; - for (int i = 0; i < size; i++) { - consumers[i] = mEnergyConsumers.valueAt(i); - } - return consumers; - } - - @Override - public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync( - int[] energyConsumerIds) { - final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture(); - final EnergyConsumerResult[] results; - final int length = energyConsumerIds.length; - if (length == 0) { - final int size = mEnergyConsumerResults.size(); - results = new EnergyConsumerResult[size]; - for (int i = 0; i < size; i++) { - results[i] = mEnergyConsumerResults.valueAt(i); - } - } else { - results = new EnergyConsumerResult[length]; - for (int i = 0; i < length; i++) { - results[i] = mEnergyConsumerResults.get(energyConsumerIds[i]); - } - } - future.complete(results); - return future; - } - - @Override - public PowerEntity[] getPowerEntityInfo() { - return new PowerEntity[0]; - } - - @Override - public CompletableFuture<StateResidencyResult[]> getStateResidencyAsync( - int[] powerEntityIds) { - return new CompletableFuture<>(); - } - - @Override - public Channel[] getEnergyMeterInfo() { - return new Channel[0]; - } - - @Override - public CompletableFuture<EnergyMeasurement[]> readEnergyMeterAsync( - int[] channelIds) { - return new CompletableFuture<>(); - } - - /** - * Util method to add a new EnergyConsumer for testing - * - * @return the EnergyConsumer id of the new EnergyConsumer - */ - public int addEnergyConsumer(@EnergyConsumerType byte type, int ordinal, String name) { - final EnergyConsumer consumer = new EnergyConsumer(); - final int id = getNextAvailableId(); - consumer.id = id; - consumer.type = type; - consumer.ordinal = ordinal; - consumer.name = name; - mEnergyConsumers.put(id, consumer); - - final EnergyConsumerResult result = new EnergyConsumerResult(); - result.id = id; - result.timestampMs = mTimeSinceBoot; - result.energyUWs = 0; - mEnergyConsumerResults.put(id, result); - return id; - } - - public void incrementEnergyConsumption(int id, long energyUWs) { - EnergyConsumerResult result = mEnergyConsumerResults.get(id, null); - assertNotNull(result); - result.energyUWs += energyUWs; - } - - private int getNextAvailableId() { - final int size = mEnergyConsumers.size(); - // Just return the first index that does not match the key (aka the EnergyConsumer id) - for (int i = size - 1; i >= 0; i--) { - if (mEnergyConsumers.keyAt(i) == i) return i + 1; - } - // Otherwise return the lowest id - return 0; - } - } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryHistoryStepDetailsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryHistoryStepDetailsProviderTest.java new file mode 100644 index 000000000000..850cd8868e67 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryHistoryStepDetailsProviderTest.java @@ -0,0 +1,163 @@ +/* + * 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.server.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.os.BatteryManager; +import android.os.BatteryStats; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.Looper; +import android.os.Process; +import android.power.PowerStatsInternal; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.os.BatteryStatsHistoryIterator; +import com.android.internal.os.MonotonicClock; +import com.android.internal.os.PowerProfile; +import com.android.server.LocalServices; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Random; + +@RunWith(AndroidJUnit4.class) +@android.platform.test.annotations.DisabledOnRavenwood(reason = + "PowerStatsInternal is not supported under Ravenwood") +@SuppressWarnings("GuardedBy") +public class BatteryHistoryStepDetailsProviderTest { + private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; + + private final MockClock mMockClock = new MockClock(); + private MockBatteryStatsImpl mBatteryStats; + private final Random mRandom = new Random(); + private Handler mHandler; + + @Before + public void setup() { + mMockClock.currentTime = 3000; + mHandler = new Handler(Looper.getMainLooper()); + mBatteryStats = new MockBatteryStatsImpl(mMockClock, null, mHandler, + mock(PowerProfile.class)); + mBatteryStats.setRecordAllHistoryLocked(true); + mBatteryStats.forceRecordAllHistory(); + mBatteryStats.setNoAutoReset(true); + } + + @Test + public void update() { + MockPowerStatsInternal powerStatsService = new MockPowerStatsInternal(); + powerStatsService.addPowerEntity(42, "foo"); + powerStatsService.addPowerEntityState(42, 0, "off"); + powerStatsService.addPowerEntityState(42, 1, "on"); + LocalServices.addService(PowerStatsInternal.class, powerStatsService); + mBatteryStats.onSystemReady(mock(Context.class)); + + mockUpdateCpuStats(100, 1_000_010, 1_000_010); + powerStatsService.addStateResidencyResult(42, 0, 1000, 2000, 3000); + powerStatsService.addStateResidencyResult(42, 1, 4000, 5000, 6000); + + mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, + 100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, + 1_000_000, 1_000_000, 1_000_000); + awaitCompletion(); + + mockUpdateCpuStats(200, 5_000_010, 5_000_010); + powerStatsService.reset(); + powerStatsService.addStateResidencyResult(42, 0, 1111, 2222, 3333); + powerStatsService.addStateResidencyResult(42, 1, 4444, 5555, 6666); + + // Battery level is unchanged, so we don't write battery level details in history + mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, + 100, /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0, + 5_500_000, 5_500_000, 5_000_000); + awaitCompletion(); + + // Not a battery state change event, so details are not written + mBatteryStats.noteAlarmStartLocked("wakeup", null, APP_UID, 6_000_000, 6_000_000); + + mockUpdateCpuStats(300, 6_000_010, 6_000_010); + powerStatsService.reset(); + + // Battery level drops, so we write the accumulated battery level details + mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, + 100, /* plugType */ 0, 70, 72, 3700, 2_000_000, 4_000_000, 0, + 6_000_000, 6_000_000, 6_000_000); + awaitCompletion(); + + final BatteryStatsHistoryIterator iterator = + mBatteryStats.iterateBatteryStatsHistory(0, MonotonicClock.UNDEFINED); + + BatteryStats.HistoryItem item; + assertThat(item = iterator.next()).isNotNull(); + assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_RESET); + assertThat(item.stepDetails).isNull(); + + assertThat(item = iterator.next()).isNotNull(); + assertThat(item.batteryLevel).isEqualTo(90); + assertThat(item.stepDetails.userTime).isEqualTo(100); + assertThat(item.stepDetails.statSubsystemPowerState).contains("subsystem_0 name=foo " + + "state_0 name=off time=1000 count=2000 last entry=3000 " + + "state_1 name=on time=4000 count=5000 last entry=6000"); + + assertThat(item = iterator.next()).isNotNull(); + assertThat(item.batteryLevel).isEqualTo(80); + assertThat(item.stepDetails.userTime).isEqualTo(200); + assertThat(item.stepDetails.statSubsystemPowerState).contains("subsystem_0 name=foo " + + "state_0 name=off time=1111 count=2222 last entry=3333 " + + "state_1 name=on time=4444 count=5555 last entry=6666"); + + assertThat(item = iterator.next()).isNotNull(); + assertThat(item.batteryLevel).isEqualTo(80); + assertThat(item.stepDetails).isNull(); + + assertThat(item = iterator.next()).isNotNull(); + assertThat(item.batteryLevel).isEqualTo(70); + assertThat(item.stepDetails.userTime).isEqualTo(300); + assertThat(item.stepDetails.statSubsystemPowerState).isNull(); + + assertThat(iterator.next()).isNull(); + } + + private void mockUpdateCpuStats(int totalUTimeMs, long elapsedRealtime, long uptime) { + BatteryStatsImpl.BatteryCallback callback = mock(BatteryStatsImpl.BatteryCallback.class); + doAnswer(inv -> { + mMockClock.realtime = elapsedRealtime; + mMockClock.uptime = uptime; + synchronized (mBatteryStats) { + mBatteryStats.addCpuStatsLocked(totalUTimeMs, 0, 0, 0, 0, 0, 0, 0); + mBatteryStats.finishAddingCpuStatsLocked(); + } + return null; + }).when(callback).batteryNeedsCpuUpdate(); + mBatteryStats.setCallback(callback); + } + + private void awaitCompletion() { + ConditionVariable done = new ConditionVariable(); + mHandler.post(done::open); + done.block(); + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java index 503e23347cf6..1b82ffdcf3e8 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java @@ -47,14 +47,12 @@ public class BatteryStatsHistoryIteratorTest { private final MockClock mMockClock = new MockClock(); private MockBatteryStatsImpl mBatteryStats; private final Random mRandom = new Random(); - private final MockExternalStatsSync mExternalStatsSync = new MockExternalStatsSync(); @Before public void setup() { final File historyDir = createTemporaryDirectory(getClass().getSimpleName()); mMockClock.currentTime = 3000; mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir); - mBatteryStats.setDummyExternalStatsSync(mExternalStatsSync); mBatteryStats.setRecordAllHistoryLocked(true); mBatteryStats.forceRecordAllHistory(); mBatteryStats.setNoAutoReset(true); @@ -227,72 +225,53 @@ public class BatteryStatsHistoryIteratorTest { 100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000, 1_000_000, 1_000_000); - mExternalStatsSync.updateCpuStats(100, 1_100_000, 1_100_000); - // Device was suspended for 3_000 seconds, note the difference in elapsed time and uptime mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100, /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0, 5_000_000, 2_000_000, 5_000_000); - mExternalStatsSync.updateCpuStats(200, 5_100_000, 2_100_000); - // Battery level is unchanged, so we don't write battery level details in history - mBatteryStats.noteAlarmStartLocked("wakeup", null, APP_UID, 6_000_000, 3_000_000); + mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, + 100, /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0, + 5_500_000, 2_500_000, 5_000_000); - assertThat(mExternalStatsSync.isSyncScheduled()).isFalse(); + // Not a battery state change event, so details are not written + mBatteryStats.noteAlarmStartLocked("wakeup", null, APP_UID, 6_000_000, 3_000_000); // Battery level drops, so we write the accumulated battery level details mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, - 100, /* plugType */ 0, 79, 72, 3700, 2_000_000, 4_000_000, 0, + 100, /* plugType */ 0, 70, 72, 3700, 2_000_000, 4_000_000, 0, 7_000_000, 4_000_000, 6_000_000); - mExternalStatsSync.updateCpuStats(300, 7_100_000, 4_100_000); - final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory(0, MonotonicClock.UNDEFINED); BatteryStats.HistoryItem item; assertThat(item = iterator.next()).isNotNull(); assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_RESET); - assertThat(item.stepDetails).isNull(); assertThat(item = iterator.next()).isNotNull(); assertThat(item.batteryLevel).isEqualTo(90); - assertThat(item.stepDetails).isNull(); - - assertThat(item = iterator.next()).isNotNull(); - assertThat(item.batteryLevel).isEqualTo(90); - assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS); + assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isNotEqualTo(0); assertThat(item = iterator.next()).isNotNull(); assertThat(item.batteryLevel).isEqualTo(90); assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isEqualTo(0); - assertThat(item.stepDetails.userTime).isEqualTo(100); assertThat(item = iterator.next()).isNotNull(); assertThat(item.batteryLevel).isEqualTo(80); assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isNotEqualTo(0); - assertThat(item.stepDetails.userTime).isEqualTo(0); - - assertThat(item = iterator.next()).isNotNull(); - assertThat(item.batteryLevel).isEqualTo(80); - assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS); assertThat(item = iterator.next()).isNotNull(); assertThat(item.batteryLevel).isEqualTo(80); assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_ALARM_START); - assertThat(item.stepDetails).isNull(); - - assertThat(item = iterator.next()).isNotNull(); - assertThat(item.batteryLevel).isEqualTo(79); assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isNotEqualTo(0); - assertThat(item.stepDetails.userTime).isEqualTo(200); assertThat(item = iterator.next()).isNotNull(); - assertThat(item.batteryLevel).isEqualTo(79); - assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS); + assertThat(item.batteryLevel).isEqualTo(70); + assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isNotEqualTo(0); - assertThat(item = iterator.next()).isNull(); + assertThat(iterator.next()).isNull(); } @Test @@ -394,26 +373,4 @@ public class BatteryStatsHistoryIteratorTest { assertThat(item.time).isEqualTo(elapsedTimeMs); } - - private class MockExternalStatsSync extends MockBatteryStatsImpl.DummyExternalStatsSync { - private boolean mSyncScheduled; - - @Override - public void scheduleCpuSyncDueToWakelockChange(long delayMillis) { - mSyncScheduled = true; - } - - public boolean isSyncScheduled() { - return mSyncScheduled; - } - - public void updateCpuStats(int totalUTimeMs, long elapsedRealtime, long uptime) { - assertThat(mExternalStatsSync.mSyncScheduled).isTrue(); - mBatteryStats.recordHistoryEventLocked(elapsedRealtime, uptime, - BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS, "wakelock-update", 0); - mBatteryStats.addCpuStatsLocked(totalUTimeMs, 0, 0, 0, 0, 0, 0, 0); - mBatteryStats.finishAddingCpuStatsLocked(); - mExternalStatsSync.mSyncScheduled = false; - } - } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index 3ed4a52bed23..7acb93c0641e 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -26,7 +26,6 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import android.os.BatteryConsumer; import android.os.BatteryManager; @@ -58,7 +57,8 @@ import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.io.File; import java.io.IOException; @@ -85,6 +85,8 @@ public class BatteryStatsHistoryTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); private final Parcel mHistoryBuffer = Parcel.obtain(); private File mSystemDir; @@ -97,14 +99,11 @@ public class BatteryStatsHistoryTest { @Mock private BatteryStatsHistory.TraceDelegate mTracer; @Mock - private BatteryStatsHistory.HistoryStepDetailsCalculator mStepDetailsCalculator; - @Mock private BatteryStatsHistory.EventLogger mEventLogger; private List<String> mReadFiles = new ArrayList<>(); @Before public void setUp() throws IOException { - MockitoAnnotations.initMocks(this); mSystemDir = Files.createTempDirectory("BatteryStatsHistoryTest").toFile(); mHistoryDir = new File(mSystemDir, "battery-history"); String[] files = mHistoryDir.list(); @@ -138,14 +137,10 @@ public class BatteryStatsHistoryTest { mClock.currentTime = 1743645660000L; // 2025-04-03, 2:01:00 AM mHistory = new BatteryStatsHistory(mHistoryBuffer, MAX_HISTORY_BUFFER_SIZE, mDirectory, - mStepDetailsCalculator, mClock, mMonotonicClock, mTracer, + mClock, mMonotonicClock, mTracer, mEventLogger); mHistory.forceRecordAllHistory(); mHistory.startRecordingHistory(mClock.realtime, mClock.uptime, false); - - when(mStepDetailsCalculator.getHistoryStepDetails()) - .thenReturn(new BatteryStats.HistoryStepDetails()); - mHistoryPrinter = new BatteryStats.HistoryPrinter(TimeZone.getTimeZone("GMT")); } @@ -288,7 +283,7 @@ public class BatteryStatsHistoryTest { // create a new BatteryStatsHistory object, it will pick up existing history files. BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, 1024, mDirectory, - null, mClock, mMonotonicClock, mTracer, mEventLogger); + mClock, mMonotonicClock, mTracer, mEventLogger); // verify constructor can pick up all files from file system. verifyFileNames(history2, fileList); verifyActiveFile(history2, "33000.bh"); @@ -595,7 +590,7 @@ public class BatteryStatsHistoryTest { // Keep the preserved part of history short - we only need to capture the very tail of // history. mHistory = new BatteryStatsHistory(mHistoryBuffer, 6000, mDirectory, - mStepDetailsCalculator, mClock, mMonotonicClock, mTracer, mEventLogger); + mClock, mMonotonicClock, mTracer, mEventLogger); mHistory.forceRecordAllHistory(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java index 8a1d37b55255..90b3e132f9d3 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java @@ -575,7 +575,7 @@ public class BatteryUsageStatsProviderTest { accumulateBatteryUsageStats(batteryStats, 10000000, 0); // Accumulate every 200 bytes of battery history accumulateBatteryUsageStats(batteryStats, 200, 1); - accumulateBatteryUsageStats(batteryStats, 50, 5); + accumulateBatteryUsageStats(batteryStats, 50, 4); // Accumulate on every invocation of accumulateBatteryUsageStats accumulateBatteryUsageStats(batteryStats, 0, 7); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index c7a19ce7b233..8c3bd17c5acd 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -329,10 +329,6 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { } @Override - public void scheduleSyncDueToBatteryLevelChange(long delayMillis) { - } - - @Override public void scheduleSyncDueToProcessStateChange(int flags, long delayMillis) { } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockPowerStatsInternal.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockPowerStatsInternal.java new file mode 100644 index 000000000000..dc5da8c2841f --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockPowerStatsInternal.java @@ -0,0 +1,198 @@ +/* + * 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.server.power.stats; + +import static org.junit.Assert.assertNotNull; + +import android.hardware.power.stats.Channel; +import android.hardware.power.stats.EnergyConsumer; +import android.hardware.power.stats.EnergyConsumerResult; +import android.hardware.power.stats.EnergyConsumerType; +import android.hardware.power.stats.EnergyMeasurement; +import android.hardware.power.stats.PowerEntity; +import android.hardware.power.stats.State; +import android.hardware.power.stats.StateResidency; +import android.hardware.power.stats.StateResidencyResult; +import android.power.PowerStatsInternal; +import android.util.SparseArray; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +class MockPowerStatsInternal extends PowerStatsInternal { + private final SparseArray<PowerEntity> mPowerEntities = new SparseArray<>(); + private final SparseArray<StateResidencyResult> mStateResidencyResults = new SparseArray<>(); + private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray<>(); + private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults = new SparseArray<>(); + private final int mTimeSinceBoot = 0; + + @Override + public EnergyConsumer[] getEnergyConsumerInfo() { + final int size = mEnergyConsumers.size(); + final EnergyConsumer[] consumers = new EnergyConsumer[size]; + for (int i = 0; i < size; i++) { + consumers[i] = mEnergyConsumers.valueAt(i); + } + return consumers; + } + + @Override + public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync( + int[] energyConsumerIds) { + final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture(); + final EnergyConsumerResult[] results; + final int length = energyConsumerIds.length; + if (length == 0) { + final int size = mEnergyConsumerResults.size(); + results = new EnergyConsumerResult[size]; + for (int i = 0; i < size; i++) { + results[i] = mEnergyConsumerResults.valueAt(i); + } + } else { + results = new EnergyConsumerResult[length]; + for (int i = 0; i < length; i++) { + results[i] = mEnergyConsumerResults.get(energyConsumerIds[i]); + } + } + future.complete(results); + return future; + } + + @Override + public PowerEntity[] getPowerEntityInfo() { + final int size = mPowerEntities.size(); + final PowerEntity[] entities = new PowerEntity[size]; + for (int i = 0; i < size; i++) { + entities[i] = mPowerEntities.valueAt(i); + } + return entities; + } + + @Override + public CompletableFuture<StateResidencyResult[]> getStateResidencyAsync( + int[] powerEntityIds) { + final CompletableFuture<StateResidencyResult[]> future = new CompletableFuture<>(); + final StateResidencyResult[] results; + final int length = powerEntityIds.length; + if (length == 0) { + final int size = mStateResidencyResults.size(); + results = new StateResidencyResult[size]; + for (int i = 0; i < size; i++) { + results[i] = mStateResidencyResults.valueAt(i); + } + } else { + results = new StateResidencyResult[length]; + for (int i = 0; i < length; i++) { + results[i] = mStateResidencyResults.get(powerEntityIds[i]); + } + } + future.complete(results); + return future; + } + + @Override + public Channel[] getEnergyMeterInfo() { + return new Channel[0]; + } + + @Override + public CompletableFuture<EnergyMeasurement[]> readEnergyMeterAsync( + int[] channelIds) { + return new CompletableFuture<>(); + } + + public void reset() { + mStateResidencyResults.clear(); + mEnergyConsumerResults.clear(); + } + + public void addPowerEntity(int id, String name) { + PowerEntity powerEntity = new PowerEntity(); + powerEntity.id = id; + powerEntity.name = name; + powerEntity.states = new State[0]; + mPowerEntities.put(id, powerEntity); + } + + public void addPowerEntityState(int powerEntityId, int stateId, String name) { + State state = new State(); + state.id = stateId; + state.name = name; + + PowerEntity powerEntity = mPowerEntities.get(powerEntityId); + powerEntity.states = Arrays.copyOf(powerEntity.states, powerEntity.states.length + 1); + powerEntity.states[powerEntity.states.length - 1] = state; + } + + public void addStateResidencyResult(int entityId, int stateId, long totalTimeInStateMs, + long totalStateEntryCount, long lastEntryTimestampMs) { + StateResidencyResult result = mStateResidencyResults.get(entityId); + if (result == null) { + result = new StateResidencyResult(); + result.id = entityId; + result.stateResidencyData = new StateResidency[0]; + mStateResidencyResults.put(entityId, result); + } + + StateResidency residency = new StateResidency(); + residency.id = stateId; + residency.totalTimeInStateMs = totalTimeInStateMs; + residency.totalStateEntryCount = totalStateEntryCount; + residency.lastEntryTimestampMs = lastEntryTimestampMs; + + result.stateResidencyData = Arrays.copyOf(result.stateResidencyData, + result.stateResidencyData.length + 1); + result.stateResidencyData[result.stateResidencyData.length - 1] = residency; + } + + /** + * Util method to add a new EnergyConsumer for testing + * + * @return the EnergyConsumer id of the new EnergyConsumer + */ + public int addEnergyConsumer(@EnergyConsumerType byte type, int ordinal, String name) { + final EnergyConsumer consumer = new EnergyConsumer(); + final int id = getNextAvailableId(); + consumer.id = id; + consumer.type = type; + consumer.ordinal = ordinal; + consumer.name = name; + mEnergyConsumers.put(id, consumer); + + final EnergyConsumerResult result = new EnergyConsumerResult(); + result.id = id; + result.timestampMs = mTimeSinceBoot; + result.energyUWs = 0; + mEnergyConsumerResults.put(id, result); + return id; + } + + public void incrementEnergyConsumption(int id, long energyUWs) { + EnergyConsumerResult result = mEnergyConsumerResults.get(id, null); + assertNotNull(result); + result.energyUWs += energyUWs; + } + + private int getNextAvailableId() { + final int size = mEnergyConsumers.size(); + // Just return the first index that does not match the key (aka the EnergyConsumer id) + for (int i = size - 1; i >= 0; i--) { + if (mEnergyConsumers.keyAt(i) == i) return i + 1; + } + // Otherwise return the lowest id + return 0; + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsAggregatorTest.java index 73d491c93bb5..7aa59bd03898 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsAggregatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsAggregatorTest.java @@ -59,9 +59,8 @@ public class PowerStatsAggregatorTest { @Before public void setup() throws ParseException { - mHistory = new BatteryStatsHistory(null, 1024, null, - mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock, - mMonotonicClock, mock(BatteryStatsHistory.TraceDelegate.class), null); + mHistory = new BatteryStatsHistory(null, 1024, null, mClock, mMonotonicClock, + mock(BatteryStatsHistory.TraceDelegate.class), null); AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); config.trackPowerComponent(TEST_POWER_COMPONENT) diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java index a5a29f5883b1..b33cb7e6739f 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java @@ -114,8 +114,7 @@ public class PowerStatsExporterTest { mPowerStatsStore = new PowerStatsStore(storeDirectory, new TestHandler()); mDirectory = new BatteryHistoryDirectory(storeDirectory, 0); - mHistory = new BatteryStatsHistory(Parcel.obtain(), 10000, mDirectory, - mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock, + mHistory = new BatteryStatsHistory(Parcel.obtain(), 10000, mDirectory, mClock, mMonotonicClock, null, null); mPowerStatsAggregator = new PowerStatsAggregator(config); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java index 5ac7216194a4..b90019f9537a 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java @@ -29,8 +29,6 @@ import static com.android.server.power.stats.processor.AggregatedPowerStatsConfi import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; - import android.annotation.SuppressLint; import android.os.BatteryConsumer; import android.os.PersistableBundle; @@ -89,7 +87,6 @@ public class WakelockPowerStatsProcessorTest { long[] uidStats = new long[descriptor.uidStatsArrayLength]; BatteryStatsHistory history = new BatteryStatsHistory(null, 10000, null, - mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mStatsRule.getMockClock(), new MonotonicClock(START_TIME, mStatsRule.getMockClock()), null, null); history.forceRecordAllHistory(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java index 33529c3f4375..44870eb5dd49 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java @@ -37,7 +37,6 @@ import android.content.Context; import android.media.AudioDeviceInfo; import android.media.AudioDevicePort; import android.media.AudioManager; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; @@ -164,7 +163,6 @@ public class HearingDevicePhoneCallNotificationControllerTest { } @Test - @EnableFlags(Flags.FLAG_HEARING_INPUT_CHANGE_WHEN_COMM_DEVICE) public void onCallStateChanged_nonHearingDevice_offHookThenIdle_callAddAndRemoveListener() { final ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener> listenerCaptor = ArgumentCaptor.forClass(AudioManager.OnCommunicationDeviceChangedListener.class); @@ -185,7 +183,6 @@ public class HearingDevicePhoneCallNotificationControllerTest { @Test - @EnableFlags(Flags.FLAG_HEARING_INPUT_CHANGE_WHEN_COMM_DEVICE) public void onCallStateChanged_hearingDeviceFromCommunicationDeviceChanged_showNotification() { final ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener> listenerCaptor = ArgumentCaptor.forClass(AudioManager.OnCommunicationDeviceChangedListener.class); @@ -209,7 +206,6 @@ public class HearingDevicePhoneCallNotificationControllerTest { } @Test - @EnableFlags(Flags.FLAG_HEARING_INPUT_CHANGE_WHEN_COMM_DEVICE) public void onCallStateChanged_offHookMultiple_addListenerOnlyOneTime() { AudioDeviceInfo a2dpDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS, AudioManager.DEVICE_OUT_BLUETOOTH_A2DP); diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java index 952d8fa47a34..09acfddacf03 100644 --- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java @@ -51,6 +51,7 @@ import android.os.IThermalStatusListener; import android.os.PowerManager; import android.os.RemoteException; import android.os.Temperature; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; @@ -78,6 +79,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; /** * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server @@ -117,7 +119,8 @@ public class ThermalManagerServiceTest { */ private class ThermalHalFake extends ThermalHalWrapper { private static final int INIT_STATUS = Temperature.THROTTLING_NONE; - private List<Temperature> mTemperatureList = new ArrayList<>(); + private final List<Temperature> mTemperatureList = new ArrayList<>(); + private AtomicInteger mGetCurrentTemperaturesCalled = new AtomicInteger(); private List<CoolingDevice> mCoolingDeviceList = new ArrayList<>(); private List<TemperatureThreshold> mTemperatureThresholdList = initializeThresholds(); @@ -173,6 +176,7 @@ public class ThermalManagerServiceTest { mTemperatureList.add(mUsbPort); mCoolingDeviceList.add(mCpu); mCoolingDeviceList.add(mGpu); + mGetCurrentTemperaturesCalled.set(0); } void enableForecastSkinTemperature() { @@ -188,14 +192,24 @@ public class ThermalManagerServiceTest { mForecastSkinTemperaturesError = true; } + void updateTemperatureList(Temperature... temperatures) { + synchronized (mTemperatureList) { + mTemperatureList.clear(); + mTemperatureList.addAll(Arrays.asList(temperatures)); + } + } + @Override protected List<Temperature> getCurrentTemperatures(boolean shouldFilter, int type) { List<Temperature> ret = new ArrayList<>(); - for (Temperature temperature : mTemperatureList) { - if (shouldFilter && type != temperature.getType()) { - continue; + synchronized (mTemperatureList) { + mGetCurrentTemperaturesCalled.incrementAndGet(); + for (Temperature temperature : mTemperatureList) { + if (shouldFilter && type != temperature.getType()) { + continue; + } + ret.add(temperature); } - ret.add(temperature); } return ret; } @@ -407,7 +421,7 @@ public class ThermalManagerServiceTest { Thread.sleep(CALLBACK_TIMEOUT_MILLI_SEC); resetListenerMock(); int status = Temperature.THROTTLING_SEVERE; - mFakeHal.mTemperatureList = new ArrayList<>(); + mFakeHal.updateTemperatureList(); // Should not notify on non-skin type Temperature newBattery = new Temperature(37, Temperature.TYPE_BATTERY, "batt", status); @@ -537,6 +551,42 @@ public class ThermalManagerServiceTest { } @Test + @DisableFlags({Flags.FLAG_ALLOW_THERMAL_HAL_SKIN_FORECAST}) + public void testGetThermalHeadroom_handlerUpdateTemperatures() + throws RemoteException, InterruptedException { + // test that handler will at least enqueue one message to periodically read temperatures + // even if there is sample seeded from HAL temperature callback + String temperatureName = "skin1"; + Temperature temperature = new Temperature(100, Temperature.TYPE_SKIN, temperatureName, + Temperature.THROTTLING_NONE); + mFakeHal.mCallback.onTemperatureChanged(temperature); + float headroom = mService.mService.getThermalHeadroom(0); + // the callback temperature 100C (headroom > 1.0f) sample should have been appended by the + // immediately scheduled fake HAL current temperatures read (mSkin1, mSkin2), and because + // there are less samples for prediction, the latest temperature mSkin1 is used to calculate + // headroom (mSkin2 has no threshold), which is 0.6f (28C vs threshold 40C). + assertEquals(0.6f, headroom, 0.01f); + // one called by service onActivityManagerReady, one called by handler on headroom call + assertEquals(2, mFakeHal.mGetCurrentTemperaturesCalled.get()); + // periodic read should update the samples history, so the headroom should increase 0.1f + // as current temperature goes up by 3C every 1100ms. + for (int i = 1; i < 5; i++) { + Temperature newTemperature = new Temperature(mFakeHal.mSkin1.getValue() + 3 * i, + Temperature.TYPE_SKIN, + temperatureName, + Temperature.THROTTLING_NONE); + mFakeHal.updateTemperatureList(newTemperature); + // wait for handler to update temperature + Thread.sleep(1100); + // assert that only one callback was scheduled to query HAL when making multiple + // headroom calls + assertEquals(2 + i, mFakeHal.mGetCurrentTemperaturesCalled.get()); + headroom = mService.mService.getThermalHeadroom(0); + assertEquals(0.6f + 0.1f * i, headroom, 0.01f); + } + } + + @Test @EnableFlags({Flags.FLAG_ALLOW_THERMAL_HAL_SKIN_FORECAST}) public void testGetThermalHeadroom_halForecast() throws RemoteException { mFakeHal.mForecastSkinTemperaturesCalled = 0; diff --git a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java index 339bac4f768b..d6b3fecb487c 100644 --- a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java @@ -45,6 +45,8 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.List; +import java.util.Map; +import java.util.HashMap; import java.util.concurrent.atomic.AtomicBoolean; @SuppressLint("VisibleForTests") @@ -66,17 +68,28 @@ public class AdvancedProtectionServiceTest { mPermissionEnforcer.grant(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE); mStore = new AdvancedProtectionService.AdvancedProtectionStore(mContext) { + private Map<String, Integer> mStoredValues = new HashMap<>(); private boolean mEnabled = false; @Override - boolean retrieve() { + boolean retrieveAdvancedProtectionModeEnabled() { return mEnabled; } @Override - void store(boolean enabled) { + void storeAdvancedProtectionModeEnabled(boolean enabled) { this.mEnabled = enabled; } + + @Override + void storeInt(String key, int value) { + mStoredValues.put(key, value); + } + + @Override + int retrieveInt(String key, int defaultValue) { + return mStoredValues.getOrDefault(key, defaultValue); + } }; mLooper = new TestLooper(); @@ -316,6 +329,18 @@ public class AdvancedProtectionServiceTest { } @Test + public void testUsbDataProtection_withoutPermission() { + mPermissionEnforcer.revoke(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE); + assertThrows(SecurityException.class, () -> mService.isUsbDataProtectionEnabled()); + } + + @Test + public void testSetUsbDataProtection_withoutPermission() { + mPermissionEnforcer.revoke(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE); + assertThrows(SecurityException.class, () -> mService.setUsbDataProtectionEnabled(true)); + } + + @Test public void testRegisterCallback_withoutPermission() { mPermissionEnforcer.revoke(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE); assertThrows(SecurityException.class, () -> mService.registerAdvancedProtectionCallback( diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 902171d614d9..8c9b9bd03b9f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -18657,4 +18657,37 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mGroupHelper).onNotificationRemoved(eq(n), any(), eq(false)); } + + @Test + public void onDisplayRemoveSystemDecorations_cancelToasts() throws RemoteException { + final String testPackage = "testPackageName"; + final INotificationManager service = ((INotificationManager) mService.mService); + final IBinder firstExternal = new Binder(); + final IBinder secondExternal = new Binder(); + final IBinder firstBuiltin = new Binder(); + service.enqueueTextToast(testPackage, + firstExternal, "First external", TOAST_DURATION, + /* isUiContext= */ true, /* displayId= */ 10, /* callback= */ null); + service.enqueueTextToast(testPackage, + secondExternal, "Second external", TOAST_DURATION, + /* isUiContext= */ true, /* displayId= */ 10, /* callback= */ null); + service.enqueueTextToast(testPackage, + firstBuiltin, "First built-in", TOAST_DURATION, /* isUiContext= */ true, + /* displayId= */ DEFAULT_DISPLAY, /* callback= */ null); + + mInternalService.onDisplayRemoveSystemDecorations(10); + + verify(mStatusBar).showToast(anyInt(), eq(testPackage), eq(firstExternal), + any(String.class), any(IBinder.class), anyInt(), any(), eq(10)); + verify(mStatusBar).hideToast(eq(testPackage), eq(firstExternal)); + // The second toast has not been shown but invokes hide() anyway as + // NotificationManagerService does not remembered if it invoked show(). + verify(mStatusBar, never()).showToast(anyInt(), eq(testPackage), eq(secondExternal), + any(String.class), any(IBinder.class), anyInt(), any(), eq(10)); + verify(mStatusBar).hideToast(eq(testPackage), eq(secondExternal)); + // The toast on the default display is shown as other notifications are cancelled. + verify(mStatusBar).showToast(anyInt(), eq(testPackage), eq(firstBuiltin), any(String.class), + any(IBinder.class), anyInt(), any(), eq(DEFAULT_DISPLAY)); + verify(mStatusBar, never()).hideToast(eq(testPackage), eq(firstBuiltin)); + } } diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java index 0495e967c0e3..5ab00361d3c0 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -491,6 +491,11 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { @Test public void testKeyGestureBack() { + mPhoneWindowManager.overrideDelegateBackGestureRemote(true); + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BACK); + mPhoneWindowManager.assertBackEventInjected(); + + mPhoneWindowManager.overrideDelegateBackGestureRemote(false); sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BACK); mPhoneWindowManager.assertBackEventInjected(); } diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 7b6d361c55d4..7059c41898f3 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -202,6 +202,7 @@ class TestPhoneWindowManager { private boolean mIsTalkBackEnabled; private boolean mIsTalkBackShortcutGestureEnabled; + private boolean mDelegateBackGestureRemote; private boolean mIsVoiceAccessEnabled; private Intent mBrowserIntent; @@ -580,6 +581,12 @@ class TestPhoneWindowManager { setPhoneCallIsInProgress(); } + void overrideDelegateBackGestureRemote(boolean isDelegating) { + mDelegateBackGestureRemote = isDelegating; + doReturn(mDelegateBackGestureRemote).when(mActivityTaskManagerInternal) + .requestBackGesture(); + } + void prepareBrightnessDecrease(float currentBrightness) { doReturn(0.0f).when(mPowerManager).getBrightnessConstraint( DEFAULT_DISPLAY, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); @@ -661,13 +668,21 @@ class TestPhoneWindowManager { } void assertBackEventInjected() { - ArgumentCaptor<InputEvent> intentCaptor = ArgumentCaptor.forClass(InputEvent.class); - verify(mInputManager, times(2)).injectInputEvent(intentCaptor.capture(), anyInt()); - List<InputEvent> inputEvents = intentCaptor.getAllValues(); - Assert.assertEquals(KeyEvent.KEYCODE_BACK, ((KeyEvent) inputEvents.get(0)).getKeyCode()); - Assert.assertEquals(KeyEvent.KEYCODE_BACK, ((KeyEvent) inputEvents.get(1)).getKeyCode()); - // Reset verifier for next call. - Mockito.clearInvocations(mContext); + if (mDelegateBackGestureRemote) { + Mockito.verify(mActivityTaskManagerInternal).requestBackGesture(); + ArgumentCaptor<InputEvent> intentCaptor = ArgumentCaptor.forClass(InputEvent.class); + verify(mInputManager, never()).injectInputEvent(intentCaptor.capture(), anyInt()); + } else { + ArgumentCaptor<InputEvent> intentCaptor = ArgumentCaptor.forClass(InputEvent.class); + verify(mInputManager, times(2)).injectInputEvent(intentCaptor.capture(), anyInt()); + List<InputEvent> inputEvents = intentCaptor.getAllValues(); + Assert.assertEquals(KeyEvent.KEYCODE_BACK, + ((KeyEvent) inputEvents.get(0)).getKeyCode()); + Assert.assertEquals(KeyEvent.KEYCODE_BACK, + ((KeyEvent) inputEvents.get(1)).getKeyCode()); + // Reset verifier for next call. + Mockito.clearInvocations(mContext); + } } void overrideSearchKeyBehavior(int behavior) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java index 521d8364a31f..449ca867b987 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java @@ -52,6 +52,8 @@ import android.view.Surface; import androidx.test.filters.SmallTest; import com.android.server.LocalServices; +import com.android.server.UiThread; +import com.android.server.notification.NotificationManagerInternal; import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry; import com.android.window.flags.Flags; @@ -59,6 +61,7 @@ import com.android.window.flags.Flags; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import java.util.HashMap; import java.util.Map; @@ -387,6 +390,30 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { } @Test + @EnableFlags(com.android.server.display.feature.flags.Flags + .FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT) + public void testSetShouldShowSystemDecorsNotifyNotificationManager() { + final NotificationManagerInternal notificationManager = Mockito.mock( + NotificationManagerInternal.class); + LocalServices.addService(NotificationManagerInternal.class, notificationManager); + try { + // First show the decoration because setting false is noop if the decoration has already + // been hidden. + mDisplayWindowSettings.setShouldShowSystemDecorsLocked( + mSecondaryDisplay, /* shouldShow= */ true); + + mDisplayWindowSettings.setShouldShowSystemDecorsLocked( + mSecondaryDisplay, /* shouldShow= */ false); + + waitHandlerIdle(UiThread.getHandler()); + Mockito.verify(notificationManager).onDisplayRemoveSystemDecorations( + mSecondaryDisplay.mDisplayId); + } finally { + LocalServices.removeServiceForTest(NotificationManagerInternal.class); + } + } + + @Test public void testPrimaryDisplayImePolicy() { assertEquals(DISPLAY_IME_POLICY_LOCAL, mDisplayWindowSettings.getImePolicyLocked(mPrimaryDisplay)); diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp index 8fbc3e8a78db..a103d03af3c3 100644 --- a/tools/protologtool/Android.bp +++ b/tools/protologtool/Android.bp @@ -11,13 +11,13 @@ java_library_host { name: "protologtool-lib", srcs: [ "src/com/android/protolog/tool/**/*.kt", - ":protolog-common-src", ], static_libs: [ "javaparser", - "platformprotos", "jsonlib", "perfetto_trace-full", + "platformprotos", + "protolog-common-lib", ], } @@ -39,10 +39,10 @@ java_test_host { unit_test: true, }, static_libs: [ - "protologtool-lib", "junit", "mockito", "objenesis", + "protologtool-lib", "truth", ], } diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt index 47724b7a9e1d..03c3a15562a9 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt @@ -24,5 +24,5 @@ interface ProtoLogCallProcessor { logCallVisitor: ProtoLogCallVisitor?, otherCallVisitor: MethodCallVisitor?, fileName: String - ): CompilationUnit + ): Collection<CodeProcessingException> } diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt index 1f6db5fe2d37..44db2ba45845 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt @@ -74,7 +74,7 @@ class ProtoLogCallProcessorImpl( } fun process(code: CompilationUnit, logCallVisitor: ProtoLogCallVisitor?, fileName: String): - CompilationUnit { + Collection<CodeProcessingException> { return process(code, logCallVisitor, null, fileName) } @@ -83,7 +83,7 @@ class ProtoLogCallProcessorImpl( logCallVisitor: ProtoLogCallVisitor?, otherCallVisitor: MethodCallVisitor?, fileName: String - ): CompilationUnit { + ): Collection<CodeProcessingException> { CodeUtils.checkWildcardStaticImported(code, protoLogClassName, fileName) CodeUtils.checkWildcardStaticImported(code, protoLogGroupClassName, fileName) @@ -93,50 +93,63 @@ class ProtoLogCallProcessorImpl( protoLogGroupClassName) val staticGroupImports = CodeUtils.staticallyImportedMethods(code, protoLogGroupClassName) + val errors = mutableListOf<CodeProcessingException>() code.findAll(MethodCallExpr::class.java) .filter { call -> isProtoCall(call, isLogClassImported, staticLogImports) }.forEach { call -> val context = ParsingContext(fileName, call) - val logMethods = LogLevel.entries.map { it.shortCode } - if (logMethods.contains(call.name.id)) { - // Process a log call - if (call.arguments.size < 2) { - throw InvalidProtoLogCallException("Method signature does not match " + - "any ProtoLog method: $call", context) - } + try { + val logMethods = LogLevel.entries.map { it.shortCode } + if (logMethods.contains(call.name.id)) { + // Process a log call + if (call.arguments.size < 2) { + errors.add(InvalidProtoLogCallException( + "Method signature does not match " + + "any ProtoLog method: $call", context + )) + return@forEach + } - val messageString = CodeUtils.concatMultilineString(call.getArgument(1), - context) - val groupNameArg = call.getArgument(0) - val groupName = - getLogGroupName(groupNameArg, isGroupClassImported, - staticGroupImports, fileName) - if (groupName !in groupMap) { - throw InvalidProtoLogCallException("Unknown group argument " + - "- not a ProtoLogGroup enum member: $call", context) - } + val messageString = CodeUtils.concatMultilineString( + call.getArgument(1), + context + ) + val groupNameArg = call.getArgument(0) + val groupName = + getLogGroupName( + groupNameArg, isGroupClassImported, + staticGroupImports, fileName + ) + if (groupName !in groupMap) { + errors.add(InvalidProtoLogCallException( + "Unknown group argument " + + "- not a ProtoLogGroup enum member: $call", context + )) + return@forEach + } - try { logCallVisitor?.processCall( call, messageString, getLevelForMethodName( call.name.toString(), call, context ), groupMap.getValue(groupName), context.lineNumber ) - } catch (e: Throwable) { - throw InvalidProtoLogCallException("Error processing log call: $call", - context, e) + } else if (call.name.id == "init") { + // No processing + } else { + // Process non-log message calls + otherCallVisitor?.processCall(call) } - } else if (call.name.id == "init") { - // No processing - } else { - // Process non-log message calls - otherCallVisitor?.processCall(call) + } catch (e: Throwable) { + errors.add(InvalidProtoLogCallException( + "Error processing log call: $call", + context, e + )) } } - return code + return errors } private fun getLevelForMethodName( diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt index d8a2954545bb..e9f2e81b69db 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt @@ -130,14 +130,16 @@ object ProtoLogTool { val outSrc = try { val code = tryParse(text, path) if (containsProtoLogText(text, PROTOLOG_CLASS_NAME)) { - transformer.processClass(text, path, packagePath(file, code), code) + val (processedText, errors) = transformer.processClass(text, path, packagePath(file, code), code) + errors.forEach { injector.reportProcessingError(it) } + processedText } else { text } } catch (ex: ParsingException) { // If we cannot parse this file, skip it (and log why). Compilation will // fail in a subsequent build step. - injector.reportParseError(ex) + injector.reportProcessingError(ex) text } path to outSrc @@ -405,7 +407,7 @@ object ProtoLogTool { } catch (ex: ParsingException) { // If we cannot parse this file, skip it (and log why). Compilation will // fail in a subsequent build step. - injector.reportParseError(ex) + injector.reportProcessingError(ex) null } } else { @@ -466,6 +468,14 @@ object ProtoLogTool { try { val command = CommandOptions(args) invoke(command) + + if (injector.processingErrors.isNotEmpty()) { + injector.processingErrors.forEachIndexed { index, it -> + println("CodeProcessingException " + + "(${index + 1}/${injector.processingErrors.size}): \n${it.message}\n") + } + exitProcess(1) + } } catch (ex: InvalidCommandException) { println("InvalidCommandException: \n${ex.message}\n") showHelpAndExit() @@ -489,12 +499,14 @@ object ProtoLogTool { } var injector = object : Injector { + override val processingErrors: MutableList<CodeProcessingException> + get() = mutableListOf() override fun fileOutputStream(file: String) = FileOutputStream(file) override fun readText(file: File) = file.readText() override fun readLogGroups(jarPath: String, className: String) = ProtoLogGroupReader().loadFromJar(jarPath, className) - override fun reportParseError(ex: ParsingException) { - println("\n${ex.message}\n") + override fun reportProcessingError(ex: CodeProcessingException) { + processingErrors.add(ex) } } @@ -502,7 +514,8 @@ object ProtoLogTool { fun fileOutputStream(file: String): OutputStream fun readText(file: File): String fun readLogGroups(jarPath: String, className: String): Map<String, LogGroup> - fun reportParseError(ex: ParsingException) + fun reportProcessingError(ex: CodeProcessingException) + val processingErrors: Collection<CodeProcessingException> } } diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt index 76df0674df65..e97b0dd9a04b 100644 --- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt +++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt @@ -66,13 +66,13 @@ class SourceTransformer( packagePath: String, compilationUnit: CompilationUnit = StaticJavaParser.parse(code) - ): String { + ): Pair<String, Collection<CodeProcessingException>> { this.path = path this.packagePath = packagePath processedCode = code.split('\n').toMutableList() offsets = IntArray(processedCode.size) - protoLogCallProcessor.process(compilationUnit, protoLogCallVisitor, otherCallVisitor, path) - return processedCode.joinToString("\n") + val processingErrors = protoLogCallProcessor.process(compilationUnit, protoLogCallVisitor, otherCallVisitor, path) + return Pair(processedCode.joinToString("\n"), processingErrors) } private val protoLogImplClassNode = diff --git a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt index 0cbbd483fe59..71bab3ebf2db 100644 --- a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt @@ -17,6 +17,7 @@ package com.android.protolog.tool import com.android.protolog.tool.ProtoLogTool.PROTOLOG_IMPL_SRC_PATH +import com.android.protolog.tool.ProtoLogTool.injector import com.google.common.truth.Truth import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -102,6 +103,42 @@ class EndToEndTest { """.trimIndent()) } + @Test + fun e2e_transform_withErrors() { + val srcs = mapOf( + "frameworks/base/org/example/Example.java" to """ + package org.example; + import com.android.internal.protolog.ProtoLog; + import static com.android.internal.protolog.ProtoLogGroup.GROUP; + + class Example { + void method() { + String argString = "hello"; + int argInt = 123; + ProtoLog.d(GROUP, "Invalid format: %s %d %9 %", argString, argInt); + } + } + """.trimIndent()) + val output = run( + srcs = srcs, + logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"), + commandOptions = CommandOptions(arrayOf("transform-protolog-calls", + "--protolog-class", "com.android.internal.protolog.ProtoLog", + "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup", + "--loggroups-jar", "not_required.jar", + "--viewer-config-file-path", "not_required.pb", + "--output-srcjar", "out.srcjar", + "frameworks/base/org/example/Example.java")) + ) + val outSrcJar = assertLoadSrcJar(output, "out.srcjar") + // No change to source code on failure to process + Truth.assertThat(outSrcJar["frameworks/base/org/example/Example.java"]) + .contains(srcs["frameworks/base/org/example/Example.java"]) + + Truth.assertThat(injector.processingErrors).hasSize(1) + Truth.assertThat(injector.processingErrors.first().message).contains("Invalid format") + } + private fun assertLoadSrcJar( outputs: Map<String, ByteArray>, path: String @@ -172,7 +209,11 @@ class EndToEndTest { override fun readLogGroups(jarPath: String, className: String) = mapOf( logGroup.name to logGroup) - override fun reportParseError(ex: ParsingException) = throw AssertionError(ex) + override fun reportProcessingError(ex: CodeProcessingException) { + processingErrors.add(ex) + } + + override val processingErrors: MutableList<CodeProcessingException> = mutableListOf() } ProtoLogTool.invoke(commandOptions) diff --git a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt index 732824ae841c..8f765aecb431 100644 --- a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt @@ -21,7 +21,6 @@ import com.android.internal.protolog.common.LogLevel import com.github.javaparser.StaticJavaParser import com.github.javaparser.ast.expr.MethodCallExpr import org.junit.Assert.assertEquals -import org.junit.Assert.assertThrows import org.junit.Test import com.google.common.truth.Truth @@ -124,7 +123,7 @@ class ProtoLogCallProcessorImplTest { checkCalls() } - @Test(expected = InvalidProtoLogCallException::class) + @Test fun process_groupNotImported() { val code = """ package org.example2; @@ -138,7 +137,10 @@ class ProtoLogCallProcessorImplTest { } """ groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager") - visitor.process(StaticJavaParser.parse(code), processor, "") + val errors = visitor.process(StaticJavaParser.parse(code), processor, "") + + Truth.assertThat(errors).hasSize(1) + Truth.assertThat(errors.first()).isInstanceOf(InvalidProtoLogCallException::class.java) } @Test @@ -159,7 +161,7 @@ class ProtoLogCallProcessorImplTest { assertEquals(0, calls.size) } - @Test(expected = InvalidProtoLogCallException::class) + @Test fun process_unknownGroup() { val code = """ package org.example; @@ -170,10 +172,13 @@ class ProtoLogCallProcessorImplTest { } } """ - visitor.process(StaticJavaParser.parse(code), processor, "") + val errors = visitor.process(StaticJavaParser.parse(code), processor, "") + + Truth.assertThat(errors).hasSize(1) + Truth.assertThat(errors.first()).isInstanceOf(InvalidProtoLogCallException::class.java) } - @Test(expected = InvalidProtoLogCallException::class) + @Test fun process_staticGroup() { val code = """ package org.example; @@ -184,10 +189,13 @@ class ProtoLogCallProcessorImplTest { } } """ - visitor.process(StaticJavaParser.parse(code), processor, "") + val errors = visitor.process(StaticJavaParser.parse(code), processor, "") + + Truth.assertThat(errors).hasSize(1) + Truth.assertThat(errors.first()).isInstanceOf(InvalidProtoLogCallException::class.java) } - @Test(expected = InvalidProtoLogCallException::class) + @Test fun process_badGroup() { val code = """ package org.example; @@ -198,10 +206,13 @@ class ProtoLogCallProcessorImplTest { } } """ - visitor.process(StaticJavaParser.parse(code), processor, "") + val errors = visitor.process(StaticJavaParser.parse(code), processor, "") + + Truth.assertThat(errors).hasSize(1) + Truth.assertThat(errors.first()).isInstanceOf(InvalidProtoLogCallException::class.java) } - @Test(expected = InvalidProtoLogCallException::class) + @Test fun process_invalidSignature() { val code = """ package org.example; @@ -212,7 +223,10 @@ class ProtoLogCallProcessorImplTest { } } """ - visitor.process(StaticJavaParser.parse(code), processor, "") + val errors = visitor.process(StaticJavaParser.parse(code), processor, "") + + Truth.assertThat(errors).hasSize(1) + Truth.assertThat(errors.first()).isInstanceOf(InvalidProtoLogCallException::class.java) } @Test @@ -257,9 +271,10 @@ class ProtoLogCallProcessorImplTest { } } - val exception = assertThrows(InvalidProtoLogCallException::class.java) { - visitor.process(StaticJavaParser.parse(code), processor, "MyTestFile.java") - } + val errors = visitor.process(StaticJavaParser.parse(code), processor, "MyTestFile.java") + Truth.assertThat(errors).hasSize(1) + + val exception = errors.first() Truth.assertThat(exception).hasMessageThat() .contains("Code processing error in MyTestFile.java:6") Truth.assertThat(exception.cause).hasMessageThat() diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt index 674a907d68d9..271fc6064678 100644 --- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt @@ -160,10 +160,10 @@ class SourceTransformerTest { visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f", LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"), 123) - invocation.arguments[0] as CompilationUnit + listOf<CodeProcessingException>() } - val out = sourceJarWriter.processClass(TEST_CODE, PATH, PATH, code) + val (out, _) = sourceJarWriter.processClass(TEST_CODE, PATH, PATH, code) code = StaticJavaParser.parse(out) val protoLogCalls = code.findAll(MethodCallExpr::class.java).filter { @@ -201,10 +201,10 @@ class SourceTransformerTest { visitor.processCall(calls[2], "test %d %f", LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"), 123) - invocation.arguments[0] as CompilationUnit + listOf<CodeProcessingException>() } - val out = sourceJarWriter.processClass(TEST_CODE_MULTICALLS, PATH, PATH, code) + val (out, _) = sourceJarWriter.processClass(TEST_CODE_MULTICALLS, PATH, PATH, code) code = StaticJavaParser.parse(out) val protoLogCalls = code.findAll(MethodCallExpr::class.java).filter { @@ -238,10 +238,10 @@ class SourceTransformerTest { "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"), 123) - invocation.arguments[0] as CompilationUnit + listOf<CodeProcessingException>() } - val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, PATH, code) + val (out, _) = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, PATH, code) code = StaticJavaParser.parse(out) val protoLogCalls = code.findAll(MethodCallExpr::class.java).filter { @@ -275,10 +275,10 @@ class SourceTransformerTest { visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test", LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"), 456) - invocation.arguments[0] as CompilationUnit + listOf<CodeProcessingException>() } - val out = sourceJarWriter.processClass(TEST_CODE_NO_PARAMS, PATH, PATH, code) + val (out, _) = sourceJarWriter.processClass(TEST_CODE_NO_PARAMS, PATH, PATH, code) code = StaticJavaParser.parse(out) val protoLogCalls = code.findAll(MethodCallExpr::class.java).filter { @@ -309,10 +309,10 @@ class SourceTransformerTest { visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f", LogLevel.WARN, LogGroup("TEST_GROUP", true, false, "WM_TEST"), 789) - invocation.arguments[0] as CompilationUnit + listOf<CodeProcessingException>() } - val out = sourceJarWriter.processClass(TEST_CODE, PATH, PATH, code) + val (out, _) = sourceJarWriter.processClass(TEST_CODE, PATH, PATH, code) code = StaticJavaParser.parse(out) val protoLogCalls = code.findAll(MethodCallExpr::class.java).filter { @@ -346,10 +346,10 @@ class SourceTransformerTest { "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP", true, false, "WM_TEST"), 123) - invocation.arguments[0] as CompilationUnit + listOf<CodeProcessingException>() } - val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, PATH, code) + val (out, _) = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, PATH, code) code = StaticJavaParser.parse(out) val protoLogCalls = code.findAll(MethodCallExpr::class.java).filter { |