diff options
958 files changed, 27823 insertions, 9350 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 4d9fdf041d58..12781e404ba9 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -265,6 +265,7 @@ java_aconfig_library { cc_aconfig_library { name: "com.android.window.flags.window-aconfig_flags_c_lib", aconfig_declarations: "com.android.window.flags.window-aconfig", + host_supported: true, } // DeviceStateManager @@ -587,7 +588,6 @@ aconfig_declarations { java_aconfig_library { name: "android.view.inputmethod.flags-aconfig-java", aconfig_declarations: "android.view.inputmethod.flags-aconfig", - host_supported: true, defaults: ["framework-minus-apex-aconfig-java-defaults"], } diff --git a/TEST_MAPPING b/TEST_MAPPING index e469f167d32f..ce0da7e21071 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -25,6 +25,12 @@ "name": "FrameworksUiServicesTests" }, { + "name": "FrameworksUiServicesNotificationTests" + }, + { + "name": "FrameworksUiServicesZenTests" + }, + { "name": "FrameworksInputMethodSystemServerTests_server_inputmethod" }, { diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index 876274ecc32e..aae5bb31273b 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -126,3 +126,15 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "tune_quota_window_default_parameters" + namespace: "backstage_power" + description: "Tune default active/exempted bucket quota parameters" + bug: "401767691" + metadata { + purpose: PURPOSE_BUGFIX + } +} + + diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 54d337eded7d..a9c4a1501dd8 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -360,13 +360,13 @@ public final class QuotaController extends StateController { /** How much time each app will have to run jobs within their standby bucket window. */ private final long[] mAllowedTimePerPeriodMs = new long[]{ - QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + QcConstants.DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS, QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS, 0, // NEVER QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, - QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS + QcConstants.DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS }; /** @@ -3178,9 +3178,11 @@ public final class QuotaController extends StateController { static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS = QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms"; - private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + // Legacy default time each app will have to run jobs within EXEMPTED bucket + private static final long DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = 10 * 60 * 1000L; // 10 minutes - private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + // Legacy default time each app will have to run jobs within ACTIVE bucket + private static final long DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS = 10 * 60 * 1000L; // 10 minutes @@ -3192,14 +3194,26 @@ public final class QuotaController extends StateController { 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = 10 * 60 * 1000L; // 10 minutes + + // Current default time each app will have to run jobs within EXEMPTED bucket + private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + 20 * 60 * 1000L; // 20 minutes + // Current default time each app will have to run jobs within ACTIVE bucket + private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + 20 * 60 * 1000L; // 20 minutes + private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = + 20 * 60 * 1000L; // 20 minutes + private static final long DEFAULT_IN_QUOTA_BUFFER_MS = 30 * 1000L; // 30 seconds // Legacy default window size for EXEMPTED bucket + // EXEMPT apps can run jobs at any time private static final long DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS = - DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // Legacy default window size for ACTIVE bucket + // ACTIVE apps can run jobs at any time private static final long DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS = - DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // ACTIVE apps can run jobs at any time + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // Legacy default window size for WORKING bucket private static final long DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS = 2 * 60 * 60 * 1000L; // 2 hours @@ -3216,6 +3230,13 @@ public final class QuotaController extends StateController { private static final long DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS = 12 * 60 * 60 * 1000L; // 12 hours + // Latest default window size for EXEMPTED bucket. + private static final long DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS = + 40 * 60 * 1000L; // 40 minutes. + // Latest default window size for ACTIVE bucket. + private static final long DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS = + 60 * 60 * 1000L; // 60 minutes. + private static final long DEFAULT_WINDOW_SIZE_RARE_MS = 24 * 60 * 60 * 1000L; // 24 hours private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS = @@ -3276,12 +3297,13 @@ public final class QuotaController extends StateController { * bucket window. */ public long ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = - DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; /** * How much time each app in the active bucket will have to run jobs within their standby * bucket window. */ - public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; /** * How much time each app in the working set bucket will have to run jobs within their * standby bucket window. @@ -3575,11 +3597,30 @@ public final class QuotaController extends StateController { public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS; void adjustDefaultBucketWindowSizes() { - WINDOW_SIZE_EXEMPTED_MS = DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS; - WINDOW_SIZE_ACTIVE_MS = DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS; + ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS : + DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS; + + WINDOW_SIZE_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS : + DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS; + WINDOW_SIZE_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS : + DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS; WINDOW_SIZE_WORKING_MS = DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS; WINDOW_SIZE_FREQUENT_MS = DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS; + mAllowedTimePerPeriodMs[EXEMPTED_INDEX] = Math.min(MAX_PERIOD_MS, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS)); + mAllowedTimePerPeriodMs[ACTIVE_INDEX] = Math.min(MAX_PERIOD_MS, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS)); + mBucketPeriodsMs[EXEMPTED_INDEX] = Math.max( mAllowedTimePerPeriodMs[EXEMPTED_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_EXEMPTED_MS)); @@ -3592,6 +3633,11 @@ public final class QuotaController extends StateController { mBucketPeriodsMs[FREQUENT_INDEX] = Math.max( mAllowedTimePerPeriodMs[FREQUENT_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS)); + + mAllowedTimePeriodAdditionaInstallerMs = + Math.min(mBucketPeriodsMs[EXEMPTED_INDEX] + - mAllowedTimePerPeriodMs[EXEMPTED_INDEX], + ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS); } void adjustDefaultEjLimits() { @@ -3882,10 +3928,14 @@ public final class QuotaController extends StateController { KEY_WINDOW_SIZE_RESTRICTED_MS); ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, - DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS); + Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS); ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, - DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS); + Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS); ALLOWED_TIME_PER_PERIOD_WORKING_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS); @@ -3900,19 +3950,27 @@ public final class QuotaController extends StateController { DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS); ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS, - DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS); + Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS + : DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS); IN_QUOTA_BUFFER_MS = properties.getLong(KEY_IN_QUOTA_BUFFER_MS, DEFAULT_IN_QUOTA_BUFFER_MS); MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS); WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS, - Flags.adjustQuotaDefaultConstants() - ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS : - DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS); + (Flags.adjustQuotaDefaultConstants() + && Flags.tuneQuotaWindowDefaultParameters()) + ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS : + (Flags.adjustQuotaDefaultConstants() + ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS : + DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS)); WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS, - Flags.adjustQuotaDefaultConstants() - ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS : - DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS); + (Flags.adjustQuotaDefaultConstants() + && Flags.tuneQuotaWindowDefaultParameters()) + ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS : + (Flags.adjustQuotaDefaultConstants() + ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS : + DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS)); WINDOW_SIZE_WORKING_MS = properties.getLong(KEY_WINDOW_SIZE_WORKING_MS, Flags.adjustQuotaDefaultConstants() diff --git a/core/api/current.txt b/core/api/current.txt index 1630d80346ce..4cd6d6f03ee8 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -46681,16 +46681,16 @@ package android.telephony { method public int getLastCauseCode(); method @Nullable public android.net.LinkProperties getLinkProperties(); method public int getNetworkType(); - method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public int getNetworkValidationStatus(); + method public int getNetworkValidationStatus(); method public int getState(); method public int getTransportType(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR; - field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_FAILURE = 4; // 0x4 - field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_IN_PROGRESS = 2; // 0x2 - field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_NOT_REQUESTED = 1; // 0x1 - field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_SUCCESS = 3; // 0x3 - field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_UNSUPPORTED = 0; // 0x0 + field public static final int NETWORK_VALIDATION_FAILURE = 4; // 0x4 + field public static final int NETWORK_VALIDATION_IN_PROGRESS = 2; // 0x2 + field public static final int NETWORK_VALIDATION_NOT_REQUESTED = 1; // 0x1 + field public static final int NETWORK_VALIDATION_SUCCESS = 3; // 0x3 + field public static final int NETWORK_VALIDATION_UNSUPPORTED = 0; // 0x0 } public final class RadioAccessSpecifier implements android.os.Parcelable { diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 132c65cc26ee..526a213a6003 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -410,7 +410,6 @@ package android.os { method public void invalidateCache(); method public static void invalidateCache(@NonNull String, @NonNull String); method @Nullable public Result query(@NonNull Query); - method @FlaggedApi("android.os.ipc_data_cache_test_apis") public static void setTestMode(boolean); field public static final String MODULE_BLUETOOTH = "bluetooth"; } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 32b170a6286b..35720fd17769 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2941,6 +2941,7 @@ package android.app.smartspace.uitemplatedata { package android.app.supervision { @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public class SupervisionManager { + method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public android.content.Intent createConfirmSupervisionCredentialsIntent(); method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSupervisionEnabled(); } @@ -3642,11 +3643,26 @@ package android.companion.virtual.sensor { method public int getDeviceId(); method @NonNull public String getName(); method public int getType(); + method @FlaggedApi("android.companion.virtualdevice.flags.virtual_sensor_additional_info") public void sendAdditionalInfo(@NonNull android.companion.virtual.sensor.VirtualSensorAdditionalInfo); method public void sendEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensor> CREATOR; } + @FlaggedApi("android.companion.virtualdevice.flags.virtual_sensor_additional_info") public final class VirtualSensorAdditionalInfo implements android.os.Parcelable { + method public int describeContents(); + method public int getType(); + method @NonNull public java.util.List<float[]> getValues(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorAdditionalInfo> CREATOR; + } + + public static final class VirtualSensorAdditionalInfo.Builder { + ctor public VirtualSensorAdditionalInfo.Builder(int); + method @NonNull public android.companion.virtual.sensor.VirtualSensorAdditionalInfo.Builder addValues(@NonNull float[]); + method @NonNull public android.companion.virtual.sensor.VirtualSensorAdditionalInfo build(); + } + public interface VirtualSensorCallback { method public void onConfigurationChanged(@NonNull android.companion.virtual.sensor.VirtualSensor, boolean, @NonNull java.time.Duration, @NonNull java.time.Duration); } @@ -3664,6 +3680,7 @@ package android.companion.virtual.sensor { method public float getResolution(); method public int getType(); method @Nullable public String getVendor(); + method @FlaggedApi("android.companion.virtualdevice.flags.virtual_sensor_additional_info") public boolean isAdditionalInfoSupported(); method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public boolean isWakeUpSensor(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR; @@ -3672,6 +3689,7 @@ package android.companion.virtual.sensor { public static final class VirtualSensorConfig.Builder { ctor public VirtualSensorConfig.Builder(@IntRange(from=1) int, @NonNull String); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build(); + method @FlaggedApi("android.companion.virtualdevice.flags.virtual_sensor_additional_info") @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setAdditionalInfoSupported(boolean); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setDirectChannelTypesSupported(int); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setHighestDirectReportRateLevel(int); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setMaxDelay(int); @@ -16442,7 +16460,7 @@ package android.telephony.data { method @Deprecated public int getMtu(); method public int getMtuV4(); method public int getMtuV6(); - method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public int getNetworkValidationStatus(); + method public int getNetworkValidationStatus(); method @NonNull public java.util.List<java.net.InetAddress> getPcscfAddresses(); method public int getPduSessionId(); method public int getProtocolType(); @@ -16479,7 +16497,7 @@ package android.telephony.data { method @Deprecated @NonNull public android.telephony.data.DataCallResponse.Builder setMtu(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV4(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV6(int); - method @FlaggedApi("com.android.internal.telephony.flags.network_validation") @NonNull public android.telephony.data.DataCallResponse.Builder setNetworkValidationStatus(int); + method @NonNull public android.telephony.data.DataCallResponse.Builder setNetworkValidationStatus(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setPcscfAddresses(@NonNull java.util.List<java.net.InetAddress>); method @NonNull public android.telephony.data.DataCallResponse.Builder setPduSessionId(@IntRange(from=android.telephony.data.DataCallResponse.PDU_SESSION_ID_NOT_SET, to=15) int); method @NonNull public android.telephony.data.DataCallResponse.Builder setProtocolType(int); @@ -16559,7 +16577,7 @@ package android.telephony.data { method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>); method public final void notifyDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile); method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback); - method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public void requestNetworkValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method public void requestNetworkValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback); method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback); method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @NonNull android.telephony.data.DataServiceCallback); @@ -16621,7 +16639,7 @@ package android.telephony.data { method public final int getSlotIndex(); method public void reportEmergencyDataNetworkPreferredTransportChanged(int); method public void reportThrottleStatusChanged(@NonNull java.util.List<android.telephony.data.ThrottleStatus>); - method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public void requestNetworkValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method public void requestNetworkValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 12bfccf2172c..4c8283907712 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2460,7 +2460,7 @@ package android.os { method public static void invalidateCache(@NonNull String, @NonNull String); method public final boolean isDisabled(); method @Nullable public Result query(@NonNull Query); - method @FlaggedApi("android.os.ipc_data_cache_test_apis") public static void setTestMode(boolean); + method public static void setTestMode(boolean); field public static final String MODULE_BLUETOOTH = "bluetooth"; field public static final String MODULE_SYSTEM = "system_server"; field public static final String MODULE_TEST = "test"; diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java index d5b2f980e1a6..9f789328d8e0 100644 --- a/core/java/android/animation/AnimationHandler.java +++ b/core/java/android/animation/AnimationHandler.java @@ -110,7 +110,7 @@ public class AnimationHandler { } }; - public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>(); + public static final ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>(); private static AnimationHandler sTestHandler = null; private boolean mListDirty = false; @@ -118,10 +118,12 @@ public class AnimationHandler { if (sTestHandler != null) { return sTestHandler; } - if (sAnimatorHandler.get() == null) { - sAnimatorHandler.set(new AnimationHandler()); + AnimationHandler animatorHandler = sAnimatorHandler.get(); + if (animatorHandler == null) { + animatorHandler = new AnimationHandler(); + sAnimatorHandler.set(animatorHandler); } - return sAnimatorHandler.get(); + return animatorHandler; } /** diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 54ab3b8f185b..62816a2fa0f5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -3099,7 +3099,8 @@ public class ActivityManager { /** * Flag for {@link #moveTaskToFront(int, int)}: also move the "home" * activity along with the task, so it is positioned immediately behind - * the task. + * the task. This flag is ignored if the task's windowing mode is + * {@link WindowConfiguration#WINDOWING_MODE_MULTI_WINDOW}. */ public static final int MOVE_TASK_WITH_HOME = 0x00000001; diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index ea4646aa9eb9..3fd9d8b26611 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -104,6 +104,8 @@ public class AppCompatTaskInfo implements Parcelable { public static final int FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE = FLAG_BASE << 9; /** Top activity flag for whether restart menu is shown due to display move. */ private static final int FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE = FLAG_BASE << 10; + /** Top activity flag for whether activity opted out of edge to edge. */ + public static final int FLAG_OPT_OUT_EDGE_TO_EDGE = FLAG_BASE << 11; @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { @@ -118,7 +120,8 @@ public class AppCompatTaskInfo implements Parcelable { FLAG_FULLSCREEN_OVERRIDE_SYSTEM, FLAG_FULLSCREEN_OVERRIDE_USER, FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE, - FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE + FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE, + FLAG_OPT_OUT_EDGE_TO_EDGE }) public @interface TopActivityFlag {} @@ -132,7 +135,8 @@ public class AppCompatTaskInfo implements Parcelable { @TopActivityFlag private static final int FLAGS_ORGANIZER_INTERESTED = FLAG_IS_FROM_LETTERBOX_DOUBLE_TAP | FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON | FLAG_FULLSCREEN_OVERRIDE_SYSTEM - | FLAG_FULLSCREEN_OVERRIDE_USER | FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE; + | FLAG_FULLSCREEN_OVERRIDE_USER | FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE + | FLAG_OPT_OUT_EDGE_TO_EDGE; @TopActivityFlag private static final int FLAGS_COMPAT_UI_INTERESTED = FLAGS_ORGANIZER_INTERESTED @@ -347,6 +351,20 @@ public class AppCompatTaskInfo implements Parcelable { setTopActivityFlag(FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE, enable); } + /** + * Sets the top activity flag for whether the activity has opted out of edge to edge. + */ + public void setOptOutEdgeToEdge(boolean enable) { + setTopActivityFlag(FLAG_OPT_OUT_EDGE_TO_EDGE, enable); + } + + /** + * @return {@code true} if the top activity has opted out of edge to edge. + */ + public boolean hasOptOutEdgeToEdge() { + return isTopActivityFlagEnabled(FLAG_OPT_OUT_EDGE_TO_EDGE); + } + /** Clear all top activity flags and set to false. */ public void clearTopActivityFlags() { mTopActivityFlags = FLAG_UNDEFINED; diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 1ed64f9416c0..00fa1c1a4f80 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -200,6 +200,8 @@ public class ApplicationPackageManager extends PackageManager { @GuardedBy("mPackageMonitorCallbacks") private final ArraySet<IRemoteCallback> mPackageMonitorCallbacks = new ArraySet<>(); + private final boolean mUseSystemFeaturesCache; + UserManager getUserManager() { if (mUserManager == null) { mUserManager = UserManager.get(mContext); @@ -824,8 +826,7 @@ public class ApplicationPackageManager extends PackageManager { if (maybeHasSystemFeature != null) { return maybeHasSystemFeature; } - if (com.android.internal.os.Flags.applicationSharedMemoryEnabled() - && android.content.pm.Flags.cacheSdkSystemFeatures()) { + if (mUseSystemFeaturesCache) { maybeHasSystemFeature = SystemFeaturesCache.getInstance().maybeHasFeature(name, version); if (maybeHasSystemFeature != null) { @@ -2221,6 +2222,25 @@ public class ApplicationPackageManager extends PackageManager { protected ApplicationPackageManager(ContextImpl context, IPackageManager pm) { mContext = context; mPM = pm; + mUseSystemFeaturesCache = isSystemFeaturesCacheEnabledAndAvailable(); + } + + private static boolean isSystemFeaturesCacheEnabledAndAvailable() { + if (!android.content.pm.Flags.cacheSdkSystemFeatures()) { + return false; + } + if (!com.android.internal.os.Flags.applicationSharedMemoryEnabled()) { + return false; + } + if (ActivityThread.isSystem() && !SystemFeaturesCache.hasInstance()) { + // There are a handful of utility "system" processes that are neither system_server nor + // bound as applications. For these processes, we don't have access to application + // shared memory or the dependent system features cache. + // TODO(b/400713460): Revisit this exception after deprecating these command-like + // system processes. + return false; + } + return true; } /** diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index 2daa52b47102..fa977c93113a 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -228,7 +228,7 @@ public final class AutomaticZenRule implements Parcelable { public AutomaticZenRule(Parcel source) { enabled = source.readInt() == ENABLED; if (source.readInt() == ENABLED) { - name = getTrimmedString(source.readString8()); + name = getTrimmedString(source.readString()); } interruptionFilter = source.readInt(); conditionId = getTrimmedUri(source.readParcelable(null, android.net.Uri.class)); @@ -238,11 +238,11 @@ public final class AutomaticZenRule implements Parcelable { source.readParcelable(null, android.content.ComponentName.class)); creationTime = source.readLong(); mZenPolicy = source.readParcelable(null, ZenPolicy.class); - mPkg = source.readString8(); + mPkg = source.readString(); mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class); mAllowManualInvocation = source.readBoolean(); mIconResId = source.readInt(); - mTriggerDescription = getTrimmedString(source.readString8(), MAX_DESC_LENGTH); + mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH); mType = source.readInt(); } @@ -514,7 +514,7 @@ public final class AutomaticZenRule implements Parcelable { dest.writeInt(enabled ? ENABLED : DISABLED); if (name != null) { dest.writeInt(1); - dest.writeString8(name); + dest.writeString(name); } else { dest.writeInt(0); } @@ -524,11 +524,11 @@ public final class AutomaticZenRule implements Parcelable { dest.writeParcelable(configurationActivity, 0); dest.writeLong(creationTime); dest.writeParcelable(mZenPolicy, 0); - dest.writeString8(mPkg); + dest.writeString(mPkg); dest.writeParcelable(mDeviceEffects, 0); dest.writeBoolean(mAllowManualInvocation); dest.writeInt(mIconResId); - dest.writeString8(mTriggerDescription); + dest.writeString(mTriggerDescription); dest.writeInt(mType); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 7e5c0fbe1ee1..99a2763650cd 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2473,11 +2473,9 @@ class ContextImpl extends Context { @Override public int getPermissionRequestState(String permission) { Objects.requireNonNull(permission, "Permission name can't be null"); - int deviceId = PermissionManager.resolveDeviceIdForPermissionCheck(this, getDeviceId(), - permission); PermissionManager permissionManager = getSystemService(PermissionManager.class); return permissionManager.getPermissionRequestState(getOpPackageName(), permission, - deviceId); + getDeviceId()); } @Override diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index feaa98f644a0..7c293cb9cb3b 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -6470,9 +6470,11 @@ public class Notification implements Parcelable contentView.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE); contentView.setTextViewText(R.id.notification_material_reply_text_3, null); - // This may get erased by bindSnoozeAction, or if we're showing the bubble icon - contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, - RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin); + if (!notificationsRedesignTemplates()) { + // This may get erased by bindSnoozeAction, or if we're showing the bubble icon + contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, + RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin); + } } private boolean bindSnoozeAction(RemoteViews contentView, StandardTemplateParams p) { @@ -6489,7 +6491,7 @@ public class Notification implements Parcelable final boolean snoozeEnabled = !hideSnoozeButton && mContext.getContentResolver() != null && isSnoozeSettingEnabled(); - if (snoozeEnabled) { + if (!notificationsRedesignTemplates() && snoozeEnabled) { contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, RemoteViews.MARGIN_BOTTOM, 0); } @@ -6569,44 +6571,18 @@ public class Notification implements Parcelable } boolean validRemoteInput = false; + // With the new design, the actions_container should always be visible to act as padding + // when there are no actions. We're making its child GONE instead. + int actionsContainerForVisibilityChange = notificationsRedesignTemplates() + ? R.id.actions_container_layout : R.id.actions_container; if (numActions > 0 && !p.mHideActions) { - contentView.setViewVisibility(R.id.actions_container, View.VISIBLE); + contentView.setViewVisibility(actionsContainerForVisibilityChange, View.VISIBLE); contentView.setViewVisibility(R.id.actions, View.VISIBLE); - contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, - RemoteViews.MARGIN_BOTTOM, 0); - if (notificationsRedesignTemplates()) { - // No need for additional space under smart replies/smart actions. - contentView.setViewLayoutMarginDimen(R.id.smart_reply_container, - RemoteViews.MARGIN_BOTTOM, 0); - if (emphasizedMode) { - // Emphasized actions look similar to smart replies, so let's use the same - // margins. - contentView.setViewLayoutMarginDimen(R.id.actions_container, - RemoteViews.MARGIN_TOP, - R.dimen.notification_2025_smart_reply_container_margin); - contentView.setViewLayoutMarginDimen(R.id.actions_container, - RemoteViews.MARGIN_BOTTOM, - R.dimen.notification_2025_smart_reply_container_margin); - } else { - contentView.setViewLayoutMarginDimen(R.id.actions_container, - RemoteViews.MARGIN_TOP, 0); - contentView.setViewLayoutMarginDimen(R.id.actions_container, - RemoteViews.MARGIN_BOTTOM, - R.dimen.notification_2025_action_list_margin_bottom); - } - } + updateMarginsForActions(contentView, emphasizedMode); validRemoteInput = populateActionsContainer(contentView, p, nonContextualActions, numActions, emphasizedMode); } else { - contentView.setViewVisibility(R.id.actions_container, View.GONE); - if (notificationsRedesignTemplates() && !snoozeEnabled) { - // Make sure smart replies & smart actions have enough space at the bottom - // (if present) when there are no actions. This should be set to 0 if we're - // showing the snooze or bubble buttons. - contentView.setViewLayoutMarginDimen(R.id.smart_reply_container, - RemoteViews.MARGIN_BOTTOM, - R.dimen.notification_2025_smart_reply_container_margin); - } + contentView.setViewVisibility(actionsContainerForVisibilityChange, View.GONE); } RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle( @@ -6652,6 +6628,30 @@ public class Notification implements Parcelable return contentView; } + private void updateMarginsForActions(RemoteViews contentView, boolean emphasizedMode) { + if (notificationsRedesignTemplates()) { + if (emphasizedMode) { + // Emphasized actions look similar to smart replies, so let's use the same + // margins. + contentView.setViewLayoutMarginDimen(R.id.actions_container, + RemoteViews.MARGIN_TOP, + R.dimen.notification_2025_smart_reply_container_margin); + contentView.setViewLayoutMarginDimen(R.id.actions_container, + RemoteViews.MARGIN_BOTTOM, + R.dimen.notification_2025_smart_reply_container_margin); + } else { + contentView.setViewLayoutMarginDimen(R.id.actions_container, + RemoteViews.MARGIN_TOP, 0); + contentView.setViewLayoutMarginDimen(R.id.actions_container, + RemoteViews.MARGIN_BOTTOM, + R.dimen.notification_2025_action_list_margin_bottom); + } + } else { + contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, + RemoteViews.MARGIN_BOTTOM, 0); + } + } + private boolean populateActionsContainer(RemoteViews contentView, StandardTemplateParams p, List<Action> nonContextualActions, int numActions, boolean emphasizedMode) { boolean validRemoteInput = false; diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 6e495768bfd4..38141cf20ce3 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -1417,36 +1417,7 @@ public class PropertyInvalidatedCache<Query, Result> { } /** - * Throw if the current process is not allowed to use test APIs. - */ - @android.ravenwood.annotation.RavenwoodReplace - private static void throwIfNotTest() { - final ActivityThread activityThread = ActivityThread.currentActivityThread(); - if (activityThread == null) { - // Only tests can reach here. - return; - } - final Instrumentation instrumentation = activityThread.getInstrumentation(); - if (instrumentation == null) { - // Only tests can reach here. - return; - } - if (instrumentation.isInstrumenting()) { - return; - } - if (Flags.enforcePicTestmodeProtocol()) { - throw new IllegalStateException("Test-only API called not from a test."); - } - } - - /** - * Do not throw if running under ravenwood. - */ - private static void throwIfNotTest$ravenwood() { - } - - /** - * Enable or disable test mode. The protocol requires that the mode toggle: for instance, it is + * Enable or disable testing. The protocol requires that the mode toggle: for instance, it is * illegal to clear the test mode if the test mode is already off. Enabling test mode puts * all caches in the process into test mode; all nonces are initialized to UNSET and * subsequent reads and writes are to process memory. This has the effect of disabling all @@ -1454,12 +1425,10 @@ public class PropertyInvalidatedCache<Query, Result> { * operation. * @param mode The desired test mode. * @throws IllegalStateException if the supplied mode is already set. - * @throws IllegalStateException if the process is not running an instrumentation test. * @hide */ @VisibleForTesting public static void setTestMode(boolean mode) { - throwIfNotTest(); synchronized (sGlobalLock) { if (sTestMode == mode) { final String msg = "cannot set test mode redundantly: mode=" + mode; @@ -1495,11 +1464,9 @@ public class PropertyInvalidatedCache<Query, Result> { * for which it would not otherwise have permission. Caches in test mode do NOT write their * values to the system properties. The effect is local to the current process. Test mode * must be true when this method is called. - * @throws IllegalStateException if the process is not running an instrumentation test. * @hide */ public void testPropertyName() { - throwIfNotTest(); synchronized (sGlobalLock) { if (sTestMode == false) { throw new IllegalStateException("cannot test property name with test mode off"); @@ -1810,12 +1777,10 @@ public class PropertyInvalidatedCache<Query, Result> { * When multiple caches share a single property value, using an instance method on one of * the cache objects to invalidate all of the cache objects becomes confusing and you should * just use the static version of this function. - * @throws IllegalStateException if the process is not running an instrumentation test. * @hide */ @VisibleForTesting public void disableSystemWide() { - throwIfNotTest(); disableSystemWide(mPropertyName); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index c74bd1a092ee..c528db8f1809 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -224,7 +224,7 @@ import java.util.function.Consumer; * <li>A <i id="deviceowner">Device Owner</i>, which only ever exists on the * {@link UserManager#isSystemUser System User} or Main User, is * the most powerful type of Device Policy Controller and can affect policy across the device. - * <li>A <i id="profileowner">Profile Owner<i>, which can exist on any user, can + * <li>A <i id="profileowner">Profile Owner</i>, which can exist on any user, can * affect policy on the user it is on, and when it is running on * {@link UserManager#isProfile a profile} has * <a href="#profile-on-parent">limited</a> ability to affect policy on its parent. diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java index 83abc048af8a..e05ede580d3f 100644 --- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java +++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java @@ -31,6 +31,7 @@ import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; import android.app.appsearch.GlobalSearchSession; import android.app.appsearch.JoinSpec; +import android.app.appsearch.PropertyPath; import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchSpec; @@ -141,6 +142,9 @@ public class AppFunctionManagerHelper { .addFilterSchemas( AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage( targetPackage)) + .addProjectionPaths( + SearchSpec.SCHEMA_TYPE_WILDCARD, + List.of(new PropertyPath(STATIC_PROPERTY_ENABLED_BY_DEFAULT))) .setJoinSpec(joinSpec) .setVerbatimSearchEnabled(true) .build(); diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java index 7718d159896e..3c8b1a0482ad 100644 --- a/core/java/android/app/jank/JankDataProcessor.java +++ b/core/java/android/app/jank/JankDataProcessor.java @@ -366,7 +366,7 @@ public class JankDataProcessor { 30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000, Integer.MAX_VALUE }; - private final int[] mFrameOverrunBuckets = new int[sFrameOverrunHistogramBounds.length]; + private final int[] mFrameOverrunBuckets = new int[sFrameOverrunHistogramBounds.length - 1]; // Histogram of frame duration overruns encoded in predetermined buckets. public PendingJankStat() { @@ -511,7 +511,7 @@ public class JankDataProcessor { if (overrunTime <= 1000) { return (overrunTime - 200) / 100 + 43; } - return sFrameOverrunHistogramBounds.length - 1; + return mFrameOverrunBuckets.length - 1; } } diff --git a/core/java/android/app/jank/TEST_MAPPING b/core/java/android/app/jank/TEST_MAPPING new file mode 100644 index 000000000000..271eb4332f79 --- /dev/null +++ b/core/java/android/app/jank/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "postsubmit": [ + { + "name": "CoreAppJankTestCases" + }, + { + "name": "CtsAppJankTestCases" + } + ] +}
\ No newline at end of file diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 3eaf2c40daca..6f0eafe487af 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -269,7 +269,7 @@ flag { namespace: "systemui" description: "enables metrics when redacting notifications on the lockscreen" bug: "343631648" - metadata { + metadata { purpose: PURPOSE_BUGFIX } } @@ -279,7 +279,16 @@ flag { namespace: "systemui" description: "enables user expanding the public view of a notification" bug: "398853084" - metadata { + metadata { + purpose: PURPOSE_BUGFIX + } + } +flag { + name: "notif_entry_creation_time_use_elapsed_realtime" + namespace: "systemui" + description: "makes the notification entry expect its creation time to be elapsedRealtime, not uptimeMillis" + bug: "343631648" + metadata { purpose: PURPOSE_BUGFIX } } @@ -367,6 +376,7 @@ flag { namespace: "systemui" description: "Allows the NAS to summarize notifications" bug: "390417189" + is_exported: true } flag { diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java index 8217ca1953ab..172ed2358a5d 100644 --- a/core/java/android/app/supervision/SupervisionManager.java +++ b/core/java/android/app/supervision/SupervisionManager.java @@ -97,6 +97,8 @@ public class SupervisionManager { * * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SUPERVISION_MANAGER_APIS) @RequiresPermission(anyOf = {MANAGE_USERS, QUERY_USERS}) @Nullable public Intent createConfirmSupervisionCredentialsIntent() { diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index f8ac27de1754..db77adeb5a3d 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -24,6 +24,7 @@ import android.companion.virtual.IVirtualDeviceSoundEffectListener; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; import android.companion.virtual.sensor.VirtualSensor; +import android.companion.virtual.sensor.VirtualSensorAdditionalInfo; import android.companion.virtual.sensor.VirtualSensorConfig; import android.companion.virtual.sensor.VirtualSensorEvent; import android.companion.virtual.camera.VirtualCameraConfig; @@ -251,6 +252,11 @@ interface IVirtualDevice { boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event); /** + * Sends additional information about the virtual sensor corresponding to the given token. + */ + boolean sendSensorAdditionalInfo(IBinder token, in VirtualSensorAdditionalInfo info); + + /** * Launches a pending intent on the given display that is owned by this virtual device. */ void launchPendingIntent(int displayId, in PendingIntent pendingIntent, diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 4fb3982c3754..615a6dffdf99 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -158,3 +158,11 @@ flag { bug: "370720522" is_exported: true } + +flag { + name: "virtual_sensor_additional_info" + namespace: "virtual_devices" + description: "API for injecting SensorAdditionalInfo for VirtualSensor" + bug: "393517834" + is_exported: true +} diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java index 934a1a8ffcbd..8d4acfcb30d7 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensor.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java @@ -16,12 +16,15 @@ package android.companion.virtual.sensor; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.companion.virtual.IVirtualDevice; +import android.companion.virtualdevice.flags.Flags; import android.hardware.Sensor; +import android.hardware.SensorAdditionalInfo; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -37,20 +40,33 @@ import android.os.RemoteException; */ @SystemApi public final class VirtualSensor implements Parcelable { + private final int mHandle; private final int mType; private final String mName; + private final int mFlags; private final IVirtualDevice mVirtualDevice; private final IBinder mToken; + // Only one additional info frame set at a time. + private final Object mAdditionalInfoLock = new Object(); /** * @hide */ public VirtualSensor(int handle, int type, String name, IVirtualDevice virtualDevice, IBinder token) { + this(handle, type, name, /*flags=*/0, virtualDevice, token); + } + + /** + * @hide + */ + public VirtualSensor(int handle, int type, String name, int flags, IVirtualDevice virtualDevice, + IBinder token) { mHandle = handle; mType = type; mName = name; + mFlags = flags; mVirtualDevice = virtualDevice; mToken = token; } @@ -61,13 +77,14 @@ public final class VirtualSensor implements Parcelable { @SuppressLint("UnflaggedApi") // @TestApi without associated feature. @TestApi public VirtualSensor(int handle, int type, @NonNull String name) { - this(handle, type, name, /*virtualDevice=*/null, /*token=*/null); + this(handle, type, name, /*flags=*/0, /*virtualDevice=*/null, /*token=*/null); } private VirtualSensor(Parcel parcel) { mHandle = parcel.readInt(); mType = parcel.readInt(); mName = parcel.readString8(); + mFlags = parcel.readInt(); mVirtualDevice = IVirtualDevice.Stub.asInterface(parcel.readStrongBinder()); mToken = parcel.readStrongBinder(); } @@ -123,6 +140,7 @@ public final class VirtualSensor implements Parcelable { parcel.writeInt(mHandle); parcel.writeInt(mType); parcel.writeString8(mName); + parcel.writeInt(mFlags); parcel.writeStrongBinder(mVirtualDevice.asBinder()); parcel.writeStrongBinder(mToken); } @@ -143,6 +161,33 @@ public final class VirtualSensor implements Parcelable { } } + /** + * Send additional information about the sensor to the system. + * + * @param info the additional sensor information to send. + * @throws UnsupportedOperationException if the sensor does not support sending additional info. + * @see Sensor#isAdditionalInfoSupported() + * @see VirtualSensorConfig.Builder#setAdditionalInfoSupported(boolean) + * @see SensorAdditionalInfo + * @see VirtualSensorAdditionalInfo + */ + @FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO) + public void sendAdditionalInfo(@NonNull VirtualSensorAdditionalInfo info) { + if (!Flags.virtualSensorAdditionalInfo()) { + return; + } + if ((mFlags & VirtualSensorConfig.ADDITIONAL_INFO_MASK) == 0) { + throw new UnsupportedOperationException("Sensor additional info not supported."); + } + try { + synchronized (mAdditionalInfoLock) { + mVirtualDevice.sendSensorAdditionalInfo(mToken, info); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @NonNull public static final Parcelable.Creator<VirtualSensor> CREATOR = new Parcelable.Creator<VirtualSensor>() { diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.aidl new file mode 100644 index 000000000000..7267be88ca75 --- /dev/null +++ b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.sensor; + +parcelable VirtualSensorAdditionalInfo;
\ No newline at end of file diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java new file mode 100644 index 000000000000..a4fca507b1d5 --- /dev/null +++ b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.sensor; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.companion.virtualdevice.flags.Flags; +import android.hardware.SensorAdditionalInfo; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +/** + * An additional information frame for a {@link VirtualSensor}, which is reported through listener + * callback {@link android.hardware.SensorEventCallback#onSensorAdditionalInfo}. + * + * @see SensorAdditionalInfo + * @see VirtualSensorConfig.Builder#setAdditionalInfoSupported(boolean) + * @hide + */ +@FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO) +@SystemApi +public final class VirtualSensorAdditionalInfo implements Parcelable { + + private final int mType; + @NonNull + private final List<float[]> mValues; + + /** @hide */ + @IntDef(prefix = "TYPE_", value = { + SensorAdditionalInfo.TYPE_UNTRACKED_DELAY, + SensorAdditionalInfo.TYPE_INTERNAL_TEMPERATURE, + SensorAdditionalInfo.TYPE_VEC3_CALIBRATION, + SensorAdditionalInfo.TYPE_SENSOR_PLACEMENT, + SensorAdditionalInfo.TYPE_SAMPLING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + private VirtualSensorAdditionalInfo(int type, @NonNull List<float[]> values) { + mType = type; + mValues = values; + } + + private VirtualSensorAdditionalInfo(@NonNull Parcel parcel) { + mType = parcel.readInt(); + final int valuesLength = parcel.readInt(); + mValues = new ArrayList<>(valuesLength); + for (int i = 0; i < valuesLength; ++i) { + mValues.add(parcel.createFloatArray()); + } + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) { + parcel.writeInt(mType); + parcel.writeInt(mValues.size()); + for (int i = 0; i < mValues.size(); ++i) { + parcel.writeFloatArray(mValues.get(i)); + } + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the type of this information frame. + * + * @see SensorAdditionalInfo#type + */ + public int getType() { + return mType; + } + + /** + * Returns the float values of this information frame, if any. + * + * @see SensorAdditionalInfo#floatValues + */ + @NonNull + public List<float[]> getValues() { + return mValues; + } + + /** + * Builder for {@link VirtualSensorAdditionalInfo}. + */ + public static final class Builder { + + @VirtualSensorAdditionalInfo.Type + private final int mType; + @NonNull + private final ArrayList<float[]> mValues = new ArrayList<>(); + + /** + * Creates a new builder. + * + * @param type type of this additional info frame. + * @see SensorAdditionalInfo + */ + public Builder(@VirtualSensorAdditionalInfo.Type int type) { + switch (type) { + case SensorAdditionalInfo.TYPE_UNTRACKED_DELAY: + case SensorAdditionalInfo.TYPE_SAMPLING: + case SensorAdditionalInfo.TYPE_INTERNAL_TEMPERATURE: + case SensorAdditionalInfo.TYPE_VEC3_CALIBRATION: + case SensorAdditionalInfo.TYPE_SENSOR_PLACEMENT: + break; + default: + throw new IllegalArgumentException("Unsupported type " + type); + } + mType = type; + } + + /** + * Additional info payload data represented in float values. Depending on the type of + * information, this may be null. + * + * @see SensorAdditionalInfo#floatValues + */ + @NonNull + public Builder addValues(@NonNull float[] values) { + if (values.length > 14) { + throw new IllegalArgumentException("Maximum payload value size is 14."); + } + if (mValues.isEmpty()) { + switch (mType) { + case SensorAdditionalInfo.TYPE_UNTRACKED_DELAY: + case SensorAdditionalInfo.TYPE_SAMPLING: + assertValuesLength(values, 2); + break; + case SensorAdditionalInfo.TYPE_INTERNAL_TEMPERATURE: + assertValuesLength(values, 1); + break; + case SensorAdditionalInfo.TYPE_VEC3_CALIBRATION: + case SensorAdditionalInfo.TYPE_SENSOR_PLACEMENT: + assertValuesLength(values, 11); + break; + } + } else if (values.length != mValues.getFirst().length) { + throw new IllegalArgumentException("All payload values must have the same length"); + } + + mValues.add(values); + return this; + } + + private void assertValuesLength(float[] values, int expected) { + if (values.length != expected) { + throw new IllegalArgumentException( + "Payload values must have size " + expected + " for type " + mType); + } + } + + /** + * Creates a new {@link VirtualSensorAdditionalInfo}. + * + * @throws IllegalArgumentException if the payload doesn't match the expectation for the + * given type, as documented in {@link SensorAdditionalInfo}. + */ + @NonNull + public VirtualSensorAdditionalInfo build() { + if (mValues.isEmpty()) { + throw new IllegalArgumentException("Payload is required"); + } + return new VirtualSensorAdditionalInfo(mType, mValues); + } + } + + public static final @NonNull Creator<VirtualSensorAdditionalInfo> CREATOR = + new Creator<>() { + public VirtualSensorAdditionalInfo createFromParcel(Parcel source) { + return new VirtualSensorAdditionalInfo(source); + } + + public VirtualSensorAdditionalInfo[] newArray(int size) { + return new VirtualSensorAdditionalInfo[size]; + } + }; +} diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java index 68bc9bce28d2..be8974ec29ad 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java @@ -59,10 +59,14 @@ public final class VirtualSensorConfig implements Parcelable { private static final int REPORTING_MODE_MASK = 0xE; private static final int REPORTING_MODE_SHIFT = 1; + // Mask for indication bit of sensor additional information support, bit 6. + static final int ADDITIONAL_INFO_MASK = 0x40; + // Mask for direct mode highest rate level, bit 7, 8, 9. private static final int DIRECT_REPORT_MASK = 0x380; private static final int DIRECT_REPORT_SHIFT = 7; + // Mask for supported direct channel, bit 10, 11 private static final int DIRECT_CHANNEL_SHIFT = 10; @@ -253,6 +257,18 @@ public final class VirtualSensorConfig implements Parcelable { } /** + * Returns whether the sensor supports additional information. + * + * @see Builder#setAdditionalInfoSupported(boolean) + * @see Sensor#isAdditionalInfoSupported() + * @see android.hardware.SensorAdditionalInfo + */ + @FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO) + public boolean isAdditionalInfoSupported() { + return (mFlags & ADDITIONAL_INFO_MASK) > 0; + } + + /** * Returns the reporting mode of this sensor. * * @see Builder#setReportingMode(int) @@ -450,6 +466,25 @@ public final class VirtualSensorConfig implements Parcelable { } /** + * Sets whether this sensor supports sensor additional information. + * + * @see Sensor#isAdditionalInfoSupported() + * @see android.hardware.SensorAdditionalInfo + * @see VirtualSensorAdditionalInfo + */ + @FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO) + @NonNull + public VirtualSensorConfig.Builder setAdditionalInfoSupported( + boolean additionalInfoSupported) { + if (additionalInfoSupported) { + mFlags |= ADDITIONAL_INFO_MASK; + } else { + mFlags &= ~ADDITIONAL_INFO_MASK; + } + return this; + } + + /** * Sets the reporting mode of this sensor. * * @throws IllegalArgumentException if the reporting mode is not one of diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 038756148a32..bb62ac321202 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -12426,6 +12426,8 @@ public class Intent implements Parcelable, Cloneable { } private void collectNestedIntentKeysRecur(Set<Intent> visited, boolean forceUnparcel) { + // if forceUnparcel is false, do not unparcel the mExtras bundle. + // forceUnparcel will only be true when this method is called from system server. if (mExtras != null && (forceUnparcel || !mExtras.isParcelled()) && !mExtras.isEmpty()) { addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED); for (String key : mExtras.keySet()) { @@ -12440,6 +12442,7 @@ public class Intent implements Parcelable, Cloneable { value = mExtras.get(key); } else { value = null; + removeExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED); } } catch (BadParcelableException e) { // This may still happen if the keys are collected on the system server side, in @@ -12459,6 +12462,13 @@ public class Intent implements Parcelable, Cloneable { } } + // if there is no extras in the bundle, we also mark the intent as keys are collected. + // isDefinitelyEmpty() will not unparceled the mExtras. This is the best we can do without + // unparceling the extra bundle. + if (mExtras == null || mExtras.isDefinitelyEmpty()) { + addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED); + } + if (mClipData != null) { for (int i = 0; i < mClipData.getItemCount(); i++) { Intent intent = mClipData.getItemAt(i).mIntent; diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index 10d3051cff6f..ded35b23608d 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -31,13 +31,13 @@ import android.os.Environment; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; -import android.util.ArrayMap; import android.util.AtomicFile; import android.util.AttributeSet; import android.util.IntArray; import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseArrayMap; import android.util.Xml; import com.android.internal.annotations.GuardedBy; @@ -98,15 +98,16 @@ public abstract class RegisteredServicesCache<V> { @GuardedBy("mServicesLock") private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2); - @GuardedBy("mServiceInfoCaches") - private final ArrayMap<ComponentName, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>(); + @GuardedBy("mUserIdToServiceInfoCaches") + private final SparseArrayMap<ComponentName, ServiceInfo<V>> mUserIdToServiceInfoCaches = + new SparseArrayMap<>(); private final Handler mBackgroundHandler; private final Runnable mClearServiceInfoCachesRunnable = new Runnable() { public void run() { - synchronized (mServiceInfoCaches) { - mServiceInfoCaches.clear(); + synchronized (mUserIdToServiceInfoCaches) { + mUserIdToServiceInfoCaches.clear(); } } }; @@ -537,8 +538,8 @@ public abstract class RegisteredServicesCache<V> { Slog.d(TAG, "Fail to get the PackageInfo in generateServicesMap: " + e); } if (lastUpdateTime >= 0) { - ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(componentName, - lastUpdateTime); + ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(userId, + componentName, lastUpdateTime); if (serviceInfo != null) { serviceInfos.add(serviceInfo); continue; @@ -553,8 +554,8 @@ public abstract class RegisteredServicesCache<V> { } serviceInfos.add(info); if (Flags.optimizeParsingInRegisteredServicesCache()) { - synchronized (mServiceInfoCaches) { - mServiceInfoCaches.put(componentName, info); + synchronized (mUserIdToServiceInfoCaches) { + mUserIdToServiceInfoCaches.add(userId, componentName, info); } } } catch (XmlPullParserException | IOException e) { @@ -563,8 +564,8 @@ public abstract class RegisteredServicesCache<V> { } if (Flags.optimizeParsingInRegisteredServicesCache()) { - synchronized (mServiceInfoCaches) { - if (!mServiceInfoCaches.isEmpty()) { + synchronized (mUserIdToServiceInfoCaches) { + if (mUserIdToServiceInfoCaches.numMaps() > 0) { mBackgroundHandler.removeCallbacks(mClearServiceInfoCachesRunnable); mBackgroundHandler.postDelayed(mClearServiceInfoCachesRunnable, SERVICE_INFO_CACHES_TIMEOUT_MILLIS); @@ -873,6 +874,11 @@ public abstract class RegisteredServicesCache<V> { synchronized (mServicesLock) { mUserServices.remove(userId); } + if (Flags.optimizeParsingInRegisteredServicesCache()) { + synchronized (mUserIdToServiceInfoCaches) { + mUserIdToServiceInfoCaches.delete(userId); + } + } } @VisibleForTesting @@ -916,10 +922,10 @@ public abstract class RegisteredServicesCache<V> { mContext.unregisterReceiver(mUserRemovedReceiver); } - private ServiceInfo<V> getServiceInfoFromServiceCache(@NonNull ComponentName componentName, - long lastUpdateTime) { - synchronized (mServiceInfoCaches) { - ServiceInfo<V> serviceCache = mServiceInfoCaches.get(componentName); + private ServiceInfo<V> getServiceInfoFromServiceCache(int userId, + @NonNull ComponentName componentName, long lastUpdateTime) { + synchronized (mUserIdToServiceInfoCaches) { + ServiceInfo<V> serviceCache = mUserIdToServiceInfoCaches.get(userId, componentName); if (serviceCache != null && serviceCache.lastUpdateTime == lastUpdateTime) { return serviceCache; } diff --git a/core/java/android/content/pm/SystemFeaturesCache.java b/core/java/android/content/pm/SystemFeaturesCache.java index b3d70fa8bfaf..57dd1e6f4d3b 100644 --- a/core/java/android/content/pm/SystemFeaturesCache.java +++ b/core/java/android/content/pm/SystemFeaturesCache.java @@ -74,6 +74,11 @@ public final class SystemFeaturesCache { return instance; } + /** Checks for existence of the process-global instance. */ + public static boolean hasInstance() { + return sInstance != null; + } + /** Clears the process-global cache instance for testing. */ @VisibleForTesting public static void clearInstance() { diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index 23f1ff8926df..92ae15a296b7 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -136,6 +136,28 @@ ] }, { + "name": "CtsPackageInstallerCUJInstallationViaIntentForResultTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJInstallationViaSessionTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJMultiUsersTestCases", "options":[ { @@ -261,6 +283,28 @@ ] }, { + "name": "CtsPackageInstallerCUJInstallationViaIntentForResultTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJInstallationViaSessionTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJMultiUsersTestCases", "options":[ { diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 7f57f5dbf0ab..3411a4897e83 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -636,3 +636,13 @@ flag { description: "Enables support for new supervising user type" bug: "389712089" } + +flag { + name: "use_unified_resources" + namespace: "multiuser" + description: "Use same resources" + bug: "392972139" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java index fdde2057a1a0..de3bafb4bb56 100644 --- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java +++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java @@ -75,7 +75,6 @@ public class MarshalQueryableParcelable<T extends Parcelable> } Parcel parcel = Parcel.obtain(); - byte[] parcelContents; try { value.writeToParcel(parcel, /*flags*/0); @@ -85,17 +84,15 @@ public class MarshalQueryableParcelable<T extends Parcelable> "Parcelable " + value + " must not have file descriptors"); } - parcelContents = parcel.marshall(); + final int position = buffer.position(); + parcel.marshall(buffer); + if (buffer.position() == position) { + throw new AssertionError("No data marshaled for " + value); + } } finally { parcel.recycle(); } - - if (parcelContents.length == 0) { - throw new AssertionError("No data marshaled for " + value); - } - - buffer.put(parcelContents); } @Override diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 92a56fc575e3..8216130e3731 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -433,8 +433,9 @@ public final class DisplayManager { public static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8; /** - * Virtual display flag: Indicates that the display should support system decorations. Virtual - * displays without this flag shouldn't show home, navigation bar or wallpaper. + * Virtual display flag: Indicates that the display supports and should always show system + * decorations. Virtual displays without this flag shouldn't show home, navigation bar or + * wallpaper. * <p>This flag doesn't work without {@link #VIRTUAL_DISPLAY_FLAG_TRUSTED}</p> * * @see #createVirtualDisplay diff --git a/core/java/android/hardware/serial/OWNERS b/core/java/android/hardware/serial/OWNERS index bc2c66ae7ecd..13f6774a4264 100644 --- a/core/java/android/hardware/serial/OWNERS +++ b/core/java/android/hardware/serial/OWNERS @@ -3,4 +3,5 @@ mjel@google.com chominskib@google.com wzwonarz@google.com gstepniewski@google.com -xutan@google.com
\ No newline at end of file +xutan@google.com +ovn@google.com diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 894b068b1528..2e7bc6d9b9f7 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -428,6 +428,9 @@ public class InputMethodService extends AbstractInputMethodService { */ @AnyThread public static boolean canImeRenderGesturalNavButtons() { + if (Flags.disallowDisablingImeNavigationBar()) { + return true; + } return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, true); } diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java index e888f520b842..2e7c3be53d90 100644 --- a/core/java/android/os/IpcDataCache.java +++ b/core/java/android/os/IpcDataCache.java @@ -718,7 +718,7 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, } /** - * Enable or disable test mode. The protocol requires that the mode toggle: for instance, it is + * Enable or disable testing. The protocol requires that the mode toggle: for instance, it is * illegal to clear the test mode if the test mode is already off. Enabling test mode puts * all caches in the process into test mode; all nonces are initialized to UNSET and * subsequent reads and writes are to process memory. This has the effect of disabling all @@ -726,11 +726,8 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, * operation. * @param mode The desired test mode. * @throws IllegalStateException if the supplied mode is already set. - * @throws IllegalStateException if the process is not running an instrumentation test. * @hide */ - @FlaggedApi(android.os.Flags.FLAG_IPC_DATA_CACHE_TEST_APIS) - @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public static void setTestMode(boolean mode) { PropertyInvalidatedCache.setTestMode(mode); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 49d3f06eb80f..6cb49b3ea166 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -52,6 +52,8 @@ import com.android.internal.util.ArrayUtils; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; +import java.nio.BufferOverflowException; +import java.nio.ReadOnlyBufferException; import libcore.util.SneakyThrow; import java.io.ByteArrayInputStream; @@ -62,6 +64,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.Serializable; +import java.nio.ByteBuffer; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Array; @@ -457,8 +460,15 @@ public final class Parcel { private static native void nativeDestroy(long nativePtr); private static native byte[] nativeMarshall(long nativePtr); + private static native int nativeMarshallArray( + long nativePtr, byte[] data, int offset, int length); + private static native int nativeMarshallBuffer( + long nativePtr, ByteBuffer buffer, int offset, int length); private static native void nativeUnmarshall( long nativePtr, byte[] data, int offset, int length); + private static native void nativeUnmarshallBuffer( + long nativePtr, ByteBuffer buffer, int offset, int length); + private static native int nativeCompareData(long thisNativePtr, long otherNativePtr); private static native boolean nativeCompareDataInRange( long ptrA, int offsetA, long ptrB, int offsetB, int length); @@ -814,12 +824,80 @@ public final class Parcel { } /** + * Writes the raw bytes of the parcel to a buffer. + * + * <p class="note">The data you retrieve here <strong>must not</strong> + * be placed in any kind of persistent storage (on local disk, across + * a network, etc). For that, you should use standard serialization + * or another kind of general serialization mechanism. The Parcel + * marshalled representation is highly optimized for local IPC, and as + * such does not attempt to maintain compatibility with data created + * in different versions of the platform. + * + * @param buffer The ByteBuffer to write the data to. + * @throws ReadOnlyBufferException if the buffer is read-only. + * @throws BufferOverflowException if the buffer is too small. + * + * @hide + */ + public final void marshall(@NonNull ByteBuffer buffer) { + if (buffer == null) { + throw new NullPointerException(); + } + if (buffer.isReadOnly()) { + throw new ReadOnlyBufferException(); + } + + final int position = buffer.position(); + final int remaining = buffer.remaining(); + + int marshalledSize = 0; + if (buffer.isDirect()) { + marshalledSize = nativeMarshallBuffer(mNativePtr, buffer, position, remaining); + } else if (buffer.hasArray()) { + marshalledSize = nativeMarshallArray( + mNativePtr, buffer.array(), buffer.arrayOffset() + position, remaining); + } else { + throw new IllegalArgumentException(); + } + + buffer.position(position + marshalledSize); + } + + /** * Fills the raw bytes of this Parcel with the supplied data. */ public final void unmarshall(@NonNull byte[] data, int offset, int length) { nativeUnmarshall(mNativePtr, data, offset, length); } + /** + * Fills the raw bytes of this Parcel with data from the supplied buffer. + * + * @param buffer will read buffer.remaining() bytes from the buffer. + * + * @hide + */ + public final void unmarshall(@NonNull ByteBuffer buffer) { + if (buffer == null) { + throw new NullPointerException(); + } + + final int position = buffer.position(); + final int remaining = buffer.remaining(); + + if (buffer.isDirect()) { + nativeUnmarshallBuffer(mNativePtr, buffer, position, remaining); + } else if (buffer.hasArray()) { + nativeUnmarshall( + mNativePtr, buffer.array(), buffer.arrayOffset() + position, remaining); + } else { + throw new IllegalArgumentException(); + } + + buffer.position(position + remaining); + } + public final void appendFrom(Parcel parcel, int offset, int length) { nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length); } diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 5d80119410e1..86acb2b21cfa 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -227,14 +227,6 @@ flag { } flag { - name: "ipc_data_cache_test_apis" - namespace: "system_performance" - description: "Expose IpcDataCache test apis to mainline modules." - bug: "396173886" - is_exported: true -} - -flag { name: "mainline_vcn_platform_api" namespace: "vcn" description: "Expose platform APIs to mainline VCN" diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 561a2c96e6a7..17033d1143c6 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1907,8 +1907,9 @@ public final class PermissionManager { @Context.PermissionRequestState public int getPermissionRequestState(@NonNull String packageName, @NonNull String permission, int deviceId) { + int resolvedDeviceId = resolveDeviceIdForPermissionCheck(mContext, deviceId, permission); return sPermissionRequestStateCache.query( - new PermissionRequestStateQuery(packageName, permission, deviceId)); + new PermissionRequestStateQuery(packageName, permission, resolvedDeviceId)); } /** @@ -2035,7 +2036,8 @@ public final class PermissionManager { */ public int checkPackageNamePermission(String permName, String pkgName, int deviceId, @UserIdInt int userId) { - String persistentDeviceId = getPersistentDeviceId(deviceId); + int resolvedDeviceId = resolveDeviceIdForPermissionCheck(mContext, deviceId, permName); + String persistentDeviceId = getPersistentDeviceId(resolvedDeviceId); return sPackageNamePermissionCache.query( new PackageNamePermissionQuery(permName, pkgName, persistentDeviceId, userId)); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index da4709b4b8b1..85f38c984f5c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4184,7 +4184,6 @@ public final class Settings { MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_REDIRECT_URL); MOVED_TO_SECURE.add(Secure.SETTINGS_CLASSNAME); MOVED_TO_SECURE.add(Secure.USE_GOOGLE_MAIL); - MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON); MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY); MOVED_TO_SECURE.add(Secure.WIFI_NUM_OPEN_NETWORKS_KEPT); MOVED_TO_SECURE.add(Secure.WIFI_ON); @@ -4223,6 +4222,7 @@ public final class Settings { MOVED_TO_SECURE_THEN_GLOBAL.add(Global.USB_MASS_STORAGE_ENABLED); MOVED_TO_SECURE_THEN_GLOBAL.add(Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS); MOVED_TO_SECURE_THEN_GLOBAL.add(Global.WIFI_MAX_DHCP_RETRY_COUNT); + MOVED_TO_SECURE_THEN_GLOBAL.add(Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON); // these are moving directly from system to global MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_ON); @@ -6484,6 +6484,14 @@ public final class Settings { public static final String SCREEN_FLASH_NOTIFICATION = "screen_flash_notification"; /** + * Setting to enable CV (proprietary) + * + * @hide + */ + public static final String CV_ENABLED = + "cv_enabled"; + + /** * Integer property that specifes the color for screen flash notification as a * packed 32-bit color. * @@ -12822,6 +12830,22 @@ public final class Settings { public static final String ADAPTIVE_CONNECTIVITY_ENABLED = "adaptive_connectivity_enabled"; /** + * Whether the Adaptive wifi scorer switch is enabled. + * + * @hide + */ + public static final String ADAPTIVE_CONNECTIVITY_WIFI_ENABLED = + "adaptive_connectivity_wifi_enabled"; + + /** + * Whether the Adaptive 5G PM switch is enabled. + * + * @hide + */ + public static final String ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED = + "adaptive_connectivity_mobile_network_enabled"; + + /** * Controls the 'Sunlight boost' toggle in wearable devices (high brightness mode). * * Valid values for this key are: '0' (disabled) or '1' (enabled). @@ -15498,7 +15522,8 @@ public final class Settings { * <ul> * <li><pre>secure</pre>: creates a secure display</li> * <li><pre>own_content_only</pre>: only shows this display's own content</li> - * <li><pre>should_show_system_decorations</pre>: supports system decorations</li> + * <li><pre>should_show_system_decorations</pre>: always shows system decorations</li> + * <li><pre>fixed_content_mode</pre>: does not allow the content mode switch</li> * </ul> * </p><p> * Example: @@ -20799,6 +20824,24 @@ public final class Settings { @Readable public static final String WEAR_LAUNCHER_UI_MODE = "wear_launcher_ui_mode"; + /** + * Setting indicating whether the primary gesture input action has been enabled by the + * user. + * + * @hide + */ + public static final String GESTURE_PRIMARY_ACTION_USER_PREFERENCE = + "gesture_primary_action_user_preference"; + + /** + * Setting indicating whether the dismiss gesture input action has been enabled by the + * user. + * + * @hide + */ + public static final String GESTURE_DISMISS_ACTION_USER_PREFERENCE = + "gesture_dismiss_action_user_preference"; + /** Whether Wear Power Anomaly Service is enabled. * * (0 = false, 1 = true) diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java index 0b2239aa42b2..62b2bcf32442 100644 --- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java +++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java @@ -124,6 +124,18 @@ public final class AdvancedProtectionManager { @Retention(RetentionPolicy.SOURCE) public @interface FeatureId {} + /** @hide */ + public static String featureIdToString(@FeatureId int featureId) { + return switch(featureId) { + case FEATURE_ID_DISALLOW_CELLULAR_2G -> "DISALLOW_CELLULAR_2G"; + case FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES -> "DISALLOW_INSTALL_UNKNOWN_SOURCES"; + case FEATURE_ID_DISALLOW_USB -> "DISALLOW_USB"; + case FEATURE_ID_DISALLOW_WEP -> "DISALLOW_WEP"; + case FEATURE_ID_ENABLE_MTE -> "ENABLE_MTE"; + default -> "UNKNOWN"; + }; + } + private static final Set<Integer> ALL_FEATURE_IDS = Set.of( FEATURE_ID_DISALLOW_CELLULAR_2G, FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES, @@ -147,7 +159,7 @@ public final class AdvancedProtectionManager { "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG"; /** - * A string extra used with {@link #createSupportIntent} to identify the feature that needs to + * An int extra used with {@link #createSupportIntent} to identify the feature that needs to * show a support dialog explaining it was disabled by advanced protection. * * @hide */ @@ -156,7 +168,7 @@ public final class AdvancedProtectionManager { "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE"; /** - * A string extra used with {@link #createSupportIntent} to identify the type of the action that + * An int extra used with {@link #createSupportIntent} to identify the type of the action that * needs to be explained in the support dialog. * * @hide */ @@ -194,6 +206,16 @@ public final class AdvancedProtectionManager { @Retention(RetentionPolicy.SOURCE) public @interface SupportDialogType {} + /** @hide */ + public static String supportDialogTypeToString(@SupportDialogType int type) { + return switch(type) { + case SUPPORT_DIALOG_TYPE_UNKNOWN -> "UNKNOWN"; + case SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION -> "BLOCKED_INTERACTION"; + case SUPPORT_DIALOG_TYPE_DISABLED_SETTING -> "DISABLED_SETTING"; + default -> "UNKNOWN"; + }; + } + private static final Set<Integer> ALL_SUPPORT_DIALOG_TYPES = Set.of( SUPPORT_DIALOG_TYPE_UNKNOWN, SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION, @@ -372,6 +394,17 @@ public final class AdvancedProtectionManager { return createSupportIntent(featureId, type); } + /** @hide */ + @RequiresPermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE) + public void logDialogShown(@FeatureId int featureId, @SupportDialogType int type, + boolean learnMoreClicked) { + try { + mService.logDialogShown(featureId, type, learnMoreClicked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * A callback class for monitoring changes to Advanced Protection state * diff --git a/core/java/android/security/advancedprotection/IAdvancedProtectionService.aidl b/core/java/android/security/advancedprotection/IAdvancedProtectionService.aidl index 1939f829c700..0fc0fbea2989 100644 --- a/core/java/android/security/advancedprotection/IAdvancedProtectionService.aidl +++ b/core/java/android/security/advancedprotection/IAdvancedProtectionService.aidl @@ -35,4 +35,6 @@ interface IAdvancedProtectionService { void setAdvancedProtectionEnabled(boolean enabled); @EnforcePermission("MANAGE_ADVANCED_PROTECTION_MODE") List<AdvancedProtectionFeature> getAdvancedProtectionFeatures(); + @EnforcePermission("MANAGE_ADVANCED_PROTECTION_MODE") + void logDialogShown(int featureId, int type, boolean learnMoreClicked); }
\ No newline at end of file diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index ce31e1ea7e38..df3b8baa40c8 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -455,7 +455,7 @@ public class DreamService extends Service implements Window.Callback { // Simply wake up in the case the device is not locked. if (!keyguardManager.isKeyguardLocked()) { - wakeUp(); + wakeUp(false); return true; } @@ -477,11 +477,11 @@ public class DreamService extends Service implements Window.Callback { if (!mInteractive) { if (mDebug) Slog.v(mTag, "Waking up on keyEvent"); - wakeUp(); + wakeUp(false); return true; } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { if (mDebug) Slog.v(mTag, "Waking up on back key"); - wakeUp(); + wakeUp(false); return true; } return mWindow.superDispatchKeyEvent(event); @@ -492,7 +492,7 @@ public class DreamService extends Service implements Window.Callback { public boolean dispatchKeyShortcutEvent(KeyEvent event) { if (!mInteractive) { if (mDebug) Slog.v(mTag, "Waking up on keyShortcutEvent"); - wakeUp(); + wakeUp(false); return true; } return mWindow.superDispatchKeyShortcutEvent(event); @@ -505,7 +505,7 @@ public class DreamService extends Service implements Window.Callback { // but finish()es on any other kind of activity if (!mInteractive && event.getActionMasked() == MotionEvent.ACTION_UP) { if (mDebug) Slog.v(mTag, "Waking up on touchEvent"); - wakeUp(); + wakeUp(false); return true; } return mWindow.superDispatchTouchEvent(event); @@ -516,7 +516,7 @@ public class DreamService extends Service implements Window.Callback { public boolean dispatchTrackballEvent(MotionEvent event) { if (!mInteractive) { if (mDebug) Slog.v(mTag, "Waking up on trackballEvent"); - wakeUp(); + wakeUp(false); return true; } return mWindow.superDispatchTrackballEvent(event); @@ -527,7 +527,7 @@ public class DreamService extends Service implements Window.Callback { public boolean dispatchGenericMotionEvent(MotionEvent event) { if (!mInteractive) { if (mDebug) Slog.v(mTag, "Waking up on genericMotionEvent"); - wakeUp(); + wakeUp(false); return true; } return mWindow.superDispatchGenericMotionEvent(event); @@ -925,32 +925,37 @@ public class DreamService extends Service implements Window.Callback { } } - private synchronized void updateDoze() { - if (mDreamToken == null) { - Slog.w(mTag, "Updating doze without a dream token."); - return; - } + /** + * Updates doze state. Note that this must be called on the mHandler. + */ + private void updateDoze() { + mHandler.post(() -> { + if (mDreamToken == null) { + Slog.w(mTag, "Updating doze without a dream token."); + return; + } - if (mDozing) { - try { - Slog.v(mTag, "UpdateDoze mDozeScreenState=" + mDozeScreenState - + " mDozeScreenBrightness=" + mDozeScreenBrightness - + " mDozeScreenBrightnessFloat=" + mDozeScreenBrightnessFloat); - if (startAndStopDozingInBackground()) { - mDreamManager.startDozingOneway( - mDreamToken, mDozeScreenState, mDozeScreenStateReason, - mDozeScreenBrightnessFloat, mDozeScreenBrightness, - mUseNormalBrightnessForDoze); - } else { - mDreamManager.startDozing( - mDreamToken, mDozeScreenState, mDozeScreenStateReason, - mDozeScreenBrightnessFloat, mDozeScreenBrightness, - mUseNormalBrightnessForDoze); + if (mDozing) { + try { + Slog.v(mTag, "UpdateDoze mDozeScreenState=" + mDozeScreenState + + " mDozeScreenBrightness=" + mDozeScreenBrightness + + " mDozeScreenBrightnessFloat=" + mDozeScreenBrightnessFloat); + if (startAndStopDozingInBackground()) { + mDreamManager.startDozingOneway( + mDreamToken, mDozeScreenState, mDozeScreenStateReason, + mDozeScreenBrightnessFloat, mDozeScreenBrightness, + mUseNormalBrightnessForDoze); + } else { + mDreamManager.startDozing( + mDreamToken, mDozeScreenState, mDozeScreenStateReason, + mDozeScreenBrightnessFloat, mDozeScreenBrightness, + mUseNormalBrightnessForDoze); + } + } catch (RemoteException ex) { + // system server died } - } catch (RemoteException ex) { - // system server died } - } + }); } /** @@ -966,14 +971,20 @@ public class DreamService extends Service implements Window.Callback { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public void stopDozing() { - if (mDozing) { - mDozing = false; - try { - mDreamManager.stopDozing(mDreamToken); - } catch (RemoteException ex) { - // system server died + mHandler.post(() -> { + if (mDreamToken == null) { + return; } - } + + if (mDozing) { + mDozing = false; + try { + mDreamManager.stopDozing(mDreamToken); + } catch (RemoteException ex) { + // system server died + } + } + }); } /** @@ -1201,7 +1212,7 @@ public class DreamService extends Service implements Window.Callback { @Override public void onExitRequested() { // Simply finish dream when exit is requested. - mHandler.post(() -> finish()); + mHandler.post(() -> finishInternal()); } @Override @@ -1299,9 +1310,13 @@ public class DreamService extends Service implements Window.Callback { * </p> */ public final void finish() { + mHandler.post(this::finishInternal); + } + + private void finishInternal() { // If there is an active overlay connection, signal that the dream is ending before - // continuing. Note that the overlay cannot rely on the unbound state, since another dream - // might have bound to it in the meantime. + // continuing. Note that the overlay cannot rely on the unbound state, since another + // dream might have bound to it in the meantime. if (mOverlayConnection != null) { mOverlayConnection.addConsumer(overlay -> { try { @@ -1357,7 +1372,7 @@ public class DreamService extends Service implements Window.Callback { * </p> */ public final void wakeUp() { - wakeUp(false); + mHandler.post(()-> wakeUp(false)); } /** @@ -1559,7 +1574,7 @@ public class DreamService extends Service implements Window.Callback { if (mActivity != null && !mActivity.isFinishing()) { mActivity.finishAndRemoveTask(); } else { - finish(); + finishInternal(); } mDreamToken = null; @@ -1719,7 +1734,7 @@ public class DreamService extends Service implements Window.Callback { // the window reference in order to fully release the DreamActivity. mWindow = null; mActivity = null; - finish(); + finishInternal(); } if (mOverlayConnection != null && mDreamStartOverlayConsumer != null) { diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index 7660ed96d30e..815444d195c9 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -219,7 +219,7 @@ public class NotificationRankingUpdate implements Parcelable { // Gets a read/write buffer mapping the entire shared memory region. buffer = mRankingMapFd.mapReadWrite(); // Puts the ranking map into the shared memory region buffer. - buffer.put(mapParcel.marshall(), 0, mapSize); + mapParcel.marshall(buffer); // Protects the region from being written to, by setting it to be read-only. mRankingMapFd.setProtect(OsConstants.PROT_READ); // Puts the SharedMemory object in the parcel. diff --git a/core/java/android/service/notification/TEST_MAPPING b/core/java/android/service/notification/TEST_MAPPING index dc7129cde5e5..ea7ee4addbe6 100644 --- a/core/java/android/service/notification/TEST_MAPPING +++ b/core/java/android/service/notification/TEST_MAPPING @@ -4,7 +4,10 @@ "name": "CtsNotificationTestCases_notification" }, { - "name": "FrameworksUiServicesTests_notification" + "name": "FrameworksUiServicesNotificationTests" + }, + { + "name": "FrameworksUiServicesZenTests" } ], "postsubmit": [ diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 1cf43d455be8..4cbd5beb3a8c 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -2636,7 +2636,7 @@ public class ZenModeConfig implements Parcelable { enabled = source.readInt() == 1; snoozing = source.readInt() == 1; if (source.readInt() == 1) { - name = source.readString8(); + name = source.readString(); } zenMode = source.readInt(); conditionId = source.readParcelable(null, android.net.Uri.class); @@ -2644,18 +2644,18 @@ public class ZenModeConfig implements Parcelable { component = source.readParcelable(null, android.content.ComponentName.class); configurationActivity = source.readParcelable(null, android.content.ComponentName.class); if (source.readInt() == 1) { - id = source.readString8(); + id = source.readString(); } creationTime = source.readLong(); if (source.readInt() == 1) { - enabler = source.readString8(); + enabler = source.readString(); } zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class); zenDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class); - pkg = source.readString8(); + pkg = source.readString(); allowManualInvocation = source.readBoolean(); - iconResName = source.readString8(); - triggerDescription = source.readString8(); + iconResName = source.readString(); + triggerDescription = source.readString(); type = source.readInt(); userModifiedFields = source.readInt(); zenPolicyUserModifiedFields = source.readInt(); @@ -2703,7 +2703,7 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(snoozing ? 1 : 0); if (name != null) { dest.writeInt(1); - dest.writeString8(name); + dest.writeString(name); } else { dest.writeInt(0); } @@ -2714,23 +2714,23 @@ public class ZenModeConfig implements Parcelable { dest.writeParcelable(configurationActivity, 0); if (id != null) { dest.writeInt(1); - dest.writeString8(id); + dest.writeString(id); } else { dest.writeInt(0); } dest.writeLong(creationTime); if (enabler != null) { dest.writeInt(1); - dest.writeString8(enabler); + dest.writeString(enabler); } else { dest.writeInt(0); } dest.writeParcelable(zenPolicy, 0); dest.writeParcelable(zenDeviceEffects, 0); - dest.writeString8(pkg); + dest.writeString(pkg); dest.writeBoolean(allowManualInvocation); - dest.writeString8(iconResName); - dest.writeString8(triggerDescription); + dest.writeString(iconResName); + dest.writeString(triggerDescription); dest.writeInt(type); dest.writeInt(userModifiedFields); dest.writeInt(zenPolicyUserModifiedFields); diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java index 6ed8c6d195e6..929e39f8b65c 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java @@ -421,7 +421,12 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser Intent intent = new Intent(SERVICE_INTERFACE); intent.setComponent(serviceInfo.getComponentName()); int flags = Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY; - mLifecycleExecutor.execute(() -> mContext.bindService(intent, this, flags)); + if (mServiceInfo == null) { + mLifecycleExecutor.execute(() -> mContext.bindService(intent, this, flags)); + } else { + mLifecycleExecutor.execute(() -> mContext.bindServiceAsUser(intent, this, flags, + UserHandle.of(mServiceInfo.getUserId()))); + } resetServiceConnectionTimeout(); } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 3f45e29f2d43..5c816543e191 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -286,7 +286,7 @@ public final class Display { /** * Display flag: Indicates that the display should show system decorations. * <p> - * This flag identifies secondary displays that should show system decorations, such as + * This flag identifies secondary displays that should always show system decorations, such as * navigation bar, home activity or wallpaper. * </p> * <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p> @@ -401,6 +401,18 @@ public final class Display { public static final int FLAG_ROTATES_WITH_CONTENT = 1 << 14; /** + * Display flag: Indicates that the display is allowed to switch the content mode between + * projected/extended and mirroring. This allows the display to dynamically add or remove the + * home and system decorations. + * + * Note that this flag shouldn't be enabled with {@link #FLAG_PRIVATE} or + * {@link #FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} at the same time; otherwise it will be ignored. + * + * @hide + */ + public static final int FLAG_ALLOWS_CONTENT_MODE_SWITCH = 1 << 15; + + /** * Display flag: Indicates that the contents of the display should not be scaled * to fit the physical screen dimensions. Used for development only to emulate * devices with smaller physicals screens while preserving density. diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index d880072aa404..bf000d5fa39a 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -1125,6 +1125,9 @@ public final class DisplayInfo implements Parcelable { if ((flags & Display.FLAG_REAR) != 0) { result.append(", FLAG_REAR_DISPLAY"); } + if ((flags & Display.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0) { + result.append(", FLAG_ALLOWS_CONTENT_MODE_SWITCH"); + } return result.toString(); } } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 4fc894ca9ff4..421d28ddd431 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -699,7 +699,7 @@ interface IWindowManager /** * Indicates the display should show system decors. * <p> - * System decors include status bar, navigation bar, launcher. + * System decors include status bar, navigation bar, launcher, and wallpaper. * </p> * * @param displayId The id of the display. @@ -719,6 +719,23 @@ interface IWindowManager void setShouldShowSystemDecors(int displayId, boolean shouldShow); /** + * Indicates that the display is eligible for the desktop mode from WindowManager's perspective. + * This includes: + * - The default display; + * - Any display that is allowed to switch the content mode between extended and mirroring + * (which means it can dynamically add or remove system decors), and it is now in extended mode + * (should currently show system decors). + * <p> + * System decors include status bar, navigation bar, launcher, and wallpaper. + * </p> + * + * @param displayId The id of the display. + * @return {@code true} if the display is eligible for the desktop mode from WindowManager's + * perspective. + */ + boolean isEligibleForDesktopMode(int displayId); + + /** * Indicates the policy for how the display should show IME. * * @param displayId The id of the display. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7dc96f21b5ae..2edce5de7ace 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -555,8 +555,6 @@ public final class ViewRootImpl implements ViewParent, @UiContext public final Context mContext; - private UiModeManager mUiModeManager; - @UnsupportedAppUsage final IWindowSession mWindowSession; @NonNull Display mDisplay; @@ -2079,8 +2077,7 @@ public final class ViewRootImpl implements ViewParent, // We also ignore dark theme, since the app developer can override the user's // preference for dark mode in configuration.uiMode. Instead, we assume that both // force invert and the system's dark theme are enabled. - if (getUiModeManager().getForceInvertState() == - UiModeManager.FORCE_INVERT_TYPE_DARK) { + if (shouldApplyForceInvertDark()) { final boolean isLightTheme = a.getBoolean(R.styleable.Theme_isLightTheme, false); // TODO: b/372558459 - Also check the background ColorDrawable color lightness @@ -2108,6 +2105,14 @@ public final class ViewRootImpl implements ViewParent, } } + private boolean shouldApplyForceInvertDark() { + final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class); + if (uiModeManager == null) { + return false; + } + return uiModeManager.getForceInvertState() == UiModeManager.FORCE_INVERT_TYPE_DARK; + } + private void updateForceDarkMode() { if (mAttachInfo.mThreadedRenderer == null) return; if (mAttachInfo.mThreadedRenderer.setForceDark(determineForceDarkType())) { @@ -3474,6 +3479,9 @@ public final class ViewRootImpl implements ViewParent, * TODO(b/260382739): Apply this to all windows. */ private static boolean shouldOptimizeMeasure(final WindowManager.LayoutParams lp) { + if (com.android.window.flags.Flags.reduceUnnecessaryMeasure()) { + return true; + } return (lp.privateFlags & PRIVATE_FLAG_OPTIMIZE_MEASURE) != 0; } @@ -9410,13 +9418,6 @@ public final class ViewRootImpl implements ViewParent, return mAudioManager; } - private UiModeManager getUiModeManager() { - if (mUiModeManager == null) { - mUiModeManager = mContext.getSystemService(UiModeManager.class); - } - return mUiModeManager; - } - private Vibrator getSystemVibrator() { if (mVibrator == null) { mVibrator = mContext.getSystemService(Vibrator.class); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 83dc79beb75e..315f1ba58529 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1514,6 +1514,44 @@ public interface WindowManager extends ViewManager { "android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"; /** + * Application or Activity level + * {@link android.content.pm.PackageManager.Property PackageManager.Property} that specifies + * whether this package or activity wants to allow safe region letterboxing. A safe + * region policy may be applied by the system to improve the user experience by ensuring that + * the activity does not have any content that is occluded and has the correct current + * window metrics. + * + * <p>Not setting the property at all defaults it to {@code true}. In such a case, the activity + * will be letterboxed in the safe region. + * + * <p>To not allow the safe region letterboxing, add this property to your app + * manifest and set the value to {@code false}. An app should ignore safe region + * letterboxing if it can handle bounds and insets from all four directions correctly when a + * request to go immersive is denied by the system. If the application does not allow safe + * region letterboxing, the system will not override this behavior. + * + * <p><b>Syntax:</b> + * <pre> + * <application> + * <property + * android:name="android.window.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING" + * android:value="false"/> + * </application> + * </pre>or + * <pre> + * <activity> + * <property + * android:name="android.window.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING" + * android:value="false"/> + * </activity> + * </pre> + * @hide + */ + @FlaggedApi(Flags.FLAG_SAFE_REGION_LETTERBOXING) + String PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING = + "android.window.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING"; + + /** * @hide */ public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array"; diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index e43fb48527a1..c97c4acf706c 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -866,31 +866,18 @@ public final class AccessibilityManager { } /** - * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services - * for a given feedback type. - * - * @param feedbackTypeFlags The feedback type flags. - * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. - * - * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE - * @see AccessibilityServiceInfo#FEEDBACK_GENERIC - * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC - * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN - * @see AccessibilityServiceInfo#FEEDBACK_VISUAL - * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE + * @see #getEnabledAccessibilityServiceList(int) + * @hide */ public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( - int feedbackTypeFlags) { + int feedbackTypeFlags, int userId) { final IAccessibilityManager service; - final int userId; synchronized (mLock) { service = getServiceLocked(); if (service == null) { return Collections.emptyList(); } - userId = mUserId; } - List<AccessibilityServiceInfo> services = null; try { services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId); @@ -912,6 +899,29 @@ public final class AccessibilityManager { } /** + * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services + * for a given feedback type. + * + * @param feedbackTypeFlags The feedback type flags. + * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. + * + * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE + * @see AccessibilityServiceInfo#FEEDBACK_GENERIC + * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC + * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN + * @see AccessibilityServiceInfo#FEEDBACK_VISUAL + * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE + */ + public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( + int feedbackTypeFlags) { + final int userId; + synchronized (mLock) { + userId = mUserId; + } + return getEnabledAccessibilityServiceList(feedbackTypeFlags, userId); + } + + /** * Returns whether the user must be shown the AccessibilityService warning dialog * before the AccessibilityService (or any shortcut for the service) can be enabled. * @hide diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig index 8c98fa455cc8..3780db38ec84 100644 --- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig +++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig @@ -21,6 +21,7 @@ flag { namespace: "pixel_state_server" description: "Feature flag to send a flush event after each frame" bug: "380381249" + is_exported: true is_fixed_read_only: true metadata { purpose: PURPOSE_BUGFIX diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index a41ab368aed8..b3bd89c2a87d 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2454,6 +2454,7 @@ public final class InputMethodManager { & WindowInsets.Type.ime()) == 0 || viewRootImpl.getInsetsController() .isPredictiveBackImeHideAnimInProgress())) { + Handler vh = view.getHandler(); ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_NO_ONGOING_USER_ANIMATION); if (resultReceiver != null) { @@ -2464,8 +2465,17 @@ public final class InputMethodManager { : InputMethodManager.RESULT_SHOWN, null); } // TODO(b/322992891) handle case of SHOW_IMPLICIT - viewRootImpl.getInsetsController().show(WindowInsets.Type.ime(), - false /* fromIme */, statsToken); + if (vh.getLooper() != Looper.myLooper()) { + // The view is running on a different thread than our own, so + // we need to reschedule our work for over there. + if (DEBUG) Log.v(TAG, "Show soft input: reschedule to view thread"); + final var finalStatsToken = statsToken; + vh.post(() -> viewRootImpl.getInsetsController().show( + WindowInsets.Type.ime(), false /* fromIme */, finalStatsToken)); + } else { + viewRootImpl.getInsetsController().show(WindowInsets.Type.ime(), + false /* fromIme */, statsToken); + } return true; } ImeTracker.forLogging().onCancelled(statsToken, diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java index 3557f16a6dc8..ced27d6d4886 100644 --- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java @@ -28,7 +28,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UiThread; import android.app.UriGrantsManager; import android.content.ContentProvider; import android.content.Intent; @@ -38,6 +37,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; import android.os.CancellationSignalBeamer; +import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -468,13 +468,27 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { }); } + /** + * Returns {@code false} if there is a sessionId mismatch and logs the event. + */ + private boolean checkSessionId(@NonNull InputConnectionCommandHeader header) { + if (header.mSessionId != mCurrentSessionId.get()) { + Log.w(TAG, "Session id mismatch header.sessionId: " + header.mSessionId + + " currentSessionId: " + mCurrentSessionId.get() + " while calling " + + Debug.getCaller()); + //TODO(b/396066692): log metrics. + return false; // cancelled + } + return true; + } + @Dispatching(cancellable = true) @Override public void getTextAfterCursor(InputConnectionCommandHeader header, int length, int flags, AndroidFuture future /* T=CharSequence */) { dispatchWithTracing("getTextAfterCursor", future, () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return null; // cancelled + if (!checkSessionId(header)) { + return null; } final InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -495,8 +509,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void getTextBeforeCursor(InputConnectionCommandHeader header, int length, int flags, AndroidFuture future /* T=CharSequence */) { dispatchWithTracing("getTextBeforeCursor", future, () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return null; // cancelled + if (!checkSessionId(header)) { + return null; } final InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -517,8 +531,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void getSelectedText(InputConnectionCommandHeader header, int flags, AndroidFuture future /* T=CharSequence */) { dispatchWithTracing("getSelectedText", future, () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return null; // cancelled + if (!checkSessionId(header)) { + return null; } final InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -539,8 +553,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void getSurroundingText(InputConnectionCommandHeader header, int beforeLength, int afterLength, int flags, AndroidFuture future /* T=SurroundingText */) { dispatchWithTracing("getSurroundingText", future, () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return null; // cancelled + if (!checkSessionId(header)) { + return null; } final InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -567,8 +581,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void getCursorCapsMode(InputConnectionCommandHeader header, int reqModes, AndroidFuture future /* T=Integer */) { dispatchWithTracing("getCursorCapsMode", future, () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return 0; // cancelled + if (!checkSessionId(header)) { + return 0; } final InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -584,8 +598,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void getExtractedText(InputConnectionCommandHeader header, ExtractedTextRequest request, int flags, AndroidFuture future /* T=ExtractedText */) { dispatchWithTracing("getExtractedText", future, () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return null; // cancelled + if (!checkSessionId(header)) { + return null; } final InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -601,8 +615,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void commitText(InputConnectionCommandHeader header, CharSequence text, int newCursorPosition) { dispatchWithTracing("commitText", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -618,8 +632,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void commitTextWithTextAttribute(InputConnectionCommandHeader header, CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute) { dispatchWithTracing("commitTextWithTextAttribute", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -634,8 +648,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void commitCompletion(InputConnectionCommandHeader header, CompletionInfo text) { dispatchWithTracing("commitCompletion", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -650,8 +664,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void commitCorrection(InputConnectionCommandHeader header, CorrectionInfo info) { dispatchWithTracing("commitCorrection", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -670,8 +684,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void setSelection(InputConnectionCommandHeader header, int start, int end) { dispatchWithTracing("setSelection", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -686,8 +700,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void performEditorAction(InputConnectionCommandHeader header, int id) { dispatchWithTracing("performEditorAction", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -702,8 +716,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void performContextMenuAction(InputConnectionCommandHeader header, int id) { dispatchWithTracing("performContextMenuAction", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -718,8 +732,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void setComposingRegion(InputConnectionCommandHeader header, int start, int end) { dispatchWithTracing("setComposingRegion", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -739,8 +753,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void setComposingRegionWithTextAttribute(InputConnectionCommandHeader header, int start, int end, @Nullable TextAttribute textAttribute) { dispatchWithTracing("setComposingRegionWithTextAttribute", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -756,8 +770,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void setComposingText(InputConnectionCommandHeader header, CharSequence text, int newCursorPosition) { dispatchWithTracing("setComposingText", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -773,8 +787,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void setComposingTextWithTextAttribute(InputConnectionCommandHeader header, CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute) { dispatchWithTracing("setComposingTextWithTextAttribute", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -826,8 +840,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { } return; } - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null && mDeactivateRequested.get()) { @@ -842,8 +856,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void sendKeyEvent(InputConnectionCommandHeader header, KeyEvent event) { dispatchWithTracing("sendKeyEvent", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -858,8 +872,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void clearMetaKeyStates(InputConnectionCommandHeader header, int states) { dispatchWithTracing("clearMetaKeyStates", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -875,8 +889,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void deleteSurroundingText(InputConnectionCommandHeader header, int beforeLength, int afterLength) { dispatchWithTracing("deleteSurroundingText", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -892,8 +906,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void deleteSurroundingTextInCodePoints(InputConnectionCommandHeader header, int beforeLength, int afterLength) { dispatchWithTracing("deleteSurroundingTextInCodePoints", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -912,8 +926,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void beginBatchEdit(InputConnectionCommandHeader header) { dispatchWithTracing("beginBatchEdit", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -928,8 +942,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void endBatchEdit(InputConnectionCommandHeader header) { dispatchWithTracing("endBatchEdit", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -944,8 +958,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void performSpellCheck(InputConnectionCommandHeader header) { dispatchWithTracing("performSpellCheck", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -961,8 +975,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void performPrivateCommand(InputConnectionCommandHeader header, String action, Bundle data) { dispatchWithTracing("performPrivateCommand", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -995,12 +1009,12 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { } } dispatchWithTracing("performHandwritingGesture", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { + if (!checkSessionId(header)) { if (resultReceiver != null) { resultReceiver.send( InputConnection.HANDWRITING_GESTURE_RESULT_CANCELLED, null); } - return; // cancelled + return; // cancelled } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1038,9 +1052,9 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { (PreviewableHandwritingGesture) gestureContainer.get(); dispatchWithTracing("previewHandwritingGesture", () -> { - if (header.mSessionId != mCurrentSessionId.get() + if (!checkSessionId(header) || (cancellationSignal != null && cancellationSignal.isCanceled())) { - return; // cancelled + return; // cancelled } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1065,8 +1079,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode, int imeDisplayId, AndroidFuture future /* T=Boolean */) { dispatchWithTracing("requestCursorUpdates", future, () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return false; // cancelled + if (!checkSessionId(header)) { + return false; // cancelled. } return requestCursorUpdatesInternal( cursorUpdateMode, 0 /* cursorUpdateFilter */, imeDisplayId); @@ -1079,8 +1093,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId, AndroidFuture future /* T=Boolean */) { dispatchWithTracing("requestCursorUpdates", future, () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return false; // cancelled + if (!checkSessionId(header)) { + return false; // cancelled. } return requestCursorUpdatesInternal( cursorUpdateMode, cursorUpdateFilter, imeDisplayId); @@ -1123,9 +1137,9 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { InputConnectionCommandHeader header, RectF bounds, @NonNull ResultReceiver resultReceiver) { dispatchWithTracing("requestTextBoundsInfo", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { + if (!checkSessionId(header)) { resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null); - return; // cancelled + return; // cancelled } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1168,8 +1182,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return false; } - if (header.mSessionId != mCurrentSessionId.get()) { - return false; // cancelled + if (!checkSessionId(header)) { + return false; // cancelled. } final InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1193,8 +1207,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void setImeConsumesInput(InputConnectionCommandHeader header, boolean imeConsumesInput) { dispatchWithTracing("setImeConsumesInput", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1217,8 +1231,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { dispatchWithTracing( "replaceText", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1236,8 +1250,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void commitText(InputConnectionCommandHeader header, CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute) { dispatchWithTracing("commitTextFromA11yIme", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1256,8 +1270,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void setSelection(InputConnectionCommandHeader header, int start, int end) { dispatchWithTracing("setSelectionFromA11yIme", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1273,8 +1287,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void getSurroundingText(InputConnectionCommandHeader header, int beforeLength, int afterLength, int flags, AndroidFuture future /* T=SurroundingText */) { dispatchWithTracing("getSurroundingTextFromA11yIme", future, () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return null; // cancelled + if (!checkSessionId(header)) { + return null; // cancelled. } final InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1301,8 +1315,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void deleteSurroundingText(InputConnectionCommandHeader header, int beforeLength, int afterLength) { dispatchWithTracing("deleteSurroundingTextFromA11yIme", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1317,8 +1331,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void sendKeyEvent(InputConnectionCommandHeader header, KeyEvent event) { dispatchWithTracing("sendKeyEventFromA11yIme", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1333,8 +1347,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void performEditorAction(InputConnectionCommandHeader header, int id) { dispatchWithTracing("performEditorActionFromA11yIme", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1349,8 +1363,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void performContextMenuAction(InputConnectionCommandHeader header, int id) { dispatchWithTracing("performContextMenuActionFromA11yIme", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1366,8 +1380,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { public void getCursorCapsMode(InputConnectionCommandHeader header, int reqModes, AndroidFuture future /* T=Integer */) { dispatchWithTracing("getCursorCapsModeFromA11yIme", future, () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return 0; // cancelled + if (!checkSessionId(header)) { + return 0; // cancelled. } final InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { @@ -1382,8 +1396,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Override public void clearMetaKeyStates(InputConnectionCommandHeader header, int states) { dispatchWithTracing("clearMetaKeyStatesFromA11yIme", () -> { - if (header.mSessionId != mCurrentSessionId.get()) { - return; // cancelled + if (!checkSessionId(header)) { + return; // cancelled. } InputConnection ic = getInputConnection(); if (ic == null || mDeactivateRequested.get()) { diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index a4ea64e5811e..cdca4102dd96 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -214,3 +214,24 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "invalidate_input_calls_restart" + namespace: "input_method" + description: "Feature flag to fix the race between invalidateInput and restartInput" + bug: "396066692" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "disallow_disabling_ime_navigation_bar" + namespace: "input_method" + description: "Disallows disabling the IME navigation bar through canImeRenderGesturalNavButtons" + bug: "402442590" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java index cc2afbc6aaa3..d53c787749d9 100644 --- a/core/java/android/window/BackMotionEvent.java +++ b/core/java/android/window/BackMotionEvent.java @@ -18,7 +18,6 @@ package android.window; import android.annotation.FloatRange; import android.annotation.NonNull; -import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.view.RemoteAnimationTarget; @@ -39,8 +38,6 @@ public final class BackMotionEvent implements Parcelable { @BackEvent.SwipeEdge private final int mSwipeEdge; - @Nullable - private final RemoteAnimationTarget mDepartingAnimationTarget; /** * Creates a new {@link BackMotionEvent} instance. @@ -53,8 +50,6 @@ public final class BackMotionEvent implements Parcelable { * @param progress Value between 0 and 1 on how far along the back gesture is. * @param triggerBack Indicates whether the back arrow is in the triggered state or not * @param swipeEdge Indicates which edge the swipe starts from. - * @param departingAnimationTarget The remote animation target of the departing - * application window. */ public BackMotionEvent( float touchX, @@ -62,15 +57,13 @@ public final class BackMotionEvent implements Parcelable { long frameTimeMillis, float progress, boolean triggerBack, - @BackEvent.SwipeEdge int swipeEdge, - @Nullable RemoteAnimationTarget departingAnimationTarget) { + @BackEvent.SwipeEdge int swipeEdge) { mTouchX = touchX; mTouchY = touchY; mFrameTimeMillis = frameTimeMillis; mProgress = progress; mTriggerBack = triggerBack; mSwipeEdge = swipeEdge; - mDepartingAnimationTarget = departingAnimationTarget; } private BackMotionEvent(@NonNull Parcel in) { @@ -79,7 +72,6 @@ public final class BackMotionEvent implements Parcelable { mProgress = in.readFloat(); mTriggerBack = in.readBoolean(); mSwipeEdge = in.readInt(); - mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR); mFrameTimeMillis = in.readLong(); } @@ -108,7 +100,6 @@ public final class BackMotionEvent implements Parcelable { dest.writeFloat(mProgress); dest.writeBoolean(mTriggerBack); dest.writeInt(mSwipeEdge); - dest.writeTypedObject(mDepartingAnimationTarget, flags); dest.writeLong(mFrameTimeMillis); } @@ -160,16 +151,6 @@ public final class BackMotionEvent implements Parcelable { return mFrameTimeMillis; } - /** - * Returns the {@link RemoteAnimationTarget} of the top departing application window, - * or {@code null} if the top window should not be moved for the current type of back - * destination. - */ - @Nullable - public RemoteAnimationTarget getDepartingAnimationTarget() { - return mDepartingAnimationTarget; - } - @Override public String toString() { return "BackMotionEvent{" @@ -179,7 +160,6 @@ public final class BackMotionEvent implements Parcelable { + ", mProgress=" + mProgress + ", mTriggerBack=" + mTriggerBack + ", mSwipeEdge=" + mSwipeEdge - + ", mDepartingAnimationTarget=" + mDepartingAnimationTarget + "}"; } } diff --git a/core/java/android/window/BackTouchTracker.java b/core/java/android/window/BackTouchTracker.java index 4908068d51e4..ea1b64066cfe 100644 --- a/core/java/android/window/BackTouchTracker.java +++ b/core/java/android/window/BackTouchTracker.java @@ -20,7 +20,6 @@ import android.annotation.FloatRange; import android.os.SystemProperties; import android.util.MathUtils; import android.view.MotionEvent; -import android.view.RemoteAnimationTarget; import java.io.PrintWriter; @@ -147,15 +146,14 @@ public class BackTouchTracker { } /** Creates a start {@link BackMotionEvent}. */ - public BackMotionEvent createStartEvent(RemoteAnimationTarget target) { + public BackMotionEvent createStartEvent() { return new BackMotionEvent( /* touchX = */ mInitTouchX, /* touchY = */ mInitTouchY, /* frameTimeMillis = */ 0, /* progress = */ 0, /* triggerBack = */ mTriggerBack, - /* swipeEdge = */ mSwipeEdge, - /* departingAnimationTarget = */ target); + /* swipeEdge = */ mSwipeEdge); } /** Creates a progress {@link BackMotionEvent}. */ @@ -239,8 +237,7 @@ public class BackTouchTracker { /* frameTimeMillis = */ 0, /* progress = */ progress, /* triggerBack = */ mTriggerBack, - /* swipeEdge = */ mSwipeEdge, - /* departingAnimationTarget = */ null); + /* swipeEdge = */ mSwipeEdge); } /** Sets the thresholds for computing progress. */ diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java index 866c16cb566d..5e8ce5ee557f 100644 --- a/core/java/android/window/DesktopExperienceFlags.java +++ b/core/java/android/window/DesktopExperienceFlags.java @@ -56,9 +56,13 @@ public enum DesktopExperienceFlags { ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT( com.android.server.display.feature.flags.Flags::enableDisplayContentModeManagement, true), + ENABLE_DISPLAY_DISCONNECT_INTERACTION(Flags::enableDisplayDisconnectInteraction, false), ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS(Flags::enableDisplayFocusInShellTransitions, true), + ENABLE_DISPLAY_RECONNECT_INTERACTION(Flags::enableDisplayReconnectInteraction, false), ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING(Flags::enableDisplayWindowingModeSwitching, true), ENABLE_DRAG_TO_MAXIMIZE(Flags::enableDragToMaximize, true), + 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_MULTIPLE_DESKTOPS_BACKEND(Flags::enableMultipleDesktopsBackend, false), ENABLE_MULTIPLE_DESKTOPS_FRONTEND(Flags::enableMultipleDesktopsFrontend, false), diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index aecf6eb261b1..5b3044e1988a 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -146,6 +146,8 @@ public enum DesktopModeFlags { Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true), INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES( Flags::inheritTaskBoundsForTrampolineTaskLaunches, false), + SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX( + Flags::skipDecorViewRelayoutWhenClosingBugfix, false), // go/keep-sorted end ; diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl index 6f4dd4e3c5ed..0b84070c8d26 100644 --- a/core/java/android/window/IWindowOrganizerController.aidl +++ b/core/java/android/window/IWindowOrganizerController.aidl @@ -66,17 +66,6 @@ interface IWindowOrganizerController { void startTransition(IBinder transitionToken, in @nullable WindowContainerTransaction t); /** - * Starts a legacy transition. - * @param type The transition type. - * @param adapter The animation to use. - * @param syncCallback A sync callback for the contents of `t` - * @param t Operations that are part of the transition. - * @return sync-id or -1 if this no-op'd because a transition is already running. - */ - int startLegacyTransition(int type, in RemoteAnimationAdapter adapter, - in IWindowContainerTransactionCallback syncCallback, in WindowContainerTransaction t); - - /** * Finishes a transition. This must be called for all created transitions. * @param transitionToken Which transition to finish * @param t Changes to make before finishing but in the same SF Transaction. Can be null. diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java index d478108d928a..69613a748884 100644 --- a/core/java/android/window/ImeOnBackInvokedDispatcher.java +++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java @@ -270,8 +270,7 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc } mIOnBackInvokedCallback.onBackStarted( new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(), frameTime, - backEvent.getProgress(), false, backEvent.getSwipeEdge(), - null)); + backEvent.getProgress(), false, backEvent.getSwipeEdge())); } catch (RemoteException e) { Log.e(TAG, "Exception when invoking forwarded callback. e: ", e); } @@ -286,8 +285,7 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc } mIOnBackInvokedCallback.onBackProgressed( new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(), frameTime, - backEvent.getProgress(), false, backEvent.getSwipeEdge(), - null)); + backEvent.getProgress(), false, backEvent.getSwipeEdge())); } catch (RemoteException e) { Log.e(TAG, "Exception when invoking forwarded callback. e: ", e); } diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 485e7b33f3a7..2ed9c3a48add 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -445,6 +445,27 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + /** + * Sets a given safe region {@code Rect} on the {@code container}. Set {@code null} to reset + * safe region bounds. When a safe region is set on a WindowContainer, the activities which + * need to be within a safe region will be letterboxed within the set safe region bounds. + * <p>Note that if the position of the WindowContainer changes, the caller needs to update the + * safe region bounds. + * + * @param container The window container that the safe region bounds are set on + * @param safeRegionBounds The rect for the safe region bounds which are absolute in nature. + * @hide + */ + @NonNull + @FlaggedApi(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public WindowContainerTransaction setSafeRegionBounds( + @NonNull WindowContainerToken container, + @Nullable Rect safeRegionBounds) { + mHierarchyOps.add( + HierarchyOp.createForSetSafeRegionBounds(container.asBinder(), safeRegionBounds)); + return this; + } + /* * =========================================================================================== * Hierarchy updates (create/destroy/reorder/reparent containers) @@ -1597,6 +1618,7 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT = 23; public static final int HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK = 24; public static final int HIERARCHY_OP_TYPE_APP_COMPAT_REACHABILITY = 25; + public static final int HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS = 26; @IntDef(prefix = {"HIERARCHY_OP_TYPE_"}, value = { HIERARCHY_OP_TYPE_REPARENT, @@ -1625,6 +1647,7 @@ public final class WindowContainerTransaction implements Parcelable { HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT, HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK, HIERARCHY_OP_TYPE_APP_COMPAT_REACHABILITY, + HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS, }) @Retention(RetentionPolicy.SOURCE) public @interface HierarchyOpType { @@ -1710,6 +1733,9 @@ public final class WindowContainerTransaction implements Parcelable { private boolean mLaunchAdjacentDisabled; + @Nullable + private Rect mSafeRegionBounds; + /** Creates a hierarchy operation for reparenting a container within the hierarchy. */ @NonNull public static HierarchyOp createForReparent( @@ -1873,6 +1899,17 @@ public final class WindowContainerTransaction implements Parcelable { .build(); } + /** Creates a hierarchy op for setting the safe region bounds. */ + @NonNull + @FlaggedApi(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public static HierarchyOp createForSetSafeRegionBounds(@NonNull IBinder container, + @Nullable Rect safeRegionBounds) { + return new Builder(HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS) + .setContainer(container) + .setSafeRegionBounds(safeRegionBounds) + .build(); + } + /** Only creates through {@link Builder}. */ private HierarchyOp(@HierarchyOpType int type) { mType = type; @@ -1903,6 +1940,7 @@ public final class WindowContainerTransaction implements Parcelable { mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents; mExcludeInsetsTypes = copy.mExcludeInsetsTypes; mLaunchAdjacentDisabled = copy.mLaunchAdjacentDisabled; + mSafeRegionBounds = copy.mSafeRegionBounds; } private HierarchyOp(@NonNull Parcel in) { @@ -1930,6 +1968,7 @@ public final class WindowContainerTransaction implements Parcelable { mIsTrimmableFromRecents = in.readBoolean(); mExcludeInsetsTypes = in.readInt(); mLaunchAdjacentDisabled = in.readBoolean(); + mSafeRegionBounds = in.readTypedObject(Rect.CREATOR); } @HierarchyOpType @@ -2051,6 +2090,12 @@ public final class WindowContainerTransaction implements Parcelable { return mLaunchAdjacentDisabled; } + /** Denotes the safe region bounds */ + @Nullable + public Rect getSafeRegionBounds() { + return mSafeRegionBounds; + } + /** Gets a string representation of a hierarchy-op type. */ public static String hopToString(@HierarchyOpType int type) { switch (type) { @@ -2084,6 +2129,7 @@ public final class WindowContainerTransaction implements Parcelable { case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: return "restoreBackNav"; case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: return "setExcludeInsetsTypes"; case HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE: return "setKeyguardState"; + case HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS: return "setSafeRegionBounds"; default: return "HOP(" + type + ")"; } } @@ -2184,6 +2230,11 @@ public final class WindowContainerTransaction implements Parcelable { sb.append("container= ").append(mContainer) .append(" isTrimmable= ") .append(mIsTrimmableFromRecents); + break; + case HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS: + sb.append("container= ").append(mContainer) + .append(" safeRegionBounds= ") + .append(mSafeRegionBounds); default: sb.append("container=").append(mContainer) .append(" reparent=").append(mReparent) @@ -2220,6 +2271,7 @@ public final class WindowContainerTransaction implements Parcelable { dest.writeBoolean(mIsTrimmableFromRecents); dest.writeInt(mExcludeInsetsTypes); dest.writeBoolean(mLaunchAdjacentDisabled); + dest.writeTypedObject(mSafeRegionBounds, flags); } @Override @@ -2305,6 +2357,9 @@ public final class WindowContainerTransaction implements Parcelable { private boolean mLaunchAdjacentDisabled; + @Nullable + private Rect mSafeRegionBounds; + Builder(@HierarchyOpType int type) { mType = type; } @@ -2426,6 +2481,11 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + Builder setSafeRegionBounds(Rect safeRegionBounds) { + mSafeRegionBounds = safeRegionBounds; + return this; + } + @NonNull HierarchyOp build() { final HierarchyOp hierarchyOp = new HierarchyOp(mType); @@ -2456,7 +2516,7 @@ public final class WindowContainerTransaction implements Parcelable { hierarchyOp.mIsTrimmableFromRecents = mIsTrimmableFromRecents; hierarchyOp.mExcludeInsetsTypes = mExcludeInsetsTypes; hierarchyOp.mLaunchAdjacentDisabled = mLaunchAdjacentDisabled; - + hierarchyOp.mSafeRegionBounds = mSafeRegionBounds; return hierarchyOp; } } diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java index 5c5da49fe543..6e56b6318727 100644 --- a/core/java/android/window/WindowOrganizer.java +++ b/core/java/android/window/WindowOrganizer.java @@ -130,27 +130,6 @@ public class WindowOrganizer { } /** - * Start a legacy transition. - * @param type The type of the transition. This is ignored if a transitionToken is provided. - * @param adapter An existing transition to start. If null, a new transition is created. - * @param t The set of window operations that are part of this transition. - * @return true on success, false if a transition was already running. - * @hide - */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) - @NonNull - public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter, - @NonNull WindowContainerTransactionCallback syncCallback, - @NonNull WindowContainerTransaction t) { - try { - return getWindowOrganizerController().startLegacyTransition( - type, adapter, syncCallback.mInterface, t); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Register an ITransitionPlayer to handle transition animations. * @hide */ diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index a42759e9e23f..cc07616412b6 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -155,4 +155,12 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "backup_and_restore_for_user_aspect_ratio_settings" + namespace: "large_screen_experiences_app_compat" + description: "Whether B&R for user aspect ratio settings is enabled" + bug: "396650383" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index e706af999117..afc9660c1a3a 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -602,6 +602,13 @@ flag { } flag { + name: "keyboard_shortcuts_to_switch_desks" + namespace: "lse_desktop_experience" + description: "Enable switching the active desk with keyboard shortcuts" + bug: "389957556" +} + +flag { name: "enable_connected_displays_dnd" namespace: "lse_desktop_experience" description: "Enable drag-and-drop between connected displays." @@ -926,6 +933,16 @@ flag { } flag { + name: "skip_decor_view_relayout_when_closing_bugfix" + namespace: "lse_desktop_experience" + description: "Enables bugfix to skip DecorView relayout when the corresponding window is closing." + bug: "394502142" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "enable_size_compat_mode_improvements_for_connected_displays" namespace: "lse_desktop_experience" description: "Enable some improvements in size compat mode for connected displays." @@ -945,3 +962,20 @@ flag { description: "Enable restart menu UI, which is shown when an app moves between displays." bug: "397804287" } + +flag { + name: "enable_dynamic_radius_computation_bugfix" + namespace: "lse_desktop_experience" + description: "Enables bugfix to compute the corner/shadow radius of desktop windows dynamically with the current window context." + bug: "399630464" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "show_home_behind_desktop" + namespace: "lse_desktop_experience" + description: "Enables the home to be shown behind the desktop." + bug: "375644149" +} diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 3927c713e500..816270235446 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -9,16 +9,6 @@ flag { } flag { - name: "wait_for_transition_on_display_switch" - namespace: "windowing_frontend" - description: "Waits for Shell transition to start before unblocking the screen after display switch" - bug: "301420598" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "apply_lifecycle_on_pip_change" namespace: "windowing_frontend" description: "Make pip activity lifecyle change with windowing mode" @@ -29,17 +19,6 @@ flag { } flag { - name: "respect_animation_clip" - namespace: "windowing_frontend" - description: "Fix missing clip transformation of animation" - bug: "376601866" - is_fixed_read_only: true - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "cache_window_style" namespace: "windowing_frontend" description: "Cache common window styles" @@ -297,6 +276,17 @@ flag { } flag { + name: "reduce_unnecessary_measure" + namespace: "windowing_frontend" + description: "Skip measuring view hierarchy if the size is known" + bug: "260382739" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "ensure_wallpaper_in_transitions" namespace: "windowing_frontend" description: "Ensure that wallpaper window tokens are always present/available for collection in transitions" diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index 1281a78d4fa2..24e80209e9e9 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -136,7 +136,8 @@ public class AccessibilityShortcutController { private final Context mContext; private final Handler mHandler; - private final UserSetupCompleteObserver mUserSetupCompleteObserver; + @VisibleForTesting + public final UserSetupCompleteObserver mUserSetupCompleteObserver; private AlertDialog mAlertDialog; private boolean mIsShortcutEnabled; @@ -471,7 +472,7 @@ public class AccessibilityShortcutController { AccessibilityManager accessibilityManager = mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext); return accessibilityManager.getEnabledAccessibilityServiceList( - FEEDBACK_ALL_MASK).contains(serviceInfo); + FEEDBACK_ALL_MASK, mUserId).contains(serviceInfo); } private boolean hasFeatureLeanback() { @@ -676,7 +677,8 @@ public class AccessibilityShortcutController { } } - private class UserSetupCompleteObserver extends ContentObserver { + @VisibleForTesting + public class UserSetupCompleteObserver extends ContentObserver { private boolean mIsRegistered = false; private int mUserId; @@ -749,7 +751,8 @@ public class AccessibilityShortcutController { R.string.config_defaultAccessibilityService); final List<AccessibilityServiceInfo> enabledServices = mFrameworkObjectProvider.getAccessibilityManagerInstance( - mContext).getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK); + mContext).getEnabledAccessibilityServiceList( + FEEDBACK_ALL_MASK, mUserId); for (int i = enabledServices.size() - 1; i >= 0; i--) { if (TextUtils.equals(defaultShortcutTarget, enabledServices.get(i).getId())) { return; diff --git a/core/java/com/android/internal/app/MediaRouteChooserContentManager.java b/core/java/com/android/internal/app/MediaRouteChooserContentManager.java new file mode 100644 index 000000000000..09c6f5e6caaa --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteChooserContentManager.java @@ -0,0 +1,56 @@ +/* + * 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.internal.app; + +import android.content.Context; +import android.view.Gravity; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.ListView; + +import com.android.internal.R; + +public class MediaRouteChooserContentManager { + Context mContext; + + private final boolean mShowProgressBarWhenEmpty; + + public MediaRouteChooserContentManager(Context context, boolean showProgressBarWhenEmpty) { + mContext = context; + mShowProgressBarWhenEmpty = showProgressBarWhenEmpty; + } + + /** + * Starts binding all the views (list view, empty view, etc.) using the + * given container view. + */ + public void bindViews(View containerView) { + View emptyView = containerView.findViewById(android.R.id.empty); + ListView listView = containerView.findViewById(R.id.media_route_list); + listView.setEmptyView(emptyView); + + if (!mShowProgressBarWhenEmpty) { + containerView.findViewById(R.id.media_route_progress_bar).setVisibility(View.GONE); + + // Center the empty view when the progress bar is not shown. + LinearLayout.LayoutParams params = + (LinearLayout.LayoutParams) emptyView.getLayoutParams(); + params.gravity = Gravity.CENTER; + emptyView.setLayoutParams(params); + } + } +} diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java index 23d966fdbc0d..5030a143ea94 100644 --- a/core/java/com/android/internal/app/MediaRouteChooserDialog.java +++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java @@ -23,14 +23,12 @@ import android.media.MediaRouter.RouteInfo; import android.os.Bundle; import android.text.TextUtils; import android.util.TypedValue; -import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; -import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; @@ -52,15 +50,15 @@ import java.util.Comparator; public class MediaRouteChooserDialog extends AlertDialog { private final MediaRouter mRouter; private final MediaRouterCallback mCallback; - private final boolean mShowProgressBarWhenEmpty; private int mRouteTypes; private View.OnClickListener mExtendedSettingsClickListener; private RouteAdapter mAdapter; - private ListView mListView; private Button mExtendedSettingsButton; private boolean mAttachedToWindow; + private final MediaRouteChooserContentManager mContentManager; + public MediaRouteChooserDialog(Context context, int theme) { this(context, theme, true); } @@ -70,7 +68,7 @@ public class MediaRouteChooserDialog extends AlertDialog { mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); mCallback = new MediaRouterCallback(); - mShowProgressBarWhenEmpty = showProgressBarWhenEmpty; + mContentManager = new MediaRouteChooserContentManager(context, showProgressBarWhenEmpty); } /** @@ -128,8 +126,9 @@ public class MediaRouteChooserDialog extends AlertDialog { @Override protected void onCreate(Bundle savedInstanceState) { // Note: setView must be called before super.onCreate(). - setView(LayoutInflater.from(getContext()).inflate(R.layout.media_route_chooser_dialog, - null)); + View containerView = LayoutInflater.from(getContext()).inflate( + R.layout.media_route_chooser_dialog, null); + setView(containerView); setTitle(mRouteTypes == MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY ? R.string.media_route_chooser_title_for_remote_display @@ -140,25 +139,15 @@ public class MediaRouteChooserDialog extends AlertDialog { super.onCreate(savedInstanceState); - View emptyView = findViewById(android.R.id.empty); mAdapter = new RouteAdapter(getContext()); - mListView = (ListView) findViewById(R.id.media_route_list); - mListView.setAdapter(mAdapter); - mListView.setOnItemClickListener(mAdapter); - mListView.setEmptyView(emptyView); + ListView listView = findViewById(R.id.media_route_list); + listView.setAdapter(mAdapter); + listView.setOnItemClickListener(mAdapter); - mExtendedSettingsButton = (Button) findViewById(R.id.media_route_extended_settings_button); + mExtendedSettingsButton = findViewById(R.id.media_route_extended_settings_button); updateExtendedSettingsButton(); - if (!mShowProgressBarWhenEmpty) { - findViewById(R.id.media_route_progress_bar).setVisibility(View.GONE); - - // Center the empty view when the progress bar is not shown. - LinearLayout.LayoutParams params = - (LinearLayout.LayoutParams) emptyView.getLayoutParams(); - params.gravity = Gravity.CENTER; - emptyView.setLayoutParams(params); - } + mContentManager.bindViews(containerView); } private void updateExtendedSettingsButton() { @@ -240,8 +229,8 @@ public class MediaRouteChooserDialog extends AlertDialog { view = mInflater.inflate(R.layout.media_route_list_item, parent, false); } MediaRouter.RouteInfo route = getItem(position); - TextView text1 = (TextView)view.findViewById(android.R.id.text1); - TextView text2 = (TextView)view.findViewById(android.R.id.text2); + TextView text1 = view.findViewById(android.R.id.text1); + TextView text2 = view.findViewById(android.R.id.text2); text1.setText(route.getName()); CharSequence description = route.getDescription(); if (TextUtils.isEmpty(description)) { diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index c6207f9451c2..8151429f9139 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -672,6 +672,7 @@ public class BatteryStatsHistory { */ public void reset() { synchronized (this) { + mMonotonicHistorySize = 0; initHistoryBuffer(); if (mStore != null) { mStore.reset(); diff --git a/core/java/com/android/internal/policy/DesktopModeCompatUtils.java b/core/java/com/android/internal/policy/DesktopModeCompatUtils.java new file mode 100644 index 000000000000..d7cfbdfed99c --- /dev/null +++ b/core/java/com/android/internal/policy/DesktopModeCompatUtils.java @@ -0,0 +1,60 @@ +/* + * 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.internal.policy; + +import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED; +import static android.content.pm.ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS; + +import android.annotation.NonNull; +import android.content.pm.ActivityInfo; +import android.window.DesktopModeFlags; + +/** + * Utility functions for app compat in desktop windowing used by both window manager and System UI. + * @hide + */ +public final class DesktopModeCompatUtils { + + /** + * Whether the caption insets should be excluded from configuration for system to handle. + * The caller should ensure the activity is in or entering desktop view. + * + * <p> The treatment is enabled when all the of the following is true: + * <li> Any flags to forcibly consume caption insets are enabled. + * <li> Top activity have configuration coupled with insets. + * <li> Task is not resizeable or per-app override + * {@link ActivityInfo#OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS} is enabled. + */ + public static boolean shouldExcludeCaptionFromAppBounds(@NonNull ActivityInfo info, + boolean isResizeable, boolean optOutEdgeToEdge) { + return DesktopModeFlags.EXCLUDE_CAPTION_FROM_APP_BOUNDS.isTrue() + && isAnyForceConsumptionFlagsEnabled() + && !isConfigurationDecoupled(info, optOutEdgeToEdge) + && (!isResizeable + || info.isChangeEnabled(OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS)); + } + + private static boolean isConfigurationDecoupled(@NonNull ActivityInfo info, + boolean optOutEdgeToEdge) { + return info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED) && !optOutEdgeToEdge; + } + + private static boolean isAnyForceConsumptionFlagsEnabled() { + return DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isTrue() + || DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue(); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java b/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java index a53d6b899898..3fe6873028cd 100644 --- a/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java +++ b/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java @@ -132,8 +132,13 @@ public class AndroidPlatformSemanticNodeApplier } } - // TODO correct values - nodeInfo.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain(-1, 1, false)); + if (scrollDirection == RootContentBehavior.SCROLL_HORIZONTAL) { + nodeInfo.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain(1, -1, false)); + nodeInfo.setClassName("android.widget.HorizontalScrollView"); + } else { + nodeInfo.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain(-1, 1, false)); + nodeInfo.setClassName("android.widget.ScrollView"); + } if (scrollDirection == RootContentBehavior.SCROLL_HORIZONTAL) { nodeInfo.setClassName("android.widget.HorizontalScrollView"); diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java index d3a496db2ca9..db2c46046561 100644 --- a/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java +++ b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java @@ -33,6 +33,7 @@ import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySem import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent; import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics; import com.android.internal.widget.remotecompose.core.semantics.ScrollableComponent; +import com.android.internal.widget.remotecompose.core.semantics.ScrollableComponent.ScrollDirection; import java.util.ArrayList; import java.util.Collections; @@ -104,9 +105,9 @@ public class CoreDocumentAccessibility implements RemoteComposeDocumentAccessibi if (isClickAction(action)) { return performClick(component); } else if (isScrollForwardAction(action)) { - return scrollByOffset(mRemoteContext, component, -500) != 0; + return scrollDirection(mRemoteContext, component, ScrollDirection.FORWARD); } else if (isScrollBackwardAction(action)) { - return scrollByOffset(mRemoteContext, component, 500) != 0; + return scrollDirection(mRemoteContext, component, ScrollDirection.BACKWARD); } else if (isShowOnScreenAction(action)) { return showOnScreen(mRemoteContext, component); } else { @@ -141,17 +142,30 @@ public class CoreDocumentAccessibility implements RemoteComposeDocumentAccessibi } private boolean showOnScreen(RemoteContext context, Component component) { - if (component.getParent() instanceof LayoutComponent) { - LayoutComponent parent = (LayoutComponent) component.getParent(); + ScrollableComponent scrollable = findScrollable(component); + + if (scrollable != null) { + return scrollable.showOnScreen(context, component); + } + + return false; + } + + @Nullable + private static ScrollableComponent findScrollable(Component component) { + Component parent = component.getParent(); + + while (parent != null) { ScrollableComponent scrollable = parent.selfOrModifier(ScrollableComponent.class); if (scrollable != null) { - scrollable.showOnScreen(context, component.getComponentId()); - return true; + return scrollable; + } else { + parent = parent.getParent(); } } - return false; + return null; } /** @@ -173,13 +187,32 @@ public class CoreDocumentAccessibility implements RemoteComposeDocumentAccessibi } /** + * scroll content in a given direction + * + * @param context + * @param component + * @param direction + * @return + */ + public boolean scrollDirection( + RemoteContext context, Component component, ScrollDirection direction) { + ScrollableComponent scrollable = component.selfOrModifier(ScrollableComponent.class); + + if (scrollable != null) { + return scrollable.scrollDirection(context, direction); + } + + return false; + } + + /** * Perform a click on the given component * * @param component * @return */ public boolean performClick(Component component) { - mDocument.performClick(mRemoteContext, component.getComponentId()); + mDocument.performClick(mRemoteContext, component.getComponentId(), ""); return true; } diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java index c38a44ac30be..da4e8d621602 100644 --- a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java +++ b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java @@ -39,6 +39,7 @@ public class PlatformRemoteComposeTouchHelper extends ExploreByTouchHelper { private final RemoteComposeDocumentAccessibility mRemoteDocA11y; private final SemanticNodeApplier<AccessibilityNodeInfo> mApplier; + private final View mHost; public PlatformRemoteComposeTouchHelper( View host, @@ -47,6 +48,7 @@ public class PlatformRemoteComposeTouchHelper extends ExploreByTouchHelper { super(host); this.mRemoteDocA11y = remoteDocA11y; this.mApplier = applier; + this.mHost = host; } public static PlatformRemoteComposeTouchHelper forRemoteComposePlayer( @@ -150,6 +152,7 @@ public class PlatformRemoteComposeTouchHelper extends ExploreByTouchHelper { boolean performed = mRemoteDocA11y.performAction(component, action, arguments); if (performed) { + mHost.invalidate(); invalidateRoot(); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java index 3cc998576741..e5c20eb7ce18 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java @@ -19,8 +19,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.widget.remotecompose.core.operations.BitmapData; import com.android.internal.widget.remotecompose.core.operations.ComponentValue; import com.android.internal.widget.remotecompose.core.operations.DrawContent; +import com.android.internal.widget.remotecompose.core.operations.FloatConstant; import com.android.internal.widget.remotecompose.core.operations.FloatExpression; import com.android.internal.widget.remotecompose.core.operations.Header; import com.android.internal.widget.remotecompose.core.operations.IntegerExpression; @@ -29,6 +31,7 @@ import com.android.internal.widget.remotecompose.core.operations.RootContentBeha import com.android.internal.widget.remotecompose.core.operations.ShaderData; import com.android.internal.widget.remotecompose.core.operations.TextData; import com.android.internal.widget.remotecompose.core.operations.Theme; +import com.android.internal.widget.remotecompose.core.operations.Utils; import com.android.internal.widget.remotecompose.core.operations.layout.CanvasOperations; import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.Container; @@ -42,6 +45,8 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.IntMa import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import com.android.internal.widget.remotecompose.core.serialize.Serializable; +import com.android.internal.widget.remotecompose.core.types.IntegerConstant; +import com.android.internal.widget.remotecompose.core.types.LongConstant; import java.util.ArrayList; import java.util.HashMap; @@ -68,7 +73,9 @@ public class CoreDocument implements Serializable { // We also keep a more fine-grained BUILD number, exposed as // ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD - static final float BUILD = 0.4f; + static final float BUILD = 0.6f; + + private static final boolean UPDATE_VARIABLES_BEFORE_LAYOUT = false; @NonNull ArrayList<Operation> mOperations = new ArrayList<>(); @@ -442,6 +449,94 @@ public class CoreDocument implements Serializable { return mDocProperties.get(key); } + /** + * Apply a collection of operations to the document + * + * @param delta the delta to apply + */ + public void applyUpdate(CoreDocument delta) { + HashMap<Integer, TextData> txtData = new HashMap<Integer, TextData>(); + HashMap<Integer, BitmapData> imgData = new HashMap<Integer, BitmapData>(); + HashMap<Integer, FloatConstant> fltData = new HashMap<Integer, FloatConstant>(); + HashMap<Integer, IntegerConstant> intData = new HashMap<Integer, IntegerConstant>(); + HashMap<Integer, LongConstant> longData = new HashMap<Integer, LongConstant>(); + recursiveTreverse( + mOperations, + (op) -> { + if (op instanceof TextData) { + TextData d = (TextData) op; + txtData.put(d.mTextId, d); + } else if (op instanceof BitmapData) { + BitmapData d = (BitmapData) op; + imgData.put(d.mImageId, d); + } else if (op instanceof FloatConstant) { + FloatConstant d = (FloatConstant) op; + fltData.put(d.mId, d); + } else if (op instanceof IntegerConstant) { + IntegerConstant d = (IntegerConstant) op; + intData.put(d.mId, d); + } else if (op instanceof LongConstant) { + LongConstant d = (LongConstant) op; + longData.put(d.mId, d); + } + }); + + recursiveTreverse( + delta.mOperations, + (op) -> { + if (op instanceof TextData) { + TextData t = (TextData) op; + TextData txtInDoc = txtData.get(t.mTextId); + if (txtInDoc != null) { + txtInDoc.update(t); + Utils.log("update" + t.mText); + txtInDoc.markDirty(); + } + } else if (op instanceof BitmapData) { + BitmapData b = (BitmapData) op; + BitmapData imgInDoc = imgData.get(b.mImageId); + if (imgInDoc != null) { + imgInDoc.update(b); + imgInDoc.markDirty(); + } + } else if (op instanceof FloatConstant) { + FloatConstant f = (FloatConstant) op; + FloatConstant fltInDoc = fltData.get(f.mId); + if (fltInDoc != null) { + fltInDoc.update(f); + fltInDoc.markDirty(); + } + } else if (op instanceof IntegerConstant) { + IntegerConstant ic = (IntegerConstant) op; + IntegerConstant intInDoc = intData.get(ic.mId); + if (intInDoc != null) { + intInDoc.update(ic); + intInDoc.markDirty(); + } + } else if (op instanceof LongConstant) { + LongConstant lc = (LongConstant) op; + LongConstant longInDoc = longData.get(lc.mId); + if (longInDoc != null) { + longInDoc.update(lc); + longInDoc.markDirty(); + } + } + }); + } + + private interface Visitor { + void visit(Operation op); + } + + private void recursiveTreverse(ArrayList<Operation> mOperations, Visitor visitor) { + for (Operation op : mOperations) { + if (op instanceof Container) { + recursiveTreverse(((Component) op).mList, visitor); + } + visitor.visit(op); + } + } + // ============== Haptic support ================== public interface HapticEngine { /** @@ -799,7 +894,10 @@ public class CoreDocument implements Serializable { registerVariables(context, mOperations); context.mMode = RemoteContext.ContextMode.UNSET; - mFirstPaint = true; + + if (UPDATE_VARIABLES_BEFORE_LAYOUT) { + mFirstPaint = true; + } } /////////////////////////////////////////////////////////////////////////////////////////////// @@ -911,7 +1009,7 @@ public class CoreDocument implements Serializable { * * @param id the click area id */ - public void performClick(@NonNull RemoteContext context, int id) { + public void performClick(@NonNull RemoteContext context, int id, @NonNull String metadata) { for (ClickAreaRepresentation clickArea : mClickAreas) { if (clickArea.mId == id) { warnClickListeners(clickArea); @@ -920,7 +1018,7 @@ public class CoreDocument implements Serializable { } for (IdActionCallback listener : mIdActionListeners) { - listener.onAction(id, ""); + listener.onAction(id, metadata); } Component component = getComponent(id); @@ -1148,11 +1246,13 @@ public class CoreDocument implements Serializable { context.mRemoteComposeState = mRemoteComposeState; context.mRemoteComposeState.setContext(context); - // Update any dirty variables - if (mFirstPaint) { - mFirstPaint = false; - } else { - updateVariables(context, theme, mOperations); + if (UPDATE_VARIABLES_BEFORE_LAYOUT) { + // Update any dirty variables + if (mFirstPaint) { + mFirstPaint = false; + } else { + updateVariables(context, theme, mOperations); + } } // If we have a content sizing set, we are going to take the original document diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java index 20897ba77de4..add9d5bae552 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -104,17 +104,20 @@ import com.android.internal.widget.remotecompose.core.operations.layout.managers import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.FitBoxLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.managers.ImageLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BackgroundModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.CollapsiblePriorityModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DrawContentOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionMetadataOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostNamedActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.MarqueeModifierOperation; @@ -247,6 +250,7 @@ public class Operations { public static final int LAYOUT_CANVAS_CONTENT = 207; public static final int LAYOUT_TEXT = 208; public static final int LAYOUT_STATE = 217; + public static final int LAYOUT_IMAGE = 234; public static final int COMPONENT_START = 2; @@ -254,6 +258,7 @@ public class Operations { public static final int MODIFIER_HEIGHT = 67; public static final int MODIFIER_WIDTH_IN = 231; public static final int MODIFIER_HEIGHT_IN = 232; + public static final int MODIFIER_COLLAPSIBLE_PRIORITY = 235; public static final int MODIFIER_BACKGROUND = 55; public static final int MODIFIER_BORDER = 107; public static final int MODIFIER_PADDING = 58; @@ -278,6 +283,7 @@ public class Operations { public static final int MODIFIER_VISIBILITY = 211; public static final int HOST_ACTION = 209; + public static final int HOST_METADATA_ACTION = 216; public static final int HOST_NAMED_ACTION = 210; public static final int VALUE_INTEGER_CHANGE_ACTION = 212; @@ -364,6 +370,7 @@ public class Operations { map.put(MODIFIER_HEIGHT, HeightModifierOperation::read); map.put(MODIFIER_WIDTH_IN, WidthInModifierOperation::read); map.put(MODIFIER_HEIGHT_IN, HeightInModifierOperation::read); + map.put(MODIFIER_COLLAPSIBLE_PRIORITY, CollapsiblePriorityModifierOperation::read); map.put(MODIFIER_PADDING, PaddingModifierOperation::read); map.put(MODIFIER_BACKGROUND, BackgroundModifierOperation::read); map.put(MODIFIER_BORDER, BorderModifierOperation::read); @@ -385,6 +392,7 @@ public class Operations { map.put(CONTAINER_END, ContainerEnd::read); map.put(HOST_ACTION, HostActionOperation::read); + map.put(HOST_METADATA_ACTION, HostActionMetadataOperation::read); map.put(HOST_NAMED_ACTION, HostNamedActionOperation::read); map.put(VALUE_INTEGER_CHANGE_ACTION, ValueIntegerChangeActionOperation::read); map.put( @@ -407,7 +415,7 @@ public class Operations { map.put(LAYOUT_CANVAS, CanvasLayout::read); map.put(LAYOUT_CANVAS_CONTENT, CanvasContent::read); map.put(LAYOUT_TEXT, TextLayout::read); - + map.put(LAYOUT_IMAGE, ImageLayout::read); map.put(LAYOUT_STATE, StateLayout::read); map.put(DRAW_CONTENT, DrawContent::read); diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java index 161a5f17ef23..1f026687680f 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java @@ -102,6 +102,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.managers import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.FitBoxLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.managers.ImageLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout; @@ -2094,6 +2095,19 @@ public class RemoteComposeBuffer { } /** + * Add an imagelayout command + * + * @param componentId component id + * @param animationId animation id + * @param bitmapId bitmap id + */ + public void addImage( + int componentId, int animationId, int bitmapId, int scaleType, float alpha) { + mLastComponentId = getComponentId(componentId); + ImageLayout.apply(mBuffer, componentId, animationId, bitmapId, scaleType, alpha); + } + + /** * Add a row start tag * * @param componentId component id diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java index e37833f33fa5..b297a023d03b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java @@ -46,7 +46,7 @@ public abstract class RemoteContext { new CoreDocument(); // todo: is this a valid way to initialize? bbade@ public @NonNull RemoteComposeState mRemoteComposeState = new RemoteComposeState(); // todo, is this a valid use of RemoteComposeState -- bbade@ - + private long mDocLoadTime = System.currentTimeMillis(); @Nullable protected PaintContext mPaintContext = null; protected float mDensity = Float.NaN; @@ -83,6 +83,20 @@ public abstract class RemoteContext { } } + /** + * Get the time the document was loaded + * + * @return time in ms since the document was loaded + */ + public long getDocLoadTime() { + return mDocLoadTime; + } + + /** Set the time the document was loaded */ + public void setDocLoadTime() { + mDocLoadTime = System.currentTimeMillis(); + } + public boolean isAnimationEnabled() { return mAnimate; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java index 255d7a46e49e..90929e06ecd0 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java @@ -41,12 +41,12 @@ import java.util.List; public class BitmapData extends Operation implements SerializableToString, Serializable { private static final int OP_CODE = Operations.DATA_BITMAP; private static final String CLASS_NAME = "BitmapData"; - int mImageId; + public final int mImageId; int mImageWidth; int mImageHeight; short mType; short mEncoding; - @NonNull final byte[] mBitmap; + @NonNull byte[] mBitmap; /** The max size of width or height */ public static final int MAX_IMAGE_DIMENSION = 8000; @@ -91,6 +91,19 @@ public class BitmapData extends Operation implements SerializableToString, Seria } /** + * Update the bitmap data + * + * @param from the bitmap to copy + */ + public void update(BitmapData from) { + this.mImageWidth = from.mImageWidth; + this.mImageHeight = from.mImageHeight; + this.mBitmap = from.mBitmap; + this.mType = from.mType; + this.mEncoding = from.mEncoding; + } + + /** * The width of the image * * @return the width diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java index 233e246d3868..5096aaf03c9d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java @@ -42,6 +42,15 @@ public class FloatConstant extends Operation implements Serializable { this.mValue = value; } + /** + * Copy the value from another operation + * + * @param from value to copy from + */ + public void update(FloatConstant from) { + mValue = from.mValue; + } + @Override public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mValue); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java index 67773d1d6187..d8ef4cbba4d6 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java @@ -37,7 +37,7 @@ public class TextData extends Operation implements SerializableToString, Seriali private static final int OP_CODE = Operations.DATA_TEXT; private static final String CLASS_NAME = "TextData"; public final int mTextId; - @NonNull public final String mText; + @NonNull public String mText; public static final int MAX_STRING_SIZE = 4000; public TextData(int textId, @NonNull String text) { @@ -45,6 +45,15 @@ public class TextData extends Operation implements SerializableToString, Seriali this.mText = text; } + /** + * Copy the data from another text data + * + * @param from source to copy from + */ + public void update(TextData from) { + mText = from.mText; + } + @Override public void write(@NonNull WireBuffer buffer) { apply(buffer, mTextId, mText); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java index e9cc26f58c9b..dee79a45de3d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java @@ -85,6 +85,9 @@ public class TimeAttribute extends PaintOperation { /** the year */ public static final short TIME_YEAR = 12; + /** (value - doc_load_time) * 1E-3 */ + public static final short TIME_FROM_LOAD_SEC = 14; + /** * creates a new operation * @@ -226,6 +229,7 @@ public class TimeAttribute extends PaintOperation { int val = mType & 255; int flags = mType >> 8; RemoteContext ctx = context.getContext(); + long load_time = ctx.getDocLoadTime(); LongConstant longConstant = (LongConstant) ctx.getObject(mTimeId); long value = longConstant.getValue(); long delta = 0; @@ -292,6 +296,9 @@ public class TimeAttribute extends PaintOperation { case TIME_YEAR: ctx.loadFloat(mId, time.getYear()); break; + case TIME_FROM_LOAD_SEC: + ctx.loadFloat(mId, (value - load_time) * 1E-3f); + break; } } @@ -334,6 +341,8 @@ public class TimeAttribute extends PaintOperation { return "TIME_DAY_OF_WEEK"; case TIME_YEAR: return "TIME_YEAR"; + case TIME_FROM_LOAD_SEC: + return "TIME_FROM_LOAD_SEC"; default: return "INVALID_TIME_TYPE"; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java index 76bb96d1b61a..2a809c6f0a2a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java @@ -27,6 +27,7 @@ import com.android.internal.widget.remotecompose.core.SerializableToString; import com.android.internal.widget.remotecompose.core.TouchListener; import com.android.internal.widget.remotecompose.core.VariableSupport; import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.operations.BitmapData; import com.android.internal.widget.remotecompose.core.operations.ComponentValue; import com.android.internal.widget.remotecompose.core.operations.TextData; import com.android.internal.widget.remotecompose.core.operations.TouchExpression; @@ -823,15 +824,27 @@ public class Component extends PaintOperation * * @param value a 2 dimension float array that will receive the horizontal and vertical position * of the component. + * @param forSelf whether the location is for this container or a child, relevant for scrollable + * items. */ - public void getLocationInWindow(@NonNull float[] value) { + public void getLocationInWindow(@NonNull float[] value, boolean forSelf) { value[0] += mX; value[1] += mY; if (mParent != null) { - mParent.getLocationInWindow(value); + mParent.getLocationInWindow(value, false); } } + /** + * Returns the location of the component relative to the root component + * + * @param value a 2 dimension float array that will receive the horizontal and vertical position + * of the component. + */ + public void getLocationInWindow(@NonNull float[] value) { + getLocationInWindow(value, true); + } + @NonNull @Override public String toString() { @@ -1131,14 +1144,14 @@ public class Component extends PaintOperation } /** - * Extract child TextData elements + * Extract child Data elements * - * @param data an ArrayList that will be populated with the TextData elements (if any) + * @param data an ArrayList that will be populated with the Data elements (if any) */ - public void getData(@NonNull ArrayList<TextData> data) { + public void getData(@NonNull ArrayList<Operation> data) { for (Operation op : mList) { - if (op instanceof TextData) { - data.add((TextData) op); + if (op instanceof TextData || op instanceof BitmapData) { + data.add(op); } } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java index e57438662012..bc099e3a3b9d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java @@ -30,7 +30,6 @@ import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; import com.android.internal.widget.remotecompose.core.operations.MatrixSave; import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate; import com.android.internal.widget.remotecompose.core.operations.PaintData; -import com.android.internal.widget.remotecompose.core.operations.TextData; import com.android.internal.widget.remotecompose.core.operations.TouchExpression; import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers; @@ -138,7 +137,7 @@ public class LayoutComponent extends Component { @Override public void inflate() { - ArrayList<TextData> data = new ArrayList<>(); + ArrayList<Operation> data = new ArrayList<>(); ArrayList<Operation> supportedOperations = new ArrayList<>(); for (Operation op : mList) { @@ -186,8 +185,6 @@ public class LayoutComponent extends Component { ((ScrollModifierOperation) op).inflate(this); } mComponentModifiers.add((ModifierOperation) op); - } else if (op instanceof TextData) { - data.add((TextData) op); } else if (op instanceof TouchExpression || (op instanceof PaintData) || (op instanceof FloatExpression)) { @@ -292,11 +289,11 @@ public class LayoutComponent extends Component { } @Override - public void getLocationInWindow(@NonNull float[] value) { + public void getLocationInWindow(@NonNull float[] value, boolean forSelf) { value[0] += mX + mPaddingLeft; value[1] += mY + mPaddingTop; if (mParent != null) { - mParent.getLocationInWindow(value); + mParent.getLocationInWindow(value, false); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java index b0089525af5a..00ec60533087 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java @@ -24,10 +24,13 @@ import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.operations.layout.Component; +import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent; import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.CollapsiblePriorityModifierOperation; +import java.util.ArrayList; import java.util.List; public class CollapsibleColumnLayout extends ColumnLayout { @@ -153,7 +156,7 @@ public class CollapsibleColumnLayout extends ColumnLayout { } @Override - protected boolean hasVerticalIntrinsicDimension() { + public boolean hasVerticalIntrinsicDimension() { return true; } @@ -166,25 +169,72 @@ public class CollapsibleColumnLayout extends ColumnLayout { boolean verticalWrap, @NonNull MeasurePass measure, @NonNull Size size) { + computeVisibleChildren( + context, maxWidth, maxHeight, horizontalWrap, verticalWrap, measure, size); + } + + @Override + public void computeSize( + @NonNull PaintContext context, + float minWidth, + float maxWidth, + float minHeight, + float maxHeight, + @NonNull MeasurePass measure) { + computeVisibleChildren(context, maxWidth, maxHeight, false, false, measure, null); + } + + @Override + public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) { + // if needed, take care of weight calculations + super.internalLayoutMeasure(context, measure); + // Check again for visibility + ComponentMeasure m = measure.get(this); + computeVisibleChildren(context, m.getW(), m.getH(), false, false, measure, null); + } + + private void computeVisibleChildren( + @NonNull PaintContext context, + float maxWidth, + float maxHeight, + boolean horizontalWrap, + boolean verticalWrap, + @NonNull MeasurePass measure, + @Nullable Size size) { int visibleChildren = 0; ComponentMeasure self = measure.get(this); self.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE); float currentMaxHeight = maxHeight; + boolean hasPriorities = false; for (Component c : mChildrenComponents) { - if (c instanceof CollapsibleColumnLayout) { - c.measure(context, 0f, maxWidth, 0f, currentMaxHeight, measure); - } else { - c.measure(context, 0f, maxWidth, 0f, Float.MAX_VALUE, measure); + if (!measure.contains(c.getComponentId())) { + // No need to remeasure here if already done + if (c instanceof CollapsibleColumnLayout) { + c.measure(context, 0f, maxWidth, 0f, currentMaxHeight, measure); + } else { + c.measure(context, 0f, maxWidth, 0f, Float.MAX_VALUE, measure); + } } + ComponentMeasure m = measure.get(c); if (!m.isGone()) { - size.setWidth(Math.max(size.getWidth(), m.getW())); - size.setHeight(size.getHeight() + m.getH()); + if (size != null) { + size.setWidth(Math.max(size.getWidth(), m.getW())); + size.setHeight(size.getHeight() + m.getH()); + } visibleChildren++; currentMaxHeight -= m.getH(); } + if (c instanceof LayoutComponent) { + LayoutComponent lc = (LayoutComponent) c; + CollapsiblePriorityModifierOperation priority = + lc.selfOrModifier(CollapsiblePriorityModifierOperation.class); + if (priority != null) { + hasPriorities = true; + } + } } - if (!mChildrenComponents.isEmpty()) { + if (!mChildrenComponents.isEmpty() && size != null) { size.setHeight(size.getHeight() + (mSpacedBy * (visibleChildren - 1))); } @@ -192,7 +242,14 @@ public class CollapsibleColumnLayout extends ColumnLayout { float childrenHeight = 0f; boolean overflow = false; - for (Component child : mChildrenComponents) { + ArrayList<Component> children = mChildrenComponents; + if (hasPriorities) { + // TODO: We need to cache this. + children = + CollapsiblePriority.sortWithPriorities( + mChildrenComponents, CollapsiblePriority.VERTICAL); + } + for (Component child : children) { ComponentMeasure childMeasure = measure.get(child); if (overflow || childMeasure.isGone()) { childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE); @@ -209,10 +266,10 @@ public class CollapsibleColumnLayout extends ColumnLayout { visibleChildren++; } } - if (verticalWrap) { + if (verticalWrap && size != null) { size.setHeight(Math.min(maxHeight, childrenHeight)); } - if (visibleChildren == 0 || size.getHeight() <= 0f) { + if (visibleChildren == 0 || (size != null && size.getHeight() <= 0f)) { self.addVisibilityOverride(Visibility.OVERRIDE_GONE); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsiblePriority.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsiblePriority.java new file mode 100644 index 000000000000..46cd45ecba8a --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsiblePriority.java @@ -0,0 +1,65 @@ +/* + * 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.internal.widget.remotecompose.core.operations.layout.managers; + +import com.android.internal.widget.remotecompose.core.operations.layout.Component; +import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.CollapsiblePriorityModifierOperation; + +import java.util.ArrayList; + +/** Utility class to manage collapsible priorities on components */ +public class CollapsiblePriority { + + public static final int HORIZONTAL = 0; + public static final int VERTICAL = 1; + + /** + * Returns the priority of a child component + * + * @param c the child component + * @return priority value, or 0f if not found + */ + static float getPriority(Component c, int orientation) { + if (c instanceof LayoutComponent) { + LayoutComponent lc = (LayoutComponent) c; + CollapsiblePriorityModifierOperation priority = + lc.selfOrModifier(CollapsiblePriorityModifierOperation.class); + if (priority != null && priority.getOrientation() == orientation) { + return priority.getPriority(); + } + } + return Float.MAX_VALUE; + } + + /** + * Allocate and return a sorted array of components by their priorities + * + * @param components the children components + * @return list of components sorted by their priority in decreasing order + */ + static ArrayList<Component> sortWithPriorities( + ArrayList<Component> components, int orientation) { + ArrayList<Component> sorted = new ArrayList<>(components); + sorted.sort( + (t1, t2) -> { + float p1 = getPriority(t1, orientation); + float p2 = getPriority(t2, orientation); + return (int) (p2 - p1); + }); + return sorted; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java index 05f332960c16..e3632f9888ec 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java @@ -24,10 +24,13 @@ import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.operations.layout.Component; +import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent; import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.CollapsiblePriorityModifierOperation; +import java.util.ArrayList; import java.util.List; public class CollapsibleRowLayout extends RowLayout { @@ -135,8 +138,12 @@ public class CollapsibleRowLayout extends RowLayout { } @Override - protected boolean hasHorizontalIntrinsicDimension() { - return true; + public float minIntrinsicHeight(@NonNull RemoteContext context) { + float height = computeModifierDefinedHeight(context); + if (!mChildrenComponents.isEmpty()) { + height += mChildrenComponents.get(0).minIntrinsicHeight(context); + } + return height; } @Override @@ -149,12 +156,8 @@ public class CollapsibleRowLayout extends RowLayout { } @Override - public float minIntrinsicHeight(@NonNull RemoteContext context) { - float height = computeModifierDefinedHeight(context); - if (!mChildrenComponents.isEmpty()) { - height += mChildrenComponents.get(0).minIntrinsicHeight(context); - } - return height; + public boolean hasHorizontalIntrinsicDimension() { + return true; } @Override @@ -166,45 +169,107 @@ public class CollapsibleRowLayout extends RowLayout { boolean verticalWrap, @NonNull MeasurePass measure, @NonNull Size size) { - super.computeWrapSize( - context, Float.MAX_VALUE, maxHeight, horizontalWrap, verticalWrap, measure, size); + computeVisibleChildren( + context, maxWidth, maxHeight, horizontalWrap, verticalWrap, measure, size); } @Override - public boolean applyVisibility( - float selfWidth, float selfHeight, @NonNull MeasurePass measure) { - float childrenWidth = 0f; - float childrenHeight = 0f; - boolean changedVisibility = false; + public void computeSize( + @NonNull PaintContext context, + float minWidth, + float maxWidth, + float minHeight, + float maxHeight, + @NonNull MeasurePass measure) { + computeVisibleChildren(context, maxWidth, maxHeight, false, false, measure, null); + } + + @Override + public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) { + // if needed, take care of weight calculations + super.internalLayoutMeasure(context, measure); + // Check again for visibility + ComponentMeasure m = measure.get(this); + computeVisibleChildren(context, m.getW(), m.getH(), false, false, measure, null); + } + + private void computeVisibleChildren( + @NonNull PaintContext context, + float maxWidth, + float maxHeight, + boolean horizontalWrap, + boolean verticalWrap, + @NonNull MeasurePass measure, + @Nullable Size size) { int visibleChildren = 0; ComponentMeasure self = measure.get(this); - self.clearVisibilityOverride(); - if (selfWidth <= 0 || selfHeight <= 0) { - self.addVisibilityOverride(Visibility.OVERRIDE_GONE); - return true; + self.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE); + float currentMaxWidth = maxWidth; + boolean hasPriorities = false; + for (Component c : mChildrenComponents) { + if (!measure.contains(c.getComponentId())) { + // No need to remeasure here if already done + if (c instanceof CollapsibleRowLayout) { + c.measure(context, 0f, currentMaxWidth, 0f, maxHeight, measure); + } else { + c.measure(context, 0f, Float.MAX_VALUE, 0f, maxHeight, measure); + } + } + ComponentMeasure m = measure.get(c); + if (!m.isGone()) { + if (size != null) { + size.setHeight(Math.max(size.getHeight(), m.getH())); + size.setWidth(size.getWidth() + m.getW()); + } + visibleChildren++; + currentMaxWidth -= m.getW(); + } + if (c instanceof LayoutComponent) { + LayoutComponent lc = (LayoutComponent) c; + CollapsiblePriorityModifierOperation priority = + lc.selfOrModifier(CollapsiblePriorityModifierOperation.class); + if (priority != null) { + hasPriorities = true; + } + } + } + if (!mChildrenComponents.isEmpty() && size != null) { + size.setWidth(size.getWidth() + (mSpacedBy * (visibleChildren - 1))); } - for (Component child : mChildrenComponents) { + + float childrenWidth = 0f; + float childrenHeight = 0f; + + boolean overflow = false; + ArrayList<Component> children = mChildrenComponents; + if (hasPriorities) { + // TODO: We need to cache this. + children = + CollapsiblePriority.sortWithPriorities( + mChildrenComponents, CollapsiblePriority.HORIZONTAL); + } + for (Component child : children) { ComponentMeasure childMeasure = measure.get(child); - int visibility = childMeasure.getVisibility(); - childMeasure.clearVisibilityOverride(); - if (!childMeasure.isVisible()) { + if (overflow || childMeasure.isGone()) { + childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE); continue; } - if (childrenWidth + childMeasure.getW() > selfWidth - && childrenHeight + childMeasure.getH() > selfHeight) { + float childWidth = childMeasure.getW(); + boolean childDoesNotFits = childrenWidth + childWidth > maxWidth; + if (childDoesNotFits) { childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE); - if (visibility != childMeasure.getVisibility()) { - changedVisibility = true; - } + overflow = true; } else { - childrenWidth += childMeasure.getW(); + childrenWidth += childWidth; childrenHeight = Math.max(childrenHeight, childMeasure.getH()); visibleChildren++; } } - if (visibleChildren == 0) { + if (horizontalWrap && size != null) { + size.setWidth(Math.min(maxWidth, childrenWidth)); + } + if (visibleChildren == 0 || (size != null && size.getWidth() <= 0f)) { self.addVisibilityOverride(Visibility.OVERRIDE_GONE); } - return changedVisibility; } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java index cda90c2d3b0b..9566242ccbc5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java @@ -33,6 +33,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure. import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog; import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; @@ -372,6 +373,17 @@ public class ColumnLayout extends LayoutManager { DebugLog.e(); } + @Override + public void getLocationInWindow(@NonNull float[] value, boolean forSelf) { + super.getLocationInWindow(value, forSelf); + + if (!forSelf && mVerticalScrollDelegate instanceof ScrollModifierOperation) { + ScrollModifierOperation smo = (ScrollModifierOperation) mVerticalScrollDelegate; + + value[1] += smo.getScrollY(); + } + } + /** * The name of the class * diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ImageLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ImageLayout.java new file mode 100644 index 000000000000..a4ed0c1319d4 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ImageLayout.java @@ -0,0 +1,314 @@ +/* + * 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.internal.widget.remotecompose.core.operations.layout.managers; + +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.VariableSupport; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.operations.BitmapData; +import com.android.internal.widget.remotecompose.core.operations.layout.Component; +import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; +import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; +import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; +import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; +import com.android.internal.widget.remotecompose.core.operations.utilities.ImageScaling; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; + +import java.util.List; + +public class ImageLayout extends LayoutManager implements VariableSupport { + + private static final boolean DEBUG = false; + private int mBitmapId = -1; + private int mScaleType; + private float mAlpha = 1f; + + @NonNull ImageScaling mScaling = new ImageScaling(); + @NonNull PaintBundle mPaint = new PaintBundle(); + + @Override + public void registerListening(@NonNull RemoteContext context) { + if (mBitmapId != -1) { + context.listensTo(mBitmapId, this); + } + } + + public ImageLayout( + @Nullable Component parent, + int componentId, + int animationId, + int bitmapId, + float x, + float y, + float width, + float height, + int scaleType, + float alpha) { + super(parent, componentId, animationId, x, y, width, height); + mBitmapId = bitmapId; + mScaleType = scaleType & 0xFF; + mAlpha = alpha; + } + + public ImageLayout( + @Nullable Component parent, + int componentId, + int animationId, + int bitmapId, + int scaleType, + float alpha) { + this(parent, componentId, animationId, bitmapId, 0, 0, 0, 0, scaleType, alpha); + } + + @Override + public void computeWrapSize( + @NonNull PaintContext context, + float maxWidth, + float maxHeight, + boolean horizontalWrap, + boolean verticalWrap, + @NonNull MeasurePass measure, + @NonNull Size size) { + + BitmapData bitmapData = (BitmapData) context.getContext().getObject(mBitmapId); + if (bitmapData != null) { + size.setWidth(bitmapData.getWidth()); + size.setHeight(bitmapData.getHeight()); + } + } + + @Override + public void computeSize( + @NonNull PaintContext context, + float minWidth, + float maxWidth, + float minHeight, + float maxHeight, + @NonNull MeasurePass measure) { + float modifiersWidth = computeModifierDefinedWidth(context.getContext()); + float modifiersHeight = computeModifierDefinedHeight(context.getContext()); + ComponentMeasure m = measure.get(this); + m.setW(modifiersWidth); + m.setH(modifiersHeight); + } + + @Override + public void paintingComponent(@NonNull PaintContext context) { + context.save(); + context.translate(mX, mY); + mComponentModifiers.paint(context); + float tx = mPaddingLeft; + float ty = mPaddingTop; + context.translate(tx, ty); + float w = mWidth - mPaddingLeft - mPaddingRight; + float h = mHeight - mPaddingTop - mPaddingBottom; + context.clipRect(0f, 0f, w, h); + + BitmapData bitmapData = (BitmapData) context.getContext().getObject(mBitmapId); + if (bitmapData != null) { + mScaling.setup( + 0f, + 0f, + bitmapData.getWidth(), + bitmapData.getHeight(), + 0f, + 0f, + w, + h, + mScaleType, + 1f); + + context.savePaint(); + if (mAlpha == 1f) { + context.drawBitmap( + mBitmapId, + (int) 0f, + (int) 0f, + (int) bitmapData.getWidth(), + (int) bitmapData.getHeight(), + (int) mScaling.mFinalDstLeft, + (int) mScaling.mFinalDstTop, + (int) mScaling.mFinalDstRight, + (int) mScaling.mFinalDstBottom, + -1); + } else { + context.savePaint(); + mPaint.reset(); + mPaint.setColor(0f, 0f, 0f, mAlpha); + context.applyPaint(mPaint); + context.drawBitmap( + mBitmapId, + (int) 0f, + (int) 0f, + (int) bitmapData.getWidth(), + (int) bitmapData.getHeight(), + (int) mScaling.mFinalDstLeft, + (int) mScaling.mFinalDstTop, + (int) mScaling.mFinalDstRight, + (int) mScaling.mFinalDstBottom, + -1); + context.restorePaint(); + } + context.restorePaint(); + } + + // debugBox(this, context); + context.translate(-tx, -ty); + context.restore(); + } + + @NonNull + @Override + public String toString() { + return "IMAGE_LAYOUT [" + + mComponentId + + ":" + + mAnimationId + + "] (" + + mX + + ", " + + mY + + " - " + + mWidth + + " x " + + mHeight + + ") " + + mVisibility; + } + + @NonNull + @Override + protected String getSerializedName() { + return "IMAGE_LAYOUT"; + } + + @Override + public void serializeToString(int indent, @NonNull StringSerializer serializer) { + serializer.append( + indent, + getSerializedName() + + " [" + + mComponentId + + ":" + + mAnimationId + + "] = " + + "[" + + mX + + ", " + + mY + + ", " + + mWidth + + ", " + + mHeight + + "] " + + mVisibility + + " (" + + mBitmapId + + "\")"); + } + + /** + * The name of the class + * + * @return the name + */ + @NonNull + public static String name() { + return "ImageLayout"; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return Operations.LAYOUT_IMAGE; + } + + /** + * Write the operation to the buffer + * + * @param buffer + * @param componentId + * @param animationId + * @param bitmapId + * @param scaleType + * @param alpha + */ + public static void apply( + @NonNull WireBuffer buffer, + int componentId, + int animationId, + int bitmapId, + int scaleType, + float alpha) { + buffer.start(id()); + buffer.writeInt(componentId); + buffer.writeInt(animationId); + buffer.writeInt(bitmapId); + buffer.writeInt(scaleType); + buffer.writeFloat(alpha); + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int componentId = buffer.readInt(); + int animationId = buffer.readInt(); + int bitmapId = buffer.readInt(); + int scaleType = buffer.readInt(); + float alpha = buffer.readFloat(); + operations.add(new ImageLayout(null, componentId, animationId, bitmapId, scaleType, alpha)); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Layout Operations", id(), name()) + .description("Image layout implementation.\n\n") + .field(INT, "COMPONENT_ID", "unique id for this component") + .field( + INT, + "ANIMATION_ID", + "id used to match components," + " for animation purposes") + .field(INT, "BITMAP_ID", "bitmap id") + .field(INT, "SCALE_TYPE", "scale type") + .field(FLOAT, "ALPHA", "alpha"); + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, mComponentId, mAnimationId, mBitmapId, mScaleType, mAlpha); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java index 5b66b95cf1dd..eb10ead34781 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java @@ -226,9 +226,17 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl measure, mCachedWrapSize); float w = mCachedWrapSize.getWidth(); - computeSize(context, 0f, w, 0, measuredHeight, measure); if (hasHorizontalScroll()) { + computeSize(context, 0f, w, 0, measuredHeight, measure); mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w); + } else { + computeSize( + context, + 0f, + Math.min(measuredWidth, insetMaxWidth), + 0, + Math.min(measuredHeight, insetMaxHeight), + measure); } } else if (hasVerticalIntrinsicDimension()) { mCachedWrapSize.setWidth(0f); @@ -236,9 +244,17 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl computeWrapSize( context, maxWidth, Float.MAX_VALUE, false, false, measure, mCachedWrapSize); float h = mCachedWrapSize.getHeight(); - computeSize(context, 0f, measuredWidth, 0, h, measure); if (hasVerticalScroll()) { + computeSize(context, 0f, measuredWidth, 0, h, measure); mComponentModifiers.setVerticalScrollDimension(measuredHeight, h); + } else { + computeSize( + context, + 0f, + Math.min(measuredWidth, insetMaxWidth), + 0, + Math.min(measuredHeight, insetMaxHeight), + measure); } } else { float maxChildWidth = measuredWidth - mPaddingLeft - mPaddingRight; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java index d5d2e03c3f2a..15b54a3ce994 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java @@ -32,6 +32,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.LayoutCo import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog; import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; @@ -386,6 +387,17 @@ public class RowLayout extends LayoutManager { DebugLog.e(); } + @Override + public void getLocationInWindow(@NonNull float[] value, boolean forSelf) { + super.getLocationInWindow(value, forSelf); + + if (!forSelf && mHorizontalScrollDelegate instanceof ScrollModifierOperation) { + ScrollModifierOperation smo = (ScrollModifierOperation) mHorizontalScrollDelegate; + + value[0] += smo.getScrollX(); + } + } + /** * The name of the class * diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java index d383ee9e4fc9..120c740eccda 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java @@ -77,6 +77,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access private final Size mCachedSize = new Size(0f, 0f); @Nullable private String mCachedString = ""; + @Nullable private String mNewString; Platform.ComputedTextLayout mComputedTextLayout; @@ -99,7 +100,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access if (cachedString != null && cachedString.equalsIgnoreCase(mCachedString)) { return; } - mCachedString = cachedString; + mNewString = cachedString; if (mType == -1) { if (mFontFamilyId != -1) { String fontFamily = context.getText(mFontFamilyId); @@ -119,8 +120,6 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access mType = 0; } } - mTextW = -1; - mTextH = -1; if (mHorizontalScrollDelegate != null) { mHorizontalScrollDelegate.reset(); @@ -351,6 +350,9 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access mPaint.setColor(mColor); context.replacePaint(mPaint); float[] bounds = new float[4]; + if (mNewString != null && !mNewString.equals(mCachedString)) { + mCachedString = mNewString; + } if (mCachedString == null) { return; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java index 656a3c0fca68..b3d76b765143 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java @@ -250,7 +250,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation { context.savePaint(); paint.reset(); paint.setColor(mR, mG, mB, mA); - paint.setStrokeWidth(mBorderWidth); + paint.setStrokeWidth(mBorderWidth * context.getContext().getDensity()); paint.setStyle(PaintBundle.STYLE_STROKE); context.replacePaint(paint); if (mShapeType == ShapeType.RECTANGLE) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/CollapsiblePriorityModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/CollapsiblePriorityModifierOperation.java new file mode 100644 index 000000000000..b1f2d2d35b93 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/CollapsiblePriorityModifierOperation.java @@ -0,0 +1,140 @@ +/* + * 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.internal.widget.remotecompose.core.operations.layout.modifiers; + +import android.annotation.NonNull; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; +import com.android.internal.widget.remotecompose.core.serialize.Serializable; +import com.android.internal.widget.remotecompose.core.serialize.SerializeTags; + +import java.util.List; + +/** Set an optional priority on a component within a collapsible layout */ +public class CollapsiblePriorityModifierOperation extends Operation + implements ModifierOperation, Serializable { + private static final int OP_CODE = Operations.MODIFIER_COLLAPSIBLE_PRIORITY; + public static final String CLASS_NAME = "CollapsiblePriorityModifierOperation"; + + private float mPriority; + private int mOrientation; + + public CollapsiblePriorityModifierOperation(int orientation, float priority) { + mOrientation = orientation; + mPriority = priority; + } + + public float getPriority() { + return mPriority; + } + + public int getOrientation() { + return mOrientation; + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, mOrientation, mPriority); + } + + @Override + public void apply(@NonNull RemoteContext context) { + // nothing + } + + @NonNull + @Override + public String deepToString(@NonNull String indent) { + return ""; + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int orientation = buffer.readInt(); + float priority = buffer.readFloat(); + operations.add(new CollapsiblePriorityModifierOperation(orientation, priority)); + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return OP_CODE; + } + + /** + * The name of the class + * + * @return the name + */ + @NonNull + public static String name() { + return CLASS_NAME; + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Layout Operations", OP_CODE, "CollapsiblePriorityModifier") + .description("Add additional priority to children of Collapsible layouts") + .field(DocumentedOperation.INT, "orientation", "Horizontal(0) or Vertical (1)") + .field(DocumentedOperation.FLOAT, "priority", "The associated priority"); + } + + /** + * Writes out the CollapsiblePriorityModifier to the buffer + * + * @param buffer buffer to write to + * @param priority priority value + * @param orientation orientation (HORIZONTAL or VERTICAL) + */ + public static void apply(@NonNull WireBuffer buffer, int orientation, float priority) { + buffer.start(OP_CODE); + buffer.writeInt(orientation); + buffer.writeFloat(priority); + } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addTags(SerializeTags.MODIFIER) + .addType(name()) + .add("orientation", mOrientation) + .add("priority", mPriority); + } + + @Override + public void serializeToString(int indent, @NonNull StringSerializer serializer) { + serializer.append(indent, "PRIORITY = [" + getPriority() + "] (" + mOrientation + ")"); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionMetadataOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionMetadataOperation.java new file mode 100644 index 000000000000..2170efd68a64 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionMetadataOperation.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.layout.modifiers; + +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; + +import android.annotation.NonNull; + +import com.android.internal.widget.remotecompose.core.CoreDocument; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.SerializableToString; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.operations.layout.ActionOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.Component; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; +import com.android.internal.widget.remotecompose.core.serialize.Serializable; +import com.android.internal.widget.remotecompose.core.serialize.SerializeTags; + +import java.util.List; + +/** Capture a host action information. This can be triggered on eg. a click. */ +public class HostActionMetadataOperation extends Operation + implements ActionOperation, SerializableToString, Serializable { + private static final int OP_CODE = Operations.HOST_METADATA_ACTION; + + int mActionId = -1; + int mMetadataId = -1; + + public HostActionMetadataOperation(int id, int metadataId) { + mActionId = id; + mMetadataId = metadataId; + } + + @NonNull + @Override + public String toString() { + return "HostMetadataActionOperation(" + mActionId + ":" + mMetadataId + ")"; + } + + public int getActionId() { + return mActionId; + } + + /** + * Returns the serialized name for this operation + * + * @return the serialized name + */ + @NonNull + public String serializedName() { + return "HOST_METADATA_ACTION"; + } + + @Override + public void serializeToString(int indent, @NonNull StringSerializer serializer) { + serializer.append(indent, serializedName() + " = " + mActionId + ", " + mMetadataId); + } + + @Override + public void apply(@NonNull RemoteContext context) {} + + @NonNull + @Override + public String deepToString(@NonNull String indent) { + return (indent != null ? indent : "") + toString(); + } + + @Override + public void write(@NonNull WireBuffer buffer) {} + + @Override + public void runAction( + @NonNull RemoteContext context, + @NonNull CoreDocument document, + @NonNull Component component, + float x, + float y) { + String metadata = context.getText(mMetadataId); + if (metadata == null) { + metadata = ""; + } + context.runAction(mActionId, metadata); + } + + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param actionId the action id + */ + public static void apply(@NonNull WireBuffer buffer, int actionId, int metadataId) { + buffer.start(OP_CODE); + buffer.writeInt(actionId); + buffer.writeInt(metadataId); + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int actionId = buffer.readInt(); + int metadataId = buffer.readInt(); + operations.add(new HostActionMetadataOperation(actionId, metadataId)); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Layout Operations", OP_CODE, "HostAction") + .description("Host action. This operation represents a host action") + .field(INT, "ACTION_ID", "Host Action ID") + .field(INT, "METADATA", "Host Action Text Metadata ID"); + } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addTags(SerializeTags.MODIFIER) + .addType("HostActionOperation") + .add("id", mActionId) + .add("metadata", mMetadataId); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java index 3e1f32de66e4..42692f95fcda 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java @@ -430,9 +430,35 @@ public class ScrollModifierOperation extends ListActionsOperation } @Override - public boolean showOnScreen(RemoteContext context, int childId) { - // TODO correct this when we trust the bounds in parent - return scrollByOffset(context, -1000) != 0; + public boolean scrollDirection(RemoteContext context, ScrollDirection direction) { + float offset = mHostDimension * 0.7f; + + if (direction == ScrollDirection.FORWARD + || direction == ScrollDirection.DOWN + || direction == ScrollDirection.RIGHT) { + offset *= -1; + } + + return scrollByOffset(context, (int) offset) != 0; + } + + @Override + public boolean showOnScreen(RemoteContext context, Component child) { + float[] locationInWindow = new float[2]; + child.getLocationInWindow(locationInWindow); + + int offset = 0; + if (handlesVerticalScroll()) { + offset = (int) -locationInWindow[1]; + } else { + offset = (int) -locationInWindow[0]; + } + + if (offset == 0) { + return true; + } else { + return scrollByOffset(context, offset) != 0; + } } @Nullable diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java index a95a175d0edd..120c7ac9efbf 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java @@ -35,7 +35,10 @@ public class StringUtils { @NonNull public static String floatToString( float value, int beforeDecimalPoint, int afterDecimalPoint, char pre, char post) { - + boolean isNeg = value < 0; + if (isNeg) { + value = -value; + } int integerPart = (int) value; float fractionalPart = value % 1; @@ -54,14 +57,13 @@ public class StringUtils { integerPartString = integerPartString.substring(iLen - beforeDecimalPoint); } if (afterDecimalPoint == 0) { - return integerPartString; + return ((isNeg) ? "-" : "") + integerPartString; } // Convert fractional part to string and pad with zeros for (int i = 0; i < afterDecimalPoint; i++) { fractionalPart *= 10; } - fractionalPart = Math.round(fractionalPart); for (int i = 0; i < afterDecimalPoint; i++) { @@ -87,6 +89,6 @@ public class StringUtils { fact = fact + new String(c); } - return integerPartString + "." + fact; + return ((isNeg) ? "-" : "") + integerPartString + "." + fact; } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/ScrollableComponent.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/ScrollableComponent.java index 3d1bd12357c9..1610e6332c1c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/semantics/ScrollableComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/ScrollableComponent.java @@ -18,6 +18,7 @@ package com.android.internal.widget.remotecompose.core.semantics; import android.annotation.Nullable; import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.operations.layout.Component; /** * Interface for components that support scrolling. @@ -48,13 +49,23 @@ public interface ScrollableComponent extends AccessibilitySemantics { } /** + * Scrolls the content in the specified direction. + * + * @param direction the direction to scroll + * @return whether a scroll was possible + */ + default boolean scrollDirection(RemoteContext context, ScrollDirection direction) { + return false; + } + + /** * Show a child with the given ID on the screen, typically scrolling so it's fully on screen. * - * @param childId The ID of the child to check for visibility. + * @param child The child (including nested) to check for visibility. * @return {@code true} if the child with the given ID could be shown on screen; {@code false} * otherwise. */ - default boolean showOnScreen(RemoteContext context, int childId) { + default boolean showOnScreen(RemoteContext context, Component child) { return false; } @@ -108,4 +119,13 @@ public interface ScrollableComponent extends AccessibilitySemantics { return mCanScrollBackwards; } } + + enum ScrollDirection { + FORWARD, + BACKWARD, + UP, + DOWN, + LEFT, + RIGHT, + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java index bdc765968387..25dcb67fe9e2 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java @@ -34,14 +34,23 @@ import java.util.List; public class IntegerConstant extends Operation implements Serializable { private static final String CLASS_NAME = "IntegerConstant"; - private final int mValue; - private final int mId; + private int mValue; + public final int mId; IntegerConstant(int id, int value) { mId = id; mValue = value; } + /** + * Updates the value of the integer constant + * + * @param ic the integer constant to copy + */ + public void update(IntegerConstant ic) { + mValue = ic.mValue; + } + @Override public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mValue); diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java index d071e0a21d22..ab0f7352182a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java @@ -36,7 +36,7 @@ public class LongConstant extends Operation implements Serializable { private static final int OP_CODE = Operations.DATA_LONG; private long mValue; - private final int mId; + public final int mId; /** * @param id the id of the constant @@ -48,6 +48,15 @@ public class LongConstant extends Operation implements Serializable { } /** + * Copy the value from another longConstant + * + * @param from the constant to copy from + */ + public void update(LongConstant from) { + mValue = from.mValue; + } + + /** * Get the value of the long constant * * @return the value of the long diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java index 1f9a27429067..f5b2cca15e43 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java @@ -41,6 +41,7 @@ import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.RemoteContextAware; import com.android.internal.widget.remotecompose.core.operations.NamedVariable; import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior; +import com.android.internal.widget.remotecompose.player.platform.AndroidRemoteContext; import com.android.internal.widget.remotecompose.player.platform.RemoteComposeCanvas; /** A view to to display and play RemoteCompose documents */ @@ -114,6 +115,23 @@ public class RemoteComposePlayer extends FrameLayout implements RemoteContextAwa return mInner.getDocument(); } + /** + * This will update values in the already loaded document. + * + * @param value the document to update variables in the current document width + */ + public void updateDocument(RemoteComposeDocument value) { + RemoteComposeDocument document = value; + AndroidRemoteContext tmpContext = new AndroidRemoteContext(); + document.initializeContext(tmpContext); + float density = getContext().getResources().getDisplayMetrics().density; + tmpContext.setAnimationEnabled(true); + tmpContext.setDensity(density); + tmpContext.setUseChoreographer(false); + mInner.getDocument().mDocument.applyUpdate(document.mDocument); + mInner.invalidate(); + } + public void setDocument(RemoteComposeDocument value) { if (value != null) { if (value.canBeDisplayed( @@ -312,7 +330,8 @@ public class RemoteComposePlayer extends FrameLayout implements RemoteContextAwa } /** - * Add a callback for handling id actions events on the document + * Add a callback for handling id actions events on the document. Can only be added after the + * document has been loaded. * * @param callback the callback lambda that will be used when a action is executed * <p>The parameter of the callback are: diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java index ad2414974780..575a6b2ee518 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java @@ -22,7 +22,6 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.TouchListener; import com.android.internal.widget.remotecompose.core.VariableSupport; @@ -43,7 +42,6 @@ import java.util.HashMap; * * <p>This is used to play the RemoteCompose operations on Android. */ -@VisibleForTesting public class AndroidRemoteContext extends RemoteContext { public void useCanvas(Canvas canvas) { @@ -197,7 +195,7 @@ public class AndroidRemoteContext extends RemoteContext { @Override public void runAction(int id, @NonNull String metadata) { - mDocument.performClick(this, id); + mDocument.performClick(this, id, metadata); } @Override diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java index 29cd40def562..17f4fc82af5f 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java @@ -102,6 +102,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta mDocument = value; mDocument.initializeContext(mARContext); mDisable = false; + mARContext.setDocLoadTime(); mARContext.setAnimationEnabled(true); mARContext.setDensity(mDensity); mARContext.setUseChoreographer(true); @@ -154,7 +155,11 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta param.leftMargin = (int) area.getLeft(); param.topMargin = (int) area.getTop(); viewArea.setOnClickListener( - view1 -> mDocument.getDocument().performClick(mARContext, area.getId())); + view1 -> + mDocument + .getDocument() + .performClick( + mARContext, area.getId(), area.getMetadata())); addView(viewArea, param); } if (!clickAreas.isEmpty()) { diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp index dec724b6a7ff..e1b3479c7ed2 100644 --- a/core/jni/android_os_Parcel.cpp +++ b/core/jni/android_os_Parcel.cpp @@ -558,8 +558,7 @@ static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jlong nativePtr delete parcel; } -static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong nativePtr) -{ +static Parcel* parcel_for_marshall(JNIEnv* env, jlong nativePtr) { Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); if (parcel == NULL) { return NULL; @@ -577,6 +576,16 @@ static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong na return NULL; } + return parcel; +} + +static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong nativePtr) +{ + Parcel* parcel = parcel_for_marshall(env, nativePtr); + if (parcel == NULL) { + return NULL; + } + jbyteArray ret = env->NewByteArray(parcel->dataSize()); if (ret != NULL) @@ -592,6 +601,56 @@ static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong na return ret; } +static long ensure_capacity(JNIEnv* env, Parcel* parcel, jint remaining) { + long dataSize = parcel->dataSize(); + if (remaining < dataSize) { + jnihelp::ThrowException(env, "java/nio/BufferOverflowException", "()V"); + return -1; + } + return dataSize; +} + +static int android_os_Parcel_marshall_array(JNIEnv* env, jclass clazz, jlong nativePtr, + jbyteArray data, jint offset, jint remaining) +{ + Parcel* parcel = parcel_for_marshall(env, nativePtr); + if (parcel == NULL) { + return 0; + } + + long data_size = ensure_capacity(env, parcel, remaining); + if (data_size < 0) { + return 0; + } + + jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(data, 0); + if (array != NULL) + { + memcpy(array + offset, parcel->data(), data_size); + env->ReleasePrimitiveArrayCritical(data, array, 0); + } + return data_size; +} + +static int android_os_Parcel_marshall_buffer(JNIEnv* env, jclass clazz, jlong nativePtr, + jobject javaBuffer, jint offset, jint remaining) { + Parcel* parcel = parcel_for_marshall(env, nativePtr); + if (parcel == NULL) { + return 0; + } + + long data_size = ensure_capacity(env, parcel, remaining); + if (data_size < 0) { + return 0; + } + + jbyte* buffer = (jbyte*)env->GetDirectBufferAddress(javaBuffer); + if (buffer != NULL) { + memcpy(buffer + offset, parcel->data(), data_size); + } + return data_size; +} + static void android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong nativePtr, jbyteArray data, jint offset, jint length) { @@ -613,6 +672,25 @@ static void android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong native } } +static void android_os_Parcel_unmarshall_buffer(JNIEnv* env, jclass clazz, jlong nativePtr, + jobject javaBuffer, jint offset, jint length) +{ + Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); + if (parcel == NULL || length < 0) { + return; + } + + jbyte* buffer = (jbyte*)env->GetDirectBufferAddress(javaBuffer); + if (buffer) + { + parcel->setDataSize(length); + parcel->setDataPosition(0); + + void* raw = parcel->writeInplace(length); + memcpy(raw, (buffer + offset), length); + } +} + static jint android_os_Parcel_compareData(JNIEnv* env, jclass clazz, jlong thisNativePtr, jlong otherNativePtr) { @@ -911,7 +989,10 @@ static const JNINativeMethod gParcelMethods[] = { {"nativeDestroy", "(J)V", (void*)android_os_Parcel_destroy}, {"nativeMarshall", "(J)[B", (void*)android_os_Parcel_marshall}, + {"nativeMarshallArray", "(J[BII)I", (void*)android_os_Parcel_marshall_array}, + {"nativeMarshallBuffer", "(JLjava/nio/ByteBuffer;II)I", (void*)android_os_Parcel_marshall_buffer}, {"nativeUnmarshall", "(J[BII)V", (void*)android_os_Parcel_unmarshall}, + {"nativeUnmarshallBuffer", "(JLjava/nio/ByteBuffer;II)V", (void*)android_os_Parcel_unmarshall_buffer}, {"nativeCompareData", "(JJ)I", (void*)android_os_Parcel_compareData}, {"nativeCompareDataInRange", "(JIJII)Z", (void*)android_os_Parcel_compareDataInRange}, {"nativeAppendFrom", "(JJII)V", (void*)android_os_Parcel_appendFrom}, diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp index e874163943b6..a6a748c3deb7 100644 --- a/core/jni/android_view_InputChannel.cpp +++ b/core/jni/android_view_InputChannel.cpp @@ -55,41 +55,25 @@ public: inline std::shared_ptr<InputChannel> getInputChannel() { return mInputChannel; } - void setDisposeCallback(InputChannelObjDisposeCallback callback, void* data); void dispose(JNIEnv* env, jobject obj); private: std::shared_ptr<InputChannel> mInputChannel; - InputChannelObjDisposeCallback mDisposeCallback; - void* mDisposeData; }; // ---------------------------------------------------------------------------- NativeInputChannel::NativeInputChannel(std::unique_ptr<InputChannel> inputChannel) - : mInputChannel(std::move(inputChannel)), mDisposeCallback(nullptr) {} + : mInputChannel(std::move(inputChannel)) {} NativeInputChannel::~NativeInputChannel() { } -void NativeInputChannel::setDisposeCallback(InputChannelObjDisposeCallback callback, void* data) { - if (input_flags::remove_input_channel_from_windowstate()) { - return; - } - mDisposeCallback = callback; - mDisposeData = data; -} - void NativeInputChannel::dispose(JNIEnv* env, jobject obj) { if (!mInputChannel) { return; } - if (mDisposeCallback) { - mDisposeCallback(env, obj, mInputChannel, mDisposeData); - mDisposeCallback = nullptr; - mDisposeData = nullptr; - } mInputChannel.reset(); } @@ -108,17 +92,6 @@ std::shared_ptr<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* return nativeInputChannel != nullptr ? nativeInputChannel->getInputChannel() : nullptr; } -void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj, - InputChannelObjDisposeCallback callback, void* data) { - NativeInputChannel* nativeInputChannel = - android_view_InputChannel_getNativeInputChannel(env, inputChannelObj); - if (!nativeInputChannel || !nativeInputChannel->getInputChannel()) { - ALOGW("Cannot set dispose callback because input channel object has not been initialized."); - } else { - nativeInputChannel->setDisposeCallback(callback, data); - } -} - static jlong android_view_InputChannel_createInputChannel( JNIEnv* env, std::unique_ptr<InputChannel> inputChannel) { std::unique_ptr<NativeInputChannel> nativeInputChannel = diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp index 7b085b16d24b..ba74b0e8cce5 100644 --- a/core/jni/android_window_ScreenCapture.cpp +++ b/core/jni/android_window_ScreenCapture.cpp @@ -109,27 +109,29 @@ public: return binder::Status::ok(); } captureResults.fenceResult.value()->waitForever(LOG_TAG); - jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer( - env, captureResults.buffer->toAHardwareBuffer()); - jobject jGainmap = nullptr; + auto jhardwareBuffer = ScopedLocalRef<jobject>( + env, android_hardware_HardwareBuffer_createFromAHardwareBuffer( + env, captureResults.buffer->toAHardwareBuffer())); + auto jGainmap = ScopedLocalRef<jobject>(env); if (captureResults.optionalGainMap) { - jGainmap = android_hardware_HardwareBuffer_createFromAHardwareBuffer( - env, captureResults.optionalGainMap->toAHardwareBuffer()); + jGainmap = ScopedLocalRef<jobject>( + env, android_hardware_HardwareBuffer_createFromAHardwareBuffer( + env, captureResults.optionalGainMap->toAHardwareBuffer())); } - jobject screenshotHardwareBuffer = - env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz, + auto screenshotHardwareBuffer = + ScopedLocalRef<jobject>(env, env->CallStaticObjectMethod( + gScreenshotHardwareBufferClassInfo.clazz, gScreenshotHardwareBufferClassInfo.builder, - jhardwareBuffer, + jhardwareBuffer.get(), static_cast<jint>(captureResults.capturedDataspace), captureResults.capturedSecureLayers, - captureResults.capturedHdrLayers, jGainmap, - captureResults.hdrSdrRatio); + captureResults.capturedHdrLayers, jGainmap.get(), + captureResults.hdrSdrRatio)); checkAndClearException(env, "builder"); - env->CallVoidMethod(consumer.get(), gConsumerClassInfo.accept, screenshotHardwareBuffer, + env->CallVoidMethod(consumer.get(), gConsumerClassInfo.accept, + screenshotHardwareBuffer.get(), fenceStatus(captureResults.fenceResult)); checkAndClearException(env, "accept"); - env->DeleteLocalRef(jhardwareBuffer); - env->DeleteLocalRef(screenshotHardwareBuffer); return binder::Status::ok(); } diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 59e01bfa0c4b..9df351fa39c4 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -35,7 +35,6 @@ import "frameworks/base/core/proto/android/os/system_properties.proto"; import "frameworks/base/core/proto/android/providers/settings.proto"; import "frameworks/base/core/proto/android/server/activitymanagerservice.proto"; import "frameworks/base/core/proto/android/server/alarm/alarmmanagerservice.proto"; -import "frameworks/base/core/proto/android/server/bluetooth_manager_service.proto"; import "frameworks/base/core/proto/android/server/fingerprint.proto"; import "frameworks/base/core/proto/android/server/jobscheduler.proto"; import "frameworks/base/core/proto/android/server/location/context_hub.proto"; @@ -483,10 +482,8 @@ message IncidentProto { (section).args = "connmetrics --proto" ]; - optional com.android.server.BluetoothManagerServiceDumpProto bluetooth_manager = 3050 [ - (section).type = SECTION_DUMPSYS, - (section).args = "bluetooth_manager --proto" - ]; + // Deprecated BluetoothManagerServiceDumpProto + reserved 3050; optional com.android.server.location.ContextHubServiceProto context_hub = 3051 [ (section).type = SECTION_DUMPSYS, diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 1caa9e7af348..5831a0bfc39e 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -239,6 +239,10 @@ message SecureSettingsProto { repeated SettingProto completed_categories = 15; optional SettingProto connectivity_release_pending_intent_delay_ms = 16 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto adaptive_connectivity_enabled = 84 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto adaptive_connectivity_wifi_enabled = 105 [ (android.privacy).dest = + DEST_AUTOMATIC ]; + optional SettingProto adaptive_connectivity_mobile_network_enabled = 106 [ (android.privacy) + .dest = DEST_AUTOMATIC ]; message Controls { option (android.msg_privacy).dest = DEST_EXPLICIT; @@ -741,5 +745,5 @@ message SecureSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 105; + // Next tag = 107; } diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto index 325790c22fce..8393f8b4db61 100644 --- a/core/proto/android/providers/settings/system.proto +++ b/core/proto/android/providers/settings/system.proto @@ -290,7 +290,16 @@ message SystemSettingsProto { optional SettingProto apply_ramping_ringer = 35 [ (android.privacy).dest = DEST_AUTOMATIC ]; + message Display { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + optional SettingProto cv_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; + } + optional Display display = 39; + + + // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 39; + // Next tag = 40; } diff --git a/core/proto/android/server/Android.bp b/core/proto/android/server/Android.bp deleted file mode 100644 index 362daa73ff14..000000000000 --- a/core/proto/android/server/Android.bp +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (C) 2021 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 { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -filegroup { - name: "srcs_bluetooth_manager_service_proto", - srcs: [ - "bluetooth_manager_service.proto", - ], - visibility: ["//packages/modules/Bluetooth:__subpackages__"], -} - diff --git a/core/proto/android/server/bluetooth_manager_service.proto b/core/proto/android/server/bluetooth_manager_service.proto deleted file mode 100644 index c33f66a9aeca..000000000000 --- a/core/proto/android/server/bluetooth_manager_service.proto +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -syntax = "proto2"; -package com.android.server; - -import "frameworks/base/core/proto/android/privacy.proto"; -import "frameworks/proto_logging/stats/enums/bluetooth/enums.proto"; - -option java_multiple_files = true; - -message BluetoothManagerServiceDumpProto { - option (.android.msg_privacy).dest = DEST_AUTOMATIC; - - message ActiveLog { - option (.android.msg_privacy).dest = DEST_AUTOMATIC; - optional int64 timestamp_ms = 1; - optional bool enable = 2; - optional string package_name = 3; - optional .android.bluetooth.EnableDisableReasonEnum reason = 4; - } - - optional bool enabled = 1; - optional int32 state = 2; - optional string state_name = 3; - optional string address = 4 [(.android.privacy).dest = DEST_EXPLICIT]; - optional string name = 5 [(.android.privacy).dest = DEST_EXPLICIT]; - optional int64 last_enabled_time_ms = 6; - optional int64 curr_timestamp_ms = 7; - repeated ActiveLog active_logs = 8; - optional int32 num_crashes = 9; - optional bool crash_log_maxed = 10; - repeated int64 crash_timestamps_ms = 11; - optional int32 num_ble_apps = 12; - repeated string ble_app_package_names = 13; -}
\ No newline at end of file diff --git a/core/res/res/layout/accessibility_autoclick_type_panel.xml b/core/res/res/layout/accessibility_autoclick_type_panel.xml index 902ef7fc38e8..615af6fb16f8 100644 --- a/core/res/res/layout/accessibility_autoclick_type_panel.xml +++ b/core/res/res/layout/accessibility_autoclick_type_panel.xml @@ -49,7 +49,8 @@ android:id="@+id/accessibility_autoclick_drag_button" style="@style/AccessibilityAutoclickPanelImageButtonStyle" android:contentDescription="@string/accessibility_autoclick_drag" - android:src="@drawable/accessibility_autoclick_drag" /> + android:src="@drawable/accessibility_autoclick_drag" + android:clickable="false" /> </LinearLayout> <LinearLayout @@ -60,7 +61,8 @@ android:id="@+id/accessibility_autoclick_double_click_button" style="@style/AccessibilityAutoclickPanelImageButtonStyle" android:contentDescription="@string/accessibility_autoclick_double_click" - android:src="@drawable/accessibility_autoclick_double_click" /> + android:src="@drawable/accessibility_autoclick_double_click" + android:clickable="false" /> </LinearLayout> <LinearLayout @@ -71,7 +73,8 @@ android:id="@+id/accessibility_autoclick_right_click_button" style="@style/AccessibilityAutoclickPanelImageButtonStyle" android:contentDescription="@string/accessibility_autoclick_right_click" - android:src="@drawable/accessibility_autoclick_right_click" /> + android:src="@drawable/accessibility_autoclick_right_click" + android:clickable="false" /> </LinearLayout> <LinearLayout @@ -82,7 +85,8 @@ android:id="@+id/accessibility_autoclick_scroll_button" style="@style/AccessibilityAutoclickPanelImageButtonStyle" android:contentDescription="@string/accessibility_autoclick_scroll" - android:src="@drawable/accessibility_autoclick_scroll" /> + android:src="@drawable/accessibility_autoclick_scroll" + android:clickable="false" /> </LinearLayout> <LinearLayout @@ -93,7 +97,8 @@ android:id="@+id/accessibility_autoclick_left_click_button" style="@style/AccessibilityAutoclickPanelImageButtonStyle" android:contentDescription="@string/accessibility_autoclick_left_click" - android:src="@drawable/accessibility_autoclick_left_click" /> + android:src="@drawable/accessibility_autoclick_left_click" + android:clickable="false" /> </LinearLayout> </LinearLayout> @@ -114,7 +119,8 @@ android:id="@+id/accessibility_autoclick_pause_button" style="@style/AccessibilityAutoclickPanelImageButtonStyle" android:contentDescription="@string/accessibility_autoclick_pause" - android:src="@drawable/accessibility_autoclick_pause" /> + android:src="@drawable/accessibility_autoclick_pause" + android:clickable="false" /> </LinearLayout> <LinearLayout @@ -125,7 +131,8 @@ android:id="@+id/accessibility_autoclick_position_button" style="@style/AccessibilityAutoclickPanelImageButtonStyle" android:contentDescription="@string/accessibility_autoclick_position" - android:src="@drawable/accessibility_autoclick_position" /> + android:src="@drawable/accessibility_autoclick_position" + android:clickable="false" /> </LinearLayout> </LinearLayout> diff --git a/core/res/res/layout/notification_2025_action_list.xml b/core/res/res/layout/notification_2025_action_list.xml index 053aca068027..6c07ec1a7c7b 100644 --- a/core/res/res/layout/notification_2025_action_list.xml +++ b/core/res/res/layout/notification_2025_action_list.xml @@ -22,6 +22,7 @@ android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="@dimen/notification_2025_action_list_margin_bottom" + android:minHeight="@dimen/notification_2025_action_list_min_height" > <LinearLayout diff --git a/core/res/res/layout/notification_2025_conversation_icon_container.xml b/core/res/res/layout/notification_2025_conversation_icon_container.xml index 7ec2450ceb71..16c95009051c 100644 --- a/core/res/res/layout/notification_2025_conversation_icon_container.xml +++ b/core/res/res/layout/notification_2025_conversation_icon_container.xml @@ -29,7 +29,8 @@ <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_margin="@dimen/notification_2025_margin" + android:layout_marginHorizontal="@dimen/notification_2025_margin" + android:layout_marginTop="@dimen/notification_2025_margin" android:clipChildren="false" android:clipToPadding="false" android:layout_gravity="top|center_horizontal" diff --git a/core/res/res/layout/notification_2025_reply_history_container.xml b/core/res/res/layout/notification_2025_reply_history_container.xml index 6923b59f34dc..256f7b89c784 100644 --- a/core/res/res/layout/notification_2025_reply_history_container.xml +++ b/core/res/res/layout/notification_2025_reply_history_container.xml @@ -28,16 +28,16 @@ android:layout_width="match_parent" android:layout_height="1dip" android:id="@+id/action_divider" - android:layout_marginTop="@dimen/notification_content_margin" - android:layout_marginBottom="@dimen/notification_content_margin" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginTop="@dimen/notification_2025_margin" + android:layout_marginBottom="@dimen/notification_2025_margin" + android:layout_marginEnd="@dimen/notification_2025_margin" android:background="@drawable/notification_template_divider" /> <TextView android:id="@+id/notification_material_reply_text_3" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:visibility="gone" android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Reply" android:singleLine="true" /> @@ -46,7 +46,7 @@ android:id="@+id/notification_material_reply_text_2" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:visibility="gone" android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Reply" android:singleLine="true" /> @@ -56,13 +56,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:layout_marginEnd="@dimen/notification_content_margin_end"> + android:layout_marginEnd="@dimen/notification_2025_margin"> <TextView android:id="@+id/notification_material_reply_text_1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:layout_gravity="center" android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Reply" android:singleLine="true" /> diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml index 63f32e3b3cd2..57c89b91cff7 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_base.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml @@ -74,7 +74,6 @@ android:id="@+id/notification_headerless_view_column" android:layout_width="0px" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" android:layout_weight="1" android:layout_marginVertical="@dimen/notification_2025_reduced_margin" android:orientation="vertical" @@ -157,7 +156,7 @@ android:id="@+id/expand_button_touch_container" android:layout_width="wrap_content" android:layout_height="match_parent" - android:minWidth="@dimen/notification_content_margin_end" + android:minWidth="@dimen/notification_2025_margin" > <include layout="@layout/notification_2025_expand_button" diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml index 4e0cf753722f..c57196e9c9e8 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_call.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml @@ -32,11 +32,11 @@ android:orientation="vertical" > - <com.android.internal.widget.NotificationMaxHeightFrameLayout + <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="@dimen/notification_2025_min_height" android:clipChildren="false" + android:layout_weight="1" > <ImageView @@ -77,9 +77,7 @@ android:id="@+id/notification_headerless_view_column" android:layout_width="0px" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" android:layout_weight="1" - android:layout_marginBottom="@dimen/notification_2025_margin" android:layout_marginTop="@dimen/notification_2025_margin" android:clipChildren="false" android:orientation="vertical" @@ -151,7 +149,7 @@ android:id="@+id/expand_button_touch_container" android:layout_width="wrap_content" android:layout_height="match_parent" - android:minWidth="@dimen/notification_content_margin_end" + android:minWidth="@dimen/notification_2025_margin" > <include layout="@layout/notification_2025_expand_button" @@ -170,23 +168,15 @@ android:layout_height="@dimen/notification_close_button_size" android:layout_gravity="top|end" /> - </com.android.internal.widget.NotificationMaxHeightFrameLayout> + </FrameLayout> - <LinearLayout - android:id="@+id/notification_action_list_margin_target" + <include layout="@layout/notification_template_smart_reply_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="-20dp" - android:clipChildren="false" - android:orientation="vertical"> - <include layout="@layout/notification_template_smart_reply_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" - android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" /> - <include layout="@layout/notification_2025_action_list" /> - </LinearLayout> + android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_2025_margin" /> + <include layout="@layout/notification_2025_action_list" /> </LinearLayout> </com.android.internal.widget.CallLayout> diff --git a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml index 5783201981a8..1c6fcb50dca5 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml @@ -32,12 +32,11 @@ android:orientation="vertical" > - - <com.android.internal.widget.NotificationMaxHeightFrameLayout + <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="@dimen/notification_2025_min_height" android:clipChildren="false" + android:layout_weight="1" > <ImageView @@ -78,17 +77,11 @@ android:clipChildren="false" > - <!-- - NOTE: because messaging will always have 2 lines, this LinearLayout should NOT - have the id/notification_headerless_view_column, as that is used for modifying - vertical margins to accommodate the single-line state that base supports - --> <LinearLayout + android:id="@+id/notification_headerless_view_column" android:layout_width="0px" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" android:layout_weight="1" - android:layout_marginBottom="@dimen/notification_2025_margin" android:layout_marginTop="@dimen/notification_2025_margin" android:layout_marginStart="@dimen/notification_2025_content_margin_start" android:clipChildren="false" @@ -150,7 +143,6 @@ android:layout_height="@dimen/notification_right_icon_size" android:layout_gravity="center_vertical|end" android:layout_marginTop="@dimen/notification_2025_margin" - android:layout_marginBottom="@dimen/notification_2025_margin" android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin" android:forceHasOverlappingRendering="false" android:spacing="0dp" @@ -175,7 +167,7 @@ android:id="@+id/expand_button_touch_container" android:layout_width="wrap_content" android:layout_height="match_parent" - android:minWidth="@dimen/notification_content_margin_end" + android:minWidth="@dimen/notification_2025_margin" > <include layout="@layout/notification_2025_expand_button" @@ -194,22 +186,15 @@ android:layout_height="@dimen/notification_close_button_size" android:layout_gravity="top|end" /> - </com.android.internal.widget.NotificationMaxHeightFrameLayout> + </FrameLayout> - <LinearLayout - android:id="@+id/notification_action_list_margin_target" + <include layout="@layout/notification_template_smart_reply_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="-20dp" - android:clipChildren="false" - android:orientation="vertical"> - <include layout="@layout/notification_template_smart_reply_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" - android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" /> + android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_2025_margin" /> <include layout="@layout/notification_2025_action_list" /> + </LinearLayout> -</LinearLayout> </com.android.internal.widget.ConversationLayout> diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml index 629af77b3dda..de82f9feb512 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_media.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml @@ -76,7 +76,6 @@ android:id="@+id/notification_headerless_view_column" android:layout_width="0px" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" android:layout_weight="1" android:layout_marginVertical="@dimen/notification_2025_reduced_margin" android:orientation="vertical" @@ -178,7 +177,7 @@ android:id="@+id/expand_button_touch_container" android:layout_width="wrap_content" android:layout_height="match_parent" - android:minWidth="@dimen/notification_content_margin_end" + android:minWidth="@dimen/notification_2025_margin" > <include layout="@layout/notification_2025_expand_button" diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml index 6391b1ebf744..8e2cb23f1198 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml @@ -35,11 +35,11 @@ > - <com.android.internal.widget.NotificationMaxHeightFrameLayout + <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="@dimen/notification_2025_min_height" android:clipChildren="false" + android:layout_weight="1" > <ImageView @@ -60,7 +60,8 @@ android:layout_width="@dimen/notification_2025_icon_circle_size" android:layout_height="@dimen/notification_2025_icon_circle_size" android:layout_alignParentStart="true" - android:layout_margin="@dimen/notification_2025_margin" + android:layout_marginHorizontal="@dimen/notification_2025_margin" + android:layout_marginTop="@dimen/notification_2025_margin" android:background="@drawable/notification_icon_circle" android:padding="@dimen/notification_2025_icon_circle_padding" /> @@ -88,17 +89,11 @@ android:clipChildren="false" > - <!-- - NOTE: because messaging will always have 2 lines, this LinearLayout should NOT - have the id/notification_headerless_view_column, as that is used for modifying - vertical margins to accommodate the single-line state that base supports - --> <LinearLayout + android:id="@+id/notification_headerless_view_column" android:layout_width="0px" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" android:layout_weight="1" - android:layout_marginBottom="@dimen/notification_2025_margin" android:layout_marginTop="@dimen/notification_2025_margin" android:layout_marginStart="@dimen/notification_2025_content_margin_start" android:clipChildren="false" @@ -160,7 +155,6 @@ android:layout_height="@dimen/notification_right_icon_size" android:layout_gravity="center_vertical|end" android:layout_marginTop="@dimen/notification_2025_margin" - android:layout_marginBottom="@dimen/notification_2025_margin" android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin" android:forceHasOverlappingRendering="false" android:spacing="0dp" @@ -185,7 +179,7 @@ android:id="@+id/expand_button_touch_container" android:layout_width="wrap_content" android:layout_height="match_parent" - android:minWidth="@dimen/notification_content_margin_end" + android:minWidth="@dimen/notification_2025_margin" > <include layout="@layout/notification_2025_expand_button" @@ -204,22 +198,15 @@ android:layout_height="@dimen/notification_close_button_size" android:layout_gravity="top|end" /> - </com.android.internal.widget.NotificationMaxHeightFrameLayout> + </FrameLayout> - <LinearLayout - android:id="@+id/notification_action_list_margin_target" + <include layout="@layout/notification_template_smart_reply_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="-20dp" - android:clipChildren="false" - android:orientation="vertical"> - <include layout="@layout/notification_template_smart_reply_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" - android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" /> + android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_2025_margin" /> <include layout="@layout/notification_2025_action_list" /> + </LinearLayout> -</LinearLayout> </com.android.internal.widget.MessagingLayout> diff --git a/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml b/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml index 52bc7b8ea3bb..b32a7788c433 100644 --- a/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml +++ b/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml @@ -76,7 +76,7 @@ android:id="@+id/expand_button_touch_container" android:layout_width="wrap_content" android:layout_height="match_parent" - android:minWidth="@dimen/notification_content_margin_end" + android:minWidth="@dimen/notification_2025_margin" > <include layout="@layout/notification_2025_expand_button" android:layout_width="wrap_content" diff --git a/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml b/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml index be6404609f25..268396f11cab 100644 --- a/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml +++ b/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml @@ -103,7 +103,7 @@ android:id="@+id/expand_button_touch_container" android:layout_width="wrap_content" android:layout_height="match_parent" - android:minWidth="@dimen/notification_content_margin_end" + android:minWidth="@dimen/notification_2025_margin" > <include layout="@layout/notification_2025_expand_button" android:layout_width="wrap_content" diff --git a/core/res/res/layout/notification_2025_template_expanded_base.xml b/core/res/res/layout/notification_2025_template_expanded_base.xml index 76a85813b980..287110766dc7 100644 --- a/core/res/res/layout/notification_2025_template_expanded_base.xml +++ b/core/res/res/layout/notification_2025_template_expanded_base.xml @@ -24,10 +24,8 @@ > <LinearLayout - android:id="@+id/notification_action_list_margin_target" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/notification_content_margin" android:orientation="vertical" > @@ -47,7 +45,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:orientation="vertical" > @@ -78,7 +76,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" /> diff --git a/core/res/res/layout/notification_2025_template_expanded_big_picture.xml b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml index 999afa66c65b..ead6d4cbc034 100644 --- a/core/res/res/layout/notification_2025_template_expanded_big_picture.xml +++ b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml @@ -28,7 +28,6 @@ <include layout="@layout/notification_2025_right_icon" /> <LinearLayout - android:id="@+id/notification_action_list_margin_target" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="top" @@ -43,7 +42,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:orientation="vertical" > @@ -67,7 +66,7 @@ android:layout_weight="1" android:layout_marginTop="13dp" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:background="@drawable/notification_big_picture_outline" android:clipToOutline="true" android:scaleType="centerCrop" @@ -85,7 +84,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" /> diff --git a/core/res/res/layout/notification_2025_template_expanded_big_text.xml b/core/res/res/layout/notification_2025_template_expanded_big_text.xml index c9206eddbcde..de5e71d2015f 100644 --- a/core/res/res/layout/notification_2025_template_expanded_big_text.xml +++ b/core/res/res/layout/notification_2025_template_expanded_big_text.xml @@ -26,11 +26,9 @@ <include layout="@layout/notification_2025_template_header" /> <com.android.internal.widget.RemeasuringLinearLayout - android:id="@+id/notification_action_list_margin_target" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" - android:layout_marginBottom="@dimen/notification_content_margin" android:clipToPadding="false" android:orientation="vertical" > @@ -43,7 +41,7 @@ android:layout_height="wrap_content" android:layout_gravity="top" android:paddingStart="@dimen/notification_2025_content_margin_start" - android:paddingEnd="@dimen/notification_content_margin_end" + android:paddingEnd="@dimen/notification_2025_margin" android:clipToPadding="false" android:orientation="vertical" android:layout_weight="1" @@ -84,7 +82,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" /> diff --git a/core/res/res/layout/notification_2025_template_expanded_call.xml b/core/res/res/layout/notification_2025_template_expanded_call.xml index ec214554a30b..c096bc8a4d15 100644 --- a/core/res/res/layout/notification_2025_template_expanded_call.xml +++ b/core/res/res/layout/notification_2025_template_expanded_call.xml @@ -30,7 +30,6 @@ <include layout="@layout/notification_2025_conversation_header"/> <com.android.internal.widget.RemeasuringLinearLayout - android:id="@+id/notification_action_list_margin_target" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" @@ -46,7 +45,7 @@ android:layout_gravity="top" android:layout_weight="1" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:orientation="vertical" android:clipChildren="false" > @@ -62,7 +61,7 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" /> + android:layout_marginEnd="@dimen/notification_2025_margin" /> <include layout="@layout/notification_2025_action_list" /> diff --git a/core/res/res/layout/notification_2025_template_expanded_conversation.xml b/core/res/res/layout/notification_2025_template_expanded_conversation.xml index 6ee82fa116ff..6eea8cc93aeb 100644 --- a/core/res/res/layout/notification_2025_template_expanded_conversation.xml +++ b/core/res/res/layout/notification_2025_template_expanded_conversation.xml @@ -29,7 +29,6 @@ <include layout="@layout/notification_2025_conversation_header"/> <com.android.internal.widget.RemeasuringLinearLayout - android:id="@+id/notification_action_list_margin_target" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" @@ -44,7 +43,7 @@ android:layout_height="wrap_content" android:layout_gravity="top" android:layout_weight="1" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:orientation="vertical" android:clipChildren="false" > @@ -64,7 +63,7 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" /> + android:layout_marginEnd="@dimen/notification_2025_margin" /> <include layout="@layout/notification_2025_action_list" /> diff --git a/core/res/res/layout/notification_2025_template_expanded_inbox.xml b/core/res/res/layout/notification_2025_template_expanded_inbox.xml index 1eaef228aaca..327cd7ac71bb 100644 --- a/core/res/res/layout/notification_2025_template_expanded_inbox.xml +++ b/core/res/res/layout/notification_2025_template_expanded_inbox.xml @@ -24,7 +24,6 @@ > <include layout="@layout/notification_2025_template_header" /> <LinearLayout - android:id="@+id/notification_action_list_margin_target" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="top" @@ -39,7 +38,7 @@ android:layout_height="wrap_content" android:layout_gravity="top" android:paddingStart="@dimen/notification_2025_content_margin_start" - android:paddingEnd="@dimen/notification_content_margin_end" + android:paddingEnd="@dimen/notification_2025_margin" android:layout_weight="1" android:clipToPadding="false" android:orientation="vertical" @@ -126,7 +125,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" /> <include layout="@layout/notification_2025_action_list" /> </LinearLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_media.xml b/core/res/res/layout/notification_2025_template_expanded_media.xml index 801e339b3a92..565a558a5115 100644 --- a/core/res/res/layout/notification_2025_template_expanded_media.xml +++ b/core/res/res/layout/notification_2025_template_expanded_media.xml @@ -41,7 +41,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:orientation="vertical" > <include layout="@layout/notification_template_part_line1"/> @@ -53,7 +53,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notification_2025_media_actions_margin_start" - android:minHeight="@dimen/notification_content_margin" + android:minHeight="@dimen/notification_2025_margin" > <!-- Nesting in FrameLayout is required to ensure that the marginStart actually applies diff --git a/core/res/res/layout/notification_2025_template_expanded_messaging.xml b/core/res/res/layout/notification_2025_template_expanded_messaging.xml index 62059af7f056..df48479e5ec3 100644 --- a/core/res/res/layout/notification_2025_template_expanded_messaging.xml +++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml @@ -29,7 +29,6 @@ <include layout="@layout/notification_2025_template_header"/> <com.android.internal.widget.RemeasuringLinearLayout - android:id="@+id/notification_action_list_margin_target" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" @@ -44,7 +43,7 @@ android:layout_height="wrap_content" android:layout_gravity="top" android:layout_weight="1" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:orientation="vertical" android:clipChildren="false" > @@ -64,7 +63,7 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" /> + android:layout_marginEnd="@dimen/notification_2025_margin" /> <include layout="@layout/notification_2025_action_list" /> diff --git a/core/res/res/layout/notification_2025_template_expanded_progress.xml b/core/res/res/layout/notification_2025_template_expanded_progress.xml index cf39d8b08c4f..b929b9ed20b7 100644 --- a/core/res/res/layout/notification_2025_template_expanded_progress.xml +++ b/core/res/res/layout/notification_2025_template_expanded_progress.xml @@ -25,10 +25,8 @@ > <LinearLayout - android:id="@+id/notification_action_list_margin_target" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/notification_content_margin" android:orientation="vertical" > @@ -48,7 +46,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:orientation="vertical" > @@ -114,7 +112,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" /> diff --git a/core/res/res/layout/notification_2025_template_heads_up_base.xml b/core/res/res/layout/notification_2025_template_heads_up_base.xml index 4d3b2453637d..e416c5054e00 100644 --- a/core/res/res/layout/notification_2025_template_heads_up_base.xml +++ b/core/res/res/layout/notification_2025_template_heads_up_base.xml @@ -56,7 +56,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notification_2025_content_margin_start" - android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginEnd="@dimen/notification_2025_margin" android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 7a38dce296de..e47adc90fc7a 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2831,6 +2831,10 @@ <!-- Whether dreams are disabled when ambient mode is suppressed. --> <bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool> + <!-- The default for the setting that controls when to auto-start hub mode. + 0 means "never" --> + <integer name="config_whenToStartHubModeDefault">0</integer> + <!-- The duration in milliseconds of the dream opening animation. --> <integer name="config_dreamOpenAnimationDuration">250</integer> <!-- The duration in milliseconds of the dream closing animation. --> @@ -6108,6 +6112,9 @@ <!-- Whether to default to an expanded list of users on the lock screen user switcher. --> <bool name="config_expandLockScreenUserSwitcher">false</bool> + <!-- Help URI, action disabled by advanced protection [DO NOT TRANSLATE] --> + <string name="config_help_url_action_disabled_by_advanced_protection" translatable="false"></string> + <!-- Toasts posted from these packages will be shown to the current user, regardless of the user the process belongs to. This is useful for packages that run under a single user but serve multiple users, e.g. the system. diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 6e540833bc46..b5ed28f82dda 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -295,6 +295,11 @@ <!-- The margin of the notification action list at the bottom in the 2025 redesign --> <dimen name="notification_2025_action_list_margin_bottom">6dp</dimen> + <!-- The minimum height of the notification action container, to act as a bottom padding for the + notification when there are no actions. This should always be equal to + notification_2025_margin - notification_2025_action_list_margin_bottom. --> + <dimen name="notification_2025_action_list_min_height">10dp</dimen> + <!-- The overall height of the emphasized notification action --> <dimen name="notification_action_emphasized_height">48dp</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 922d59dfcfe0..93fe4085e843 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2072,6 +2072,7 @@ <java-symbol type="bool" name="config_allowTheaterModeWakeFromWindowLayout" /> <java-symbol type="bool" name="config_keepDreamingWhenUnplugging" /> <java-symbol type="bool" name="config_glanceableHubEnabled" /> + <java-symbol type="integer" name="config_whenToStartHubModeDefault" /> <java-symbol type="integer" name="config_keyguardDrawnTimeout" /> <java-symbol type="bool" name="config_goToSleepOnButtonPressTheaterMode" /> <java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" /> @@ -5963,6 +5964,8 @@ <!-- Device CMF Theming Settings --> <java-symbol type="array" name="theming_defaults" /> + <java-symbol type="string" name="config_help_url_action_disabled_by_advanced_protection" /> + <!-- Advanced Protection Service USB feature --> <java-symbol type="string" name="usb_apm_usb_plugged_in_when_locked_notification_title" /> <java-symbol type="string" name="usb_apm_usb_plugged_in_when_locked_notification_text" /> diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java index fa0744775f6d..5af837fce612 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java @@ -18,7 +18,7 @@ package com.android.server.broadcastradio.hal2; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static org.junit.Assert.*; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; diff --git a/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java b/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java index e8b295bd5fdb..0287e6c086aa 100644 --- a/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java +++ b/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java @@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.hardware.usb.UsbDevice; import android.platform.test.annotations.RequiresFlagsEnabled; @@ -118,6 +118,6 @@ public class BrailleDisplayControllerImplTest { verify(mBrailleDisplayCallback).onConnectionFailed( BrailleDisplayController.BrailleDisplayCallback.FLAG_ERROR_CANNOT_ACCESS); - verifyZeroInteractions(mAccessibilityServiceConnection); + verifyNoMoreInteractions(mAccessibilityServiceConnection); } } diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java index dccbf4036b3e..70b4150115fe 100644 --- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java +++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java @@ -233,7 +233,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { /** * Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing */ - private class TestServicesCache extends RegisteredServicesCache<TestServiceType> { + public class TestServicesCache extends RegisteredServicesCache<TestServiceType> { static final String SERVICE_INTERFACE = "RegisteredServicesCacheTest"; static final String SERVICE_META_DATA = "RegisteredServicesCacheTest"; static final String ATTRIBUTES_NAME = "test"; @@ -245,12 +245,6 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, new TestSerializer()); } - TestServicesCache(Injector<TestServiceType> injector, - XmlSerializerAndParser<TestServiceType> serializerAndParser) { - super(injector, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, - serializerAndParser); - } - @Override public TestServiceType parseServiceAttributes(Resources res, String packageName, AttributeSet attrs) { @@ -338,7 +332,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { } } - static class TestSerializer implements XmlSerializerAndParser<TestServiceType> { + public static class TestSerializer implements XmlSerializerAndParser<TestServiceType> { public void writeAsXml(TestServiceType item, TypedXmlSerializer out) throws IOException { out.attribute(null, "type", item.type); @@ -353,7 +347,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { } } - static class TestServiceType implements Parcelable { + public static class TestServiceType implements Parcelable { final String type; final String value; diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java new file mode 100644 index 000000000000..8349659517c5 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import static android.content.pm.Flags.FLAG_OPTIMIZE_PARSING_IN_REGISTERED_SERVICES_CACHE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.RegisteredServicesCacheTest.TestServiceType; +import android.content.res.Resources; +import android.os.Handler; +import android.os.Message; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.util.AttributeSet; +import android.util.SparseArray; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.os.BackgroundThread; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Unit tests for {@link android.content.pm.RegisteredServicesCache} + */ +@Presubmit +@RunWith(AndroidJUnit4.class) +@RequiresFlagsEnabled(FLAG_OPTIMIZE_PARSING_IN_REGISTERED_SERVICES_CACHE) +public class RegisteredServicesCacheUnitTest { + private static final String TAG = "RegisteredServicesCacheUnitTest"; + private static final int U0 = 0; + private static final int U1 = 1; + private static final int UID1 = 1; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private final ResolveInfo mResolveInfo1 = new ResolveInfo(); + private final ResolveInfo mResolveInfo2 = new ResolveInfo(); + private final TestServiceType mTestServiceType1 = new TestServiceType("t1", "value1"); + private final TestServiceType mTestServiceType2 = new TestServiceType("t2", "value2"); + @Mock + RegisteredServicesCache.Injector<TestServiceType> mMockInjector; + @Mock + Context mMockContext; + Handler mMockBackgroundHandler; + @Mock + PackageManager mMockPackageManager; + + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mMockInjector.getContext()).thenReturn(mMockContext); + mMockBackgroundHandler = spy(BackgroundThread.getHandler()); + when(mMockInjector.getBackgroundHandler()).thenReturn(mMockBackgroundHandler); + doReturn(mock(Intent.class)).when(mMockContext).registerReceiverAsUser(any(), any(), any(), + any(), any()); + doReturn(mock(Intent.class)).when(mMockContext).registerReceiver(any(), any(), any(), + any()); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + + addServiceInfoIntoResolveInfo(mResolveInfo1, "r1.package.name" /* packageName */, + "r1.service.name" /* serviceName */); + addServiceInfoIntoResolveInfo(mResolveInfo2, "r2.package.name" /* packageName */, + "r2.service.name" /* serviceName */); + } + + @Test + public void testSaveServiceInfoIntoCaches() throws Exception { + PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */); + when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName), + anyInt(), eq(U0))).thenReturn(packageInfo1); + PackageInfo packageInfo2 = createPackageInfo(2000L /* lastUpdateTime */); + when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo2.serviceInfo.packageName), + anyInt(), eq(U1))).thenReturn(packageInfo2); + + TestRegisteredServicesCache testServicesCache = spy( + new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */)); + final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo( + mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(), + 1000L /* lastUpdateTime */); + testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1); + + int u1uid = UserHandle.getUid(U1, UID1); + final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo2 = newServiceInfo( + mTestServiceType2, u1uid, mResolveInfo2.serviceInfo.getComponentName(), + 2000L /* lastUpdateTime */); + testServicesCache.addServiceForQuerying(U1, mResolveInfo2, serviceInfo2); + + testServicesCache.getAllServices(U0); + verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L)); + testServicesCache.getAllServices(U1); + verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo2), eq(2000L)); + + reset(testServicesCache); + + testServicesCache.invalidateCache(U0); + testServicesCache.invalidateCache(U1); + testServicesCache.getAllServices(U0); + verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo1), eq(1000L)); + testServicesCache.getAllServices(U1); + verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo2), eq(2000L)); + } + + @Test + public void testClearServiceInfoCachesAfterRemoveUserId() throws Exception { + PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */); + when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName), + anyInt(), eq(U0))).thenReturn(packageInfo1); + + TestRegisteredServicesCache testServicesCache = spy( + new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */)); + final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo( + mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(), + 1000L /* lastUpdateTime */); + testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1); + + testServicesCache.getAllServices(U0); + verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L)); + + reset(testServicesCache); + + testServicesCache.onUserRemoved(U0); + testServicesCache.getAllServices(U0); + verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L)); + } + + @Test + public void testGetServiceInfoCachesForMultiUser() throws Exception { + PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */); + when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName), + anyInt(), eq(U0))).thenReturn(packageInfo1); + when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName), + anyInt(), eq(U1))).thenReturn(packageInfo1); + + TestRegisteredServicesCache testServicesCache = spy( + new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */)); + final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo( + mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(), + 1000L /* lastUpdateTime */); + testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1); + + testServicesCache.getAllServices(U0); + verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L)); + + reset(testServicesCache); + + testServicesCache.clearServicesForQuerying(); + int u1uid = UserHandle.getUid(U1, UID1); + assertThat(u1uid).isNotEqualTo(UID1); + + final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo2 = newServiceInfo( + mTestServiceType1, u1uid, mResolveInfo1.serviceInfo.getComponentName(), + 1000L /* lastUpdateTime */); + testServicesCache.addServiceForQuerying(U1, mResolveInfo1, serviceInfo2); + + testServicesCache.getAllServices(U1); + verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L)); + + reset(testServicesCache); + + testServicesCache.invalidateCache(U0); + testServicesCache.invalidateCache(U1); + + // There is a bug to return the same info from the cache for different users. Make sure it + // will return the different info from the cache for different users. + Collection<RegisteredServicesCache.ServiceInfo<TestServiceType>> serviceInfos; + serviceInfos = testServicesCache.getAllServices(U0); + // Make sure the service info is retrieved from the cache for U0. + verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo1), eq(1000L)); + for (RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo : serviceInfos) { + assertThat(serviceInfo.componentInfo.applicationInfo.uid).isEqualTo(UID1); + } + + serviceInfos = testServicesCache.getAllServices(U1); + // Make sure the service info is retrieved from the cache for U1. + verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo2), eq(2000L)); + for (RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo : serviceInfos) { + assertThat(serviceInfo.componentInfo.applicationInfo.uid).isEqualTo(u1uid); + } + } + + @Test + public void testUpdateServiceInfoIntoCachesWhenPackageInfoNotFound() throws Exception { + PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */); + when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName), + anyInt(), eq(U0))).thenReturn(packageInfo1); + + TestRegisteredServicesCache testServicesCache = spy( + new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */)); + final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo( + mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(), + 1000L /* lastUpdateTime */); + testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1); + + testServicesCache.getAllServices(U0); + verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L)); + + reset(testServicesCache); + reset(mMockPackageManager); + + doThrow(new SecurityException("")).when(mMockPackageManager).getPackageInfoAsUser( + eq(mResolveInfo1.serviceInfo.packageName), anyInt(), eq(U0)); + + testServicesCache.invalidateCache(U0); + testServicesCache.getAllServices(U0); + verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), anyLong()); + } + + @Test + public void testUpdateServiceInfoIntoCachesWhenTheApplicationHasBeenUpdated() throws Exception { + PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */); + when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName), + anyInt(), eq(U0))).thenReturn(packageInfo1); + + TestRegisteredServicesCache testServicesCache = spy( + new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */)); + final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo( + mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(), + 1000L /* lastUpdateTime */); + testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1); + + testServicesCache.getAllServices(U0); + verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L)); + + reset(testServicesCache); + reset(mMockPackageManager); + + PackageInfo packageInfo2 = createPackageInfo(2000L /* lastUpdateTime */); + when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName), + anyInt(), eq(U0))).thenReturn(packageInfo2); + + testServicesCache.invalidateCache(U0); + testServicesCache.getAllServices(U0); + verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(2000L)); + } + + @Test + public void testClearServiceInfoCachesAfterTimeout() throws Exception { + PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */); + when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName), + anyInt(), eq(U0))).thenReturn(packageInfo1); + + TestRegisteredServicesCache testServicesCache = spy( + new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */)); + final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo( + mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(), + 1000L /* lastUpdateTime */); + testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1); + + // Immediately invoke run on the Runnable posted to the handler + doAnswer(invocation -> { + Message message = invocation.getArgument(0); + message.getCallback().run(); + return true; + }).when(mMockBackgroundHandler).sendMessageAtTime(any(Message.class), anyLong()); + + testServicesCache.getAllServices(U0); + verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L)); + verify(mMockBackgroundHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong()); + + reset(testServicesCache); + + testServicesCache.invalidateCache(U0); + testServicesCache.getAllServices(U0); + verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L)); + } + + private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo( + TestServiceType type, int uid, ComponentName componentName, long lastUpdateTime) { + final ComponentInfo info = new ComponentInfo(); + info.applicationInfo = new ApplicationInfo(); + info.applicationInfo.uid = uid; + return new RegisteredServicesCache.ServiceInfo<>(type, info, componentName, lastUpdateTime); + } + + private void addServiceInfoIntoResolveInfo(ResolveInfo resolveInfo, String packageName, + String serviceName) { + final ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.packageName = packageName; + serviceInfo.name = serviceName; + resolveInfo.serviceInfo = serviceInfo; + } + + private PackageInfo createPackageInfo(long lastUpdateTime) { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.lastUpdateTime = lastUpdateTime; + return packageInfo; + } + + /** + * Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing + */ + public class TestRegisteredServicesCache extends RegisteredServicesCache<TestServiceType> { + static final String SERVICE_INTERFACE = "RegisteredServicesCacheUnitTest"; + static final String SERVICE_META_DATA = "RegisteredServicesCacheUnitTest"; + static final String ATTRIBUTES_NAME = "test"; + private SparseArray<Map<ResolveInfo, ServiceInfo<TestServiceType>>> mServices = + new SparseArray<>(); + + public TestRegisteredServicesCache(Injector<TestServiceType> injector, + XmlSerializerAndParser<TestServiceType> serializerAndParser) { + super(injector, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, + serializerAndParser); + } + + @Override + public TestServiceType parseServiceAttributes(Resources res, String packageName, + AttributeSet attrs) { + return null; + } + + @Override + protected List<ResolveInfo> queryIntentServices(int userId) { + Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId, + new HashMap<ResolveInfo, ServiceInfo<TestServiceType>>()); + return new ArrayList<>(map.keySet()); + } + + void addServiceForQuerying(int userId, ResolveInfo resolveInfo, + ServiceInfo<TestServiceType> serviceInfo) { + Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId); + if (map == null) { + map = new HashMap<>(); + mServices.put(userId, map); + } + map.put(resolveInfo, serviceInfo); + } + + void clearServicesForQuerying() { + mServices.clear(); + } + + @Override + protected ServiceInfo<TestServiceType> parseServiceInfo(ResolveInfo resolveInfo, + long lastUpdateTime) throws XmlPullParserException, IOException { + int size = mServices.size(); + for (int i = 0; i < size; i++) { + Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i); + ServiceInfo<TestServiceType> serviceInfo = map.get(resolveInfo); + if (serviceInfo != null) { + return serviceInfo; + } + } + throw new IllegalArgumentException("Unexpected service " + resolveInfo); + } + + @Override + public void onUserRemoved(int userId) { + super.onUserRemoved(userId); + } + } +} diff --git a/core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java b/core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java index 8b513cb996b5..524e35535f03 100644 --- a/core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java +++ b/core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java @@ -136,9 +136,11 @@ public class SystemFeaturesCacheTest { SystemFeaturesCache cache = new SystemFeaturesCache(features); SystemFeaturesCache.clearInstance(); + assertThat(SystemFeaturesCache.hasInstance()).isFalse(); assertThrows(IllegalStateException.class, () -> SystemFeaturesCache.getInstance()); SystemFeaturesCache.setInstance(cache); + assertThat(SystemFeaturesCache.hasInstance()).isTrue(); assertThat(SystemFeaturesCache.getInstance()).isEqualTo(cache); assertThrows( @@ -149,6 +151,7 @@ public class SystemFeaturesCacheTest { @Test public void testSingletonAutomaticallySetWithFeatureEnabled() { assumeTrue(android.content.pm.Flags.cacheSdkSystemFeatures()); + assertThat(SystemFeaturesCache.hasInstance()).isTrue(); assertThat(SystemFeaturesCache.getInstance()).isNotNull(); } diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java index de5f0ffbe23f..34650be331d3 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java +++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java @@ -264,7 +264,7 @@ public class DisplayManagerGlobalTest { /* isEventFilterExplicit */ true); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); waitForHandler(); - Mockito.verifyZeroInteractions(mDisplayListener); + Mockito.verifyNoMoreInteractions(mDisplayListener); mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, ALL_DISPLAY_EVENTS @@ -272,7 +272,7 @@ public class DisplayManagerGlobalTest { /* isEventFilterExplicit */ true); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); waitForHandler(); - Mockito.verifyZeroInteractions(mDisplayListener); + Mockito.verifyNoMoreInteractions(mDisplayListener); mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, ALL_DISPLAY_EVENTS @@ -280,7 +280,7 @@ public class DisplayManagerGlobalTest { /* isEventFilterExplicit */ true); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); waitForHandler(); - Mockito.verifyZeroInteractions(mDisplayListener); + Mockito.verifyNoMoreInteractions(mDisplayListener); } @Test diff --git a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java index 46f22cec4213..504786111efe 100644 --- a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java +++ b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java @@ -20,8 +20,8 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertSame; import static junit.framework.Assert.fail; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import android.Manifest.permission; import android.content.Context; diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index 3e6520106ab0..bb059108d4b6 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -29,6 +29,8 @@ import android.util.Log; import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -416,4 +418,63 @@ public class ParcelTest { int binderEndPos = pA.dataPosition(); assertTrue(pA.hasBinders(binderStartPos, binderEndPos - binderStartPos)); } + + private static final byte[] TEST_DATA = new byte[] {4, 8, 15, 16, 23, 42}; + + // Allow for some Parcel overhead + private static final int TEST_DATA_LENGTH = TEST_DATA.length + 100; + + @Test + public void testMarshall_ByteBuffer_wrapped() { + ByteBuffer bb = ByteBuffer.allocate(TEST_DATA_LENGTH); + testMarshall_ByteBuffer(bb); + } + + @Test + public void testMarshall_DirectByteBuffer() { + ByteBuffer bb = ByteBuffer.allocateDirect(TEST_DATA_LENGTH); + testMarshall_ByteBuffer(bb); + } + + private void testMarshall_ByteBuffer(ByteBuffer bb) { + // Ensure that Parcel respects the starting offset by not starting at 0 + bb.position(1); + bb.mark(); + + // Parcel test data, then marshall into the ByteBuffer + Parcel p1 = Parcel.obtain(); + p1.writeByteArray(TEST_DATA); + p1.marshall(bb); + p1.recycle(); + + assertTrue(bb.position() > 1); + bb.reset(); + + // Unmarshall test data into a new Parcel + Parcel p2 = Parcel.obtain(); + bb.reset(); + p2.unmarshall(bb); + assertTrue(bb.position() > 1); + p2.setDataPosition(0); + byte[] marshalled = p2.marshall(); + + bb.reset(); + for (int i = 0; i < TEST_DATA.length; i++) { + assertEquals(bb.get(), marshalled[i]); + } + + byte[] testDataCopy = new byte[TEST_DATA.length]; + p2.setDataPosition(0); + p2.readByteArray(testDataCopy); + for (int i = 0; i < TEST_DATA.length; i++) { + assertEquals(TEST_DATA[i], testDataCopy[i]); + } + + // Test that overflowing the buffer throws an exception + bb.reset(); + // Leave certainly not enough room for the test data + bb.limit(bb.position() + TEST_DATA.length - 1); + assertThrows(BufferOverflowException.class, () -> p2.marshall(bb)); + p2.recycle(); + } } diff --git a/core/tests/coretests/src/android/provider/FontsContractTest.java b/core/tests/coretests/src/android/provider/FontsContractTest.java index 21a22057d7c8..f5209952508c 100644 --- a/core/tests/coretests/src/android/provider/FontsContractTest.java +++ b/core/tests/coretests/src/android/provider/FontsContractTest.java @@ -21,8 +21,8 @@ import static android.provider.FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABL import static android.provider.FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY; import static android.provider.FontsContract.Columns.RESULT_CODE_OK; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java index 8ac9292390b0..50cd4c00c2ce 100644 --- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java @@ -29,7 +29,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.os.CancellationSignal; @@ -230,7 +230,7 @@ public class PendingInsetsControllerTest { InsetsController secondController = mock(InsetsController.class); mPendingInsetsController.replayAndAttach(secondController); verify(mReplayedController).show(eq(systemBars())); - verifyZeroInteractions(secondController); + verifyNoMoreInteractions(secondController); } @Test diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java index e5ad5613af2d..341947c900c5 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java @@ -29,8 +29,8 @@ import static junit.framework.Assert.assertNull; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyObject; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -79,7 +79,7 @@ public class AccessibilityCacheTest { @Before public void setUp() { mAccessibilityNodeRefresher = mock(AccessibilityCache.AccessibilityNodeRefresher.class); - when(mAccessibilityNodeRefresher.refreshNode(anyObject(), anyBoolean())).thenReturn(true); + when(mAccessibilityNodeRefresher.refreshNode(any(), anyBoolean())).thenReturn(true); mAccessibilityCache = new AccessibilityCache(mAccessibilityNodeRefresher); } @@ -854,7 +854,7 @@ public class AccessibilityCacheTest { try { assertEventTypeClearsNode(eventType, false); verify(mAccessibilityNodeRefresher, never()) - .refreshNode(anyObject(), anyBoolean()); + .refreshNode(any(), anyBoolean()); } catch (Throwable e) { throw new AssertionError( "Failed for eventType: " + AccessibilityEvent.eventTypeToString( diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java index eb482f2e0aa5..f811d8efedeb 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java @@ -20,7 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.MockitoAnnotations.initMocks; import android.os.Bundle; @@ -74,7 +74,7 @@ public class AccessibilityInteractionClientTest { MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null); assertEquals("Node got lost along the way", nodeFromConnection, node); - verifyZeroInteractions(mMockCache); + verifyNoMoreInteractions(mMockCache); } @Test diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java index 82e34275c66c..551357c6ba7a 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java @@ -34,8 +34,8 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java index 5f89f9c14793..8bbe81da512b 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java @@ -27,7 +27,7 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.content.ComponentName; import android.content.ContentCaptureOptions; @@ -130,7 +130,7 @@ public class MainContentCaptureSessionTest { mTestableLooper.processAllMessages(); assertThat(session.mContentProtectionEventProcessor).isNull(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); } @Test @@ -151,7 +151,7 @@ public class MainContentCaptureSessionTest { mTestableLooper.processAllMessages(); assertThat(session.mContentProtectionEventProcessor).isNull(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); } @Test @@ -172,7 +172,7 @@ public class MainContentCaptureSessionTest { mTestableLooper.processAllMessages(); assertThat(session.mContentProtectionEventProcessor).isNull(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); } @Test @@ -197,7 +197,7 @@ public class MainContentCaptureSessionTest { session.sendEvent(EVENT); mTestableLooper.processAllMessages(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isNull(); } @@ -227,7 +227,7 @@ public class MainContentCaptureSessionTest { session.sendEvent(EVENT); mTestableLooper.processAllMessages(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isNotNull(); assertThat(session.mEvents).containsExactly(EVENT); } @@ -255,7 +255,7 @@ public class MainContentCaptureSessionTest { session.sendEvent(EVENT); mTestableLooper.processAllMessages(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isNull(); } @@ -272,8 +272,8 @@ public class MainContentCaptureSessionTest { session.flush(REASON); mTestableLooper.processAllMessages(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); - verifyZeroInteractions(mMockContentCaptureDirectManager); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentCaptureDirectManager); assertThat(session.mEvents).containsExactly(EVENT); } @@ -289,8 +289,8 @@ public class MainContentCaptureSessionTest { session.flush(REASON); mTestableLooper.processAllMessages(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); - verifyZeroInteractions(mMockContentCaptureDirectManager); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentCaptureDirectManager); assertThat(session.mEvents).containsExactly(EVENT); } @@ -307,7 +307,7 @@ public class MainContentCaptureSessionTest { session.flush(REASON); mTestableLooper.processAllMessages(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isEmpty(); assertEventFlushedContentCapture(options); } @@ -325,7 +325,7 @@ public class MainContentCaptureSessionTest { session.flush(REASON); mTestableLooper.processAllMessages(); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isEmpty(); assertEventFlushedContentCapture(options); } @@ -339,7 +339,7 @@ public class MainContentCaptureSessionTest { mTestableLooper.processAllMessages(); verify(mMockSystemServerInterface).finishSession(anyInt()); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); assertThat(session.mDirectServiceInterface).isNull(); assertThat(session.mContentProtectionEventProcessor).isNull(); } @@ -352,8 +352,8 @@ public class MainContentCaptureSessionTest { session.resetSession(/* newState= */ 0); mTestableLooper.processAllMessages(); - verifyZeroInteractions(mMockSystemServerInterface); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockSystemServerInterface); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); assertThat(session.mDirectServiceInterface).isNull(); assertThat(session.mContentProtectionEventProcessor).isNull(); } @@ -370,8 +370,8 @@ public class MainContentCaptureSessionTest { notifyContentCaptureEvents(session); mTestableLooper.processAllMessages(); - verifyZeroInteractions(mMockContentCaptureDirectManager); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentCaptureDirectManager); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isNull(); } @@ -388,8 +388,8 @@ public class MainContentCaptureSessionTest { notifyContentCaptureEvents(session); mTestableLooper.processAllMessages(); - verifyZeroInteractions(mMockContentCaptureDirectManager); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentCaptureDirectManager); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isNull(); } @@ -407,8 +407,8 @@ public class MainContentCaptureSessionTest { notifyContentCaptureEvents(session); mTestableLooper.processAllMessages(); - verifyZeroInteractions(mMockContentCaptureDirectManager); - verifyZeroInteractions(mMockContentProtectionEventProcessor); + verifyNoMoreInteractions(mMockContentCaptureDirectManager); + verifyNoMoreInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isNull(); } diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java index ba0dbf454ad2..e75452cafdab 100644 --- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java +++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.annotation.NonNull; @@ -443,7 +442,7 @@ public class ContentProtectionEventProcessorTest { mTestLooper.dispatchAll(); verify(mMockEventBuffer, never()).clear(); verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + verifyNoMoreInteractions(mMockContentCaptureManager); } private void assertLoginDetected() throws Exception { diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java index 3eb7d9aa738a..34ccc8bb5179 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -51,7 +51,7 @@ import static junit.framework.Assert.assertTrue; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.is; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; diff --git a/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java b/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java index b61d86819c17..3570c2e0ace0 100644 --- a/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java +++ b/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java @@ -33,7 +33,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import android.app.Activity; import android.app.Instrumentation; @@ -159,7 +158,7 @@ public class TextViewReceiveContentTest { ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build(); mDefaultReceiver.onReceiveContent(mEditText, payload); - verifyZeroInteractions(ic.mMock); + verifyNoMoreInteractions(ic.mMock); } @Test @@ -180,19 +179,19 @@ public class TextViewReceiveContentTest { ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD).build(); mDefaultReceiver.onReceiveContent(mEditText, payload); - verifyZeroInteractions(ic.mMock); + verifyNoMoreInteractions(ic.mMock); payload = new ContentInfo.Builder(clip, SOURCE_INPUT_METHOD).build(); mDefaultReceiver.onReceiveContent(mEditText, payload); - verifyZeroInteractions(ic.mMock); + verifyNoMoreInteractions(ic.mMock); payload = new ContentInfo.Builder(clip, SOURCE_DRAG_AND_DROP).build(); mDefaultReceiver.onReceiveContent(mEditText, payload); - verifyZeroInteractions(ic.mMock); + verifyNoMoreInteractions(ic.mMock); payload = new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build(); mDefaultReceiver.onReceiveContent(mEditText, payload); - verifyZeroInteractions(ic.mMock); + verifyNoMoreInteractions(ic.mMock); } private static class MyInputConnection extends InputConnectionWrapper { diff --git a/core/tests/coretests/src/android/window/BackTouchTrackerTest.kt b/core/tests/coretests/src/android/window/BackTouchTrackerTest.kt index 381b566018c7..ad68e385459e 100644 --- a/core/tests/coretests/src/android/window/BackTouchTrackerTest.kt +++ b/core/tests/coretests/src/android/window/BackTouchTrackerTest.kt @@ -37,7 +37,7 @@ class BackTouchTrackerTest { fun generatesProgress_onStart() { val linearTracker = linearTouchTracker() linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT) - val event = linearTracker.createStartEvent(null) + val event = linearTracker.createStartEvent() assertEquals(0f, event.progress, 0f) } diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index 215c1623a530..66524d1c1d2a 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -695,8 +695,7 @@ public class WindowOnBackInvokedDispatcherTest { /* frameTimeMillis = */ 0, /* progress = */ progress, /* triggerBack = */ false, - /* swipeEdge = */ BackEvent.EDGE_LEFT, - /* departingAnimationTarget = */ null); + /* swipeEdge = */ BackEvent.EDGE_LEFT); } private void verifyImeCallackRegistrations() throws RemoteException { diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java index 74b4de1833ea..1977ff52c7c5 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java @@ -20,6 +20,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHO import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN; import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE; import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES; +import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME; @@ -34,17 +35,16 @@ import static org.junit.Assert.fail; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.eq; +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.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; @@ -165,7 +165,7 @@ public class AccessibilityShortcutControllerTest { .thenReturn(accessibilityManager); when(mFrameworkObjectProvider.getAlertDialogBuilder(mContext)) .thenReturn(mAlertDialogBuilder); - when(mFrameworkObjectProvider.makeToastFromText(eq(mContext), anyObject(), anyInt())) + when(mFrameworkObjectProvider.makeToastFromText(eq(mContext), any(), anyInt())) .thenReturn(mToast); when(mFrameworkObjectProvider.getSystemUiContext()).thenReturn(mContext); when(mFrameworkObjectProvider.getTextToSpeech(eq(mContext), any())) @@ -179,20 +179,20 @@ public class AccessibilityShortcutControllerTest { ResolveInfo resolveInfo = mock(ResolveInfo.class); resolveInfo.serviceInfo = mock(ServiceInfo.class); resolveInfo.serviceInfo.applicationInfo = mApplicationInfo; - when(resolveInfo.loadLabel(anyObject())).thenReturn(PACKAGE_NAME_STRING); + when(resolveInfo.loadLabel(any())).thenReturn(PACKAGE_NAME_STRING); when(mServiceInfo.getResolveInfo()).thenReturn(resolveInfo); when(mServiceInfo.getComponentName()) .thenReturn(ComponentName.unflattenFromString(SERVICE_NAME_STRING)); when(mServiceInfo.loadSummary(any())).thenReturn(SERVICE_NAME_SUMMARY); - when(mAlertDialogBuilder.setTitle(anyObject())).thenReturn(mAlertDialogBuilder); + when(mAlertDialogBuilder.setTitle(any())).thenReturn(mAlertDialogBuilder); when(mAlertDialogBuilder.setCancelable(anyBoolean())).thenReturn(mAlertDialogBuilder); - when(mAlertDialogBuilder.setMessage(anyObject())).thenReturn(mAlertDialogBuilder); - when(mAlertDialogBuilder.setPositiveButton(anyInt(), anyObject())) + when(mAlertDialogBuilder.setMessage(any())).thenReturn(mAlertDialogBuilder); + when(mAlertDialogBuilder.setPositiveButton(anyInt(), any())) .thenReturn(mAlertDialogBuilder); - when(mAlertDialogBuilder.setNegativeButton(anyInt(), anyObject())) + when(mAlertDialogBuilder.setNegativeButton(anyInt(), any())) .thenReturn(mAlertDialogBuilder); - when(mAlertDialogBuilder.setOnCancelListener(anyObject())).thenReturn(mAlertDialogBuilder); + when(mAlertDialogBuilder.setOnCancelListener(any())).thenReturn(mAlertDialogBuilder); when(mAlertDialogBuilder.create()).thenReturn(mAlertDialog); mLayoutParams.privateFlags = 0; @@ -348,7 +348,7 @@ public class AccessibilityShortcutControllerTest { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); AccessibilityShortcutController accessibilityShortcutController = getController(); accessibilityShortcutController.performAccessibilityShortcut(); - verify(mVibrator).vibrate(aryEq(VIBRATOR_PATTERN_LONG), eq(-1), anyObject()); + verify(mVibrator).vibrate(aryEq(VIBRATOR_PATTERN_LONG), eq(-1), any()); } @Test @@ -522,7 +522,7 @@ public class AccessibilityShortcutControllerTest { AccessibilityShortcutController.DialogStatus.SHOWN); getController().performAccessibilityShortcut(); - verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog); + verifyNoMoreInteractions(mAlertDialogBuilder, mAlertDialog); verify(mToast).show(); verify(mAccessibilityManagerService).performAccessibilityShortcut( Display.DEFAULT_DISPLAY, HARDWARE, null); @@ -615,7 +615,7 @@ public class AccessibilityShortcutControllerTest { AccessibilityShortcutController.DialogStatus.SHOWN); getController().performAccessibilityShortcut(); - verifyZeroInteractions(mToast); + verifyNoMoreInteractions(mToast); verify(mAccessibilityManagerService).performAccessibilityShortcut( Display.DEFAULT_DISPLAY, HARDWARE, null); } @@ -632,7 +632,7 @@ public class AccessibilityShortcutControllerTest { AccessibilityShortcutController.DialogStatus.SHOWN); getController().performAccessibilityShortcut(); - verifyZeroInteractions(mToast); + verifyNoMoreInteractions(mToast); verify(mAccessibilityManagerService).performAccessibilityShortcut( Display.DEFAULT_DISPLAY, HARDWARE, null); } @@ -715,6 +715,25 @@ public class AccessibilityShortcutControllerTest { verify(mRingtone, times(0)).play(); } + @Test + public void onUserSetupComplete_noEnabledServices_blankHardwareSetting() throws Exception { + AccessibilityShortcutController controller = getController(); + configureValidShortcutService(); + // Shortcut setting should be cleared on user setup + Settings.Secure.putStringForUser( + mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null, 0); + when(mAccessibilityManagerService + .getEnabledAccessibilityServiceList(anyInt(), eq(0))) + .thenReturn(Collections.emptyList()); + Settings.Secure.putInt(mContentResolver, USER_SETUP_COMPLETE, 1); + + controller.mUserSetupCompleteObserver.onChange(true); + + final String shortcut = Settings.Secure.getStringForUser( + mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, 0); + assertThat(shortcut).isEqualTo(""); + } + private void configureNoShortcutService() throws Exception { when(mAccessibilityManagerService .getAccessibilityShortcutTargets(HARDWARE)) diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index d21ab44d251d..15e746cb13b6 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -87,6 +87,10 @@ public class ResolverActivityTest { private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry .getInstrumentation().getTargetContext().getUser(); + private static final int WORK_USER_ID = PERSONAL_USER_HANDLE.getIdentifier() + 1; + private static final int CLONE_USER_ID = PERSONAL_USER_HANDLE.getIdentifier() + 2; + private static final int PRIVATE_USER_ID = PERSONAL_USER_HANDLE.getIdentifier() + 3; + @Rule public ActivityTestRule<ResolverWrapperActivity> mActivityRule = new ActivityTestRule<>(ResolverWrapperActivity.class, false, @@ -247,7 +251,7 @@ public class ResolverActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10, + createResolvedComponentsForTestWithOtherProfile(2, WORK_USER_ID, PERSONAL_USER_HANDLE); markWorkProfileUserAvailable(); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, @@ -270,7 +274,7 @@ public class ResolverActivityTest { }; // Make a stable copy of the components as the original list may be modified List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10, + createResolvedComponentsForTestWithOtherProfile(2, WORK_USER_ID, PERSONAL_USER_HANDLE); // We pick the first one as there is another one in the work profile side onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))) @@ -444,7 +448,7 @@ public class ResolverActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10, + createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID, PERSONAL_USER_HANDLE); markWorkProfileUserAvailable(); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, @@ -456,7 +460,7 @@ public class ResolverActivityTest { final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); waitForIdle(); - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0)); + assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE)); // The work list adapter must be populated in advance before tapping the other tab assertThat(activity.getWorkListAdapter().getCount(), is(4)); } @@ -466,7 +470,7 @@ public class ResolverActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, + createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID, PERSONAL_USER_HANDLE); markWorkProfileUserAvailable(); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, @@ -478,7 +482,7 @@ public class ResolverActivityTest { waitForIdle(); onView(withText(R.string.resolver_work_tab)).perform(click()); - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); + assertThat(activity.getCurrentUserHandle().getIdentifier(), is(WORK_USER_ID)); assertThat(activity.getWorkListAdapter().getCount(), is(4)); } @@ -498,7 +502,7 @@ public class ResolverActivityTest { waitForIdle(); onView(withText(R.string.resolver_work_tab)).perform(click()); - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); + assertThat(activity.getCurrentUserHandle().getIdentifier(), is(WORK_USER_ID)); assertThat(activity.getPersonalListAdapter().getCount(), is(2)); } @@ -508,7 +512,7 @@ public class ResolverActivityTest { ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, + createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID, PERSONAL_USER_HANDLE); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, sOverrides.workProfileUserHandle); @@ -530,7 +534,7 @@ public class ResolverActivityTest { ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, + createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID, PERSONAL_USER_HANDLE); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, sOverrides.workProfileUserHandle); @@ -633,7 +637,7 @@ public class ResolverActivityTest { ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10, + createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID, PERSONAL_USER_HANDLE); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, sOverrides.workProfileUserHandle); @@ -669,7 +673,7 @@ public class ResolverActivityTest { markWorkProfileUserAvailable(); int workProfileTargets = 4; List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, + createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID, PERSONAL_USER_HANDLE); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets, @@ -697,7 +701,7 @@ public class ResolverActivityTest { markWorkProfileUserAvailable(); int workProfileTargets = 4; List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, + createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID, PERSONAL_USER_HANDLE); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets, @@ -844,7 +848,7 @@ public class ResolverActivityTest { public void testAutolaunch_singleTarget_withWorkProfileAndTabbedViewOff_noAutolaunch() { ResolverActivity.ENABLE_TABBED_VIEW = false; List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10, + createResolvedComponentsForTestWithOtherProfile(2, WORK_USER_ID, PERSONAL_USER_HANDLE); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), @@ -898,7 +902,7 @@ public class ResolverActivityTest { markWorkProfileUserAvailable(); int workProfileTargets = 4; List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10, + createResolvedComponentsForTestWithOtherProfile(2, WORK_USER_ID, PERSONAL_USER_HANDLE); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets, @@ -1376,15 +1380,16 @@ public class ResolverActivityTest { } private void markWorkProfileUserAvailable() { - ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10); + ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(WORK_USER_ID); } private void markCloneProfileUserAvailable() { - ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11); + ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(CLONE_USER_ID); } private void markPrivateProfileUserAvailable() { - ResolverWrapperActivity.sOverrides.privateProfileUserHandle = UserHandle.of(12); + ResolverWrapperActivity.sOverrides.privateProfileUserHandle = + UserHandle.of(PRIVATE_USER_ID); } private void setTabOwnerUserHandleForLaunch(UserHandle tabOwnerUserHandleForLaunch) { diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java index 90f5c24e13a1..cdf506c941e9 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java @@ -22,10 +22,10 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.mockito.ArgumentMatchers.intThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyString; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java index 4604b01d1bd2..050a68a89d6f 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java @@ -112,8 +112,8 @@ public class ResolverWrapperActivity extends ResolverActivity { @Override protected ResolverListController createListController(UserHandle userHandle) { - if (userHandle == UserHandle.SYSTEM) { - when(sOverrides.resolverListController.getUserHandle()).thenReturn(UserHandle.SYSTEM); + if (userHandle == getUser()) { + when(sOverrides.resolverListController.getUserHandle()).thenReturn(getUser()); return sOverrides.resolverListController; } if (isLaunchedInSingleUserMode()) { diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java index 17fe15c94294..21ef391ee9de 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java @@ -28,7 +28,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.annotation.EnforcePermission; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; @@ -207,7 +207,7 @@ public final class DeviceStateManagerGlobalTest { mService.setSupportedStates(List.of(OTHER_DEVICE_STATE)); mService.setBaseState(OTHER_DEVICE_STATE); - verifyZeroInteractions(callback); + verifyNoMoreInteractions(callback); } @Test diff --git a/core/tests/mockingcoretests/src/android/util/TimingsTraceLogTest.java b/core/tests/mockingcoretests/src/android/util/TimingsTraceLogTest.java index 8de919681006..7c6046223698 100644 --- a/core/tests/mockingcoretests/src/android/util/TimingsTraceLogTest.java +++ b/core/tests/mockingcoretests/src/android/util/TimingsTraceLogTest.java @@ -22,10 +22,10 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.contains; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.matches; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.matches; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml index 3b4014867ef7..f25ceb14fb10 100644 --- a/data/etc/preinstalled-packages-platform.xml +++ b/data/etc/preinstalled-packages-platform.xml @@ -139,4 +139,10 @@ to pre-existing users, but cannot uninstall pre-existing system packages from pr <install-in-user-type package="com.android.multiuser"> <install-in user-type="FULL" /> </install-in-user-type> + + <!-- PrivateSpace App, only install in private profile --> + <install-in-user-type package="com.android.privatespace"> + <install-in user-type="android.os.usertype.profile.PRIVATE" /> + </install-in-user-type> + </config> diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index cd5a54c2fd3f..a4ba2b398deb 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -755,6 +755,9 @@ public final class Bitmap implements Parcelable { if (b != null) { b.setPremultiplied(mRequestPremultiplied); b.mDensity = mDensity; + if (hasGainmap()) { + b.setGainmap(getGainmap().asShared()); + } } return b; } @@ -767,7 +770,8 @@ public final class Bitmap implements Parcelable { */ @NonNull public Bitmap asShared() { - if (nativeIsBackedByAshmem(mNativePtr) && nativeIsImmutable(mNativePtr)) { + if (nativeIsBackedByAshmem(mNativePtr) && nativeIsImmutable(mNativePtr) + && (!hasGainmap() || getGainmap().asShared() == getGainmap())) { return this; } Bitmap shared = createAshmemBitmap(); @@ -2091,7 +2095,7 @@ public final class Bitmap implements Parcelable { */ public void setGainmap(@Nullable Gainmap gainmap) { checkRecycled("Bitmap is recycled"); - mGainmap = null; + mGainmap = gainmap; nativeSetGainmap(mNativePtr, gainmap == null ? 0 : gainmap.mNativePtr); } diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java index 7fc13db85659..2417a1270bc5 100644 --- a/graphics/java/android/graphics/Gainmap.java +++ b/graphics/java/android/graphics/Gainmap.java @@ -161,6 +161,18 @@ public final class Gainmap implements Parcelable { } /** + * @hide + */ + public Gainmap asShared() { + final Bitmap sharedContents = mGainmapContents.asShared(); + if (sharedContents == mGainmapContents) { + return this; + } else { + return new Gainmap(sharedContents, nCreateCopy(mNativePtr)); + } + } + + /** * @return Returns the image data of the gainmap represented as a Bitmap. This is represented * as a Bitmap for broad API compatibility, however certain aspects of the Bitmap are ignored * such as {@link Bitmap#getColorSpace()} or {@link Bitmap#getGainmap()} as they are not diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 592c7cdd070c..b6a1501831c0 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -114,7 +114,7 @@ flag { name: "enable_shell_top_task_tracking" namespace: "multitasking" description: "Enables tracking top tasks from the shell" - bug: "342627272" + bug: "346588978" metadata { purpose: PURPOSE_BUGFIX } diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml index 4daaf9c6b57f..225303b2d942 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml @@ -103,4 +103,35 @@ </LinearLayout> + <!-- Menu option to move a bubble to fullscreen; only visible if bubble anything is enabled. --> + <LinearLayout + android:id="@+id/bubble_manage_menu_fullscreen_container" + android:background="@drawable/bubble_manage_menu_row" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + android:minHeight="@dimen/bubble_menu_item_height" + android:gravity="center_vertical" + android:paddingStart="@dimen/bubble_menu_padding" + android:paddingEnd="@dimen/bubble_menu_padding" + android:orientation="horizontal"> + + <ImageView + android:id="@+id/bubble_manage_menu_fullscreen_icon" + android:layout_width="@dimen/bubble_menu_icon_size" + android:layout_height="@dimen/bubble_menu_icon_size" + android:src="@drawable/desktop_mode_ic_handle_menu_fullscreen" + android:tint="@color/bubbles_icon_tint"/> + + <TextView + android:id="@+id/bubble_manage_menu_fullscreen_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:text="@string/bubble_fullscreen_text" + android:textColor="@androidprv:color/materialColorOnSurface" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" /> + + </LinearLayout> + </LinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml index e11babe5cb0e..bfaa40771894 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml @@ -22,8 +22,8 @@ android:layout_height="wrap_content" android:clipChildren="false" android:clipToPadding="false" - android:paddingBottom="@dimen/desktop_mode_handle_menu_pill_elevation" - android:paddingEnd="@dimen/desktop_mode_handle_menu_pill_elevation" + android:paddingBottom="@dimen/desktop_mode_handle_menu_pill_elevation_padding" + android:paddingEnd="@dimen/desktop_mode_handle_menu_pill_elevation_padding" android:orientation="vertical"> <LinearLayout @@ -43,8 +43,7 @@ android:layout_height="@dimen/desktop_mode_handle_menu_icon_radius" android:layout_marginStart="10dp" android:layout_marginEnd="12dp" - android:contentDescription="@string/app_icon_text" - android:importantForAccessibility="no"/> + android:contentDescription="@string/app_icon_text" /> <com.android.wm.shell.windowdecor.MarqueedTextView android:id="@+id/application_name" @@ -142,7 +141,6 @@ android:contentDescription="@string/screenshot_text" android:text="@string/screenshot_text" android:src="@drawable/desktop_mode_ic_handle_menu_screenshot" - android:importantForAccessibility="no" style="@style/DesktopModeHandleMenuActionButton"/> <com.android.wm.shell.windowdecor.HandleMenuActionButton @@ -150,7 +148,6 @@ android:contentDescription="@string/new_window_text" android:text="@string/new_window_text" android:src="@drawable/desktop_mode_ic_handle_menu_new_window" - android:importantForAccessibility="no" style="@style/DesktopModeHandleMenuActionButton"/> <com.android.wm.shell.windowdecor.HandleMenuActionButton @@ -158,7 +155,6 @@ android:contentDescription="@string/manage_windows_text" android:text="@string/manage_windows_text" android:src="@drawable/desktop_mode_ic_handle_menu_manage_windows" - android:importantForAccessibility="no" style="@style/DesktopModeHandleMenuActionButton"/> <com.android.wm.shell.windowdecor.HandleMenuActionButton @@ -166,7 +162,6 @@ android:contentDescription="@string/change_aspect_ratio_text" android:text="@string/change_aspect_ratio_text" android:src="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio" - android:importantForAccessibility="no" style="@style/DesktopModeHandleMenuActionButton"/> </LinearLayout> @@ -186,7 +181,6 @@ android:text="@string/open_in_browser_text" android:src="@drawable/desktop_mode_ic_handle_menu_open_in_browser" style="@style/DesktopModeHandleMenuActionButton" - android:importantForAccessibility="no" android:layout_width="0dp" android:layout_weight="1"/> diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml index de38e6fc2330..35e7de0e7c1e 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml @@ -22,8 +22,7 @@ android:layout_height="match_parent" android:gravity="start|center_vertical" android:paddingHorizontal="16dp" - android:clickable="true" - android:focusable="true" + android:importantForAccessibility="yes" android:orientation="horizontal" android:background="?android:attr/selectableItemBackground"> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 9ebbf71138b0..e1bf6638a9b2 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -291,6 +291,9 @@ <!-- Corner radius for expanded view drop target --> <dimen name="bubble_bar_expanded_view_drop_target_corner">28dp</dimen> <dimen name="bubble_bar_expanded_view_drop_target_padding">24dp</dimen> + <dimen name="bubble_bar_expanded_view_drop_target_padding_top">60dp</dimen> + <dimen name="bubble_bar_expanded_view_drop_target_padding_bottom">24dp</dimen> + <dimen name="bubble_bar_expanded_view_drop_target_padding_horizontal">48dp</dimen> <!-- Width of the box around bottom center of the screen where drag only leads to dismiss --> <dimen name="bubble_bar_dismiss_zone_width">192dp</dimen> <!-- Height of the box around bottom center of the screen where drag only leads to dismiss --> @@ -525,17 +528,21 @@ <!-- The radius of the Maximize menu shadow. --> <dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen> - <!-- The width of the handle menu in desktop mode. --> - <dimen name="desktop_mode_handle_menu_width">216dp</dimen> + <!-- The width of the handle menu in desktop mode plus the 2dp added for padding to account for + pill elevation. --> + <dimen name="desktop_mode_handle_menu_width">218dp</dimen> - <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each, - additional actions pill 208dp, plus 2dp spacing between them plus 4dp top padding. - 52*3 + 52*4 + (4-1)*2 + 4 = 374 --> - <dimen name="desktop_mode_handle_menu_height">374dp</dimen> + <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each plus + additional actions pill 208dp plus 2dp spacing between them plus 4dp top padding + plus 2dp bottom padding: 52*3 + 52*4 + (4-1)*2 + 4 + 2 = 376 --> + <dimen name="desktop_mode_handle_menu_height">376dp</dimen> <!-- The elevation set on the handle menu pills. --> <dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen> + <!-- The padding added to account for the handle menu's pills' elevation. --> + <dimen name="desktop_mode_handle_menu_pill_elevation_padding">2dp</dimen> + <!-- The height of the handle menu's "App Info" pill in desktop mode. --> <dimen name="desktop_mode_handle_menu_app_info_pill_height">52dp</dimen> diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml index 637b47ab3ace..5f1db83d7acb 100644 --- a/libs/WindowManager/Shell/res/values/styles.xml +++ b/libs/WindowManager/Shell/res/values/styles.xml @@ -45,6 +45,7 @@ <item name="android:layout_height">52dp</item> <item name="android:textColor">@androidprv:color/materialColorOnSurface</item> <item name="android:drawableTint">@androidprv:color/materialColorOnSurface</item> + <item name="android:importantForAccessibility">no</item> </style> <style name="DesktopModeHandleMenuActionButtonImage"> diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt index 9ea0532f9450..529203f7ded2 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt @@ -19,13 +19,11 @@ package com.android.wm.shell.shared.desktopmode import android.Manifest.permission.SYSTEM_ALERT_WINDOW import android.app.TaskInfo import android.content.Context -import android.content.pm.ActivityInfo -import android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED -import android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION -import android.content.pm.ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS import android.content.pm.PackageManager import android.window.DesktopModeFlags import com.android.internal.R +import com.android.internal.policy.DesktopModeCompatUtils +import java.util.function.Supplier /** * Class to decide whether to apply app compat policies in desktop mode. @@ -37,9 +35,11 @@ class DesktopModeCompatPolicy(private val context: Context) { private val pkgManager: PackageManager get() = context.getPackageManager() private val defaultHomePackage: String? - get() = pkgManager.getHomeActivities(ArrayList())?.packageName + get() = defaultHomePackageSupplier?.get() + ?: pkgManager.getHomeActivities(ArrayList())?.packageName private val packageInfoCache = mutableMapOf<String, Boolean>() + var defaultHomePackageSupplier: Supplier<String?>? = null /** * If the top activity should be exempt from desktop windowing and forced back to fullscreen. @@ -49,33 +49,28 @@ class DesktopModeCompatPolicy(private val context: Context) { */ fun isTopActivityExemptFromDesktopWindowing(task: TaskInfo) = isTopActivityExemptFromDesktopWindowing(task.baseActivity?.packageName, - task.numActivities, task.isTopActivityNoDisplay, task.isActivityStackTransparent) + task.numActivities, task.isTopActivityNoDisplay, task.isActivityStackTransparent, + task.userId) - fun isTopActivityExemptFromDesktopWindowing(packageName: String?, - numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) = + fun isTopActivityExemptFromDesktopWindowing( + packageName: String?, + numActivities: Int, + isTopActivityNoDisplay: Boolean, + isActivityStackTransparent: Boolean, + userId: Int + ) = DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue && ((isSystemUiTask(packageName) || isPartOfDefaultHomePackageOrNoHomeAvailable(packageName) || (isTransparentTask(isActivityStackTransparent, numActivities) && - hasFullscreenTransparentPermission(packageName))) && + hasFullscreenTransparentPermission(packageName, userId))) && !isTopActivityNoDisplay) - /** - * Whether the caption insets should be excluded from configuration for system to handle. - * - * The treatment is enabled when all the of the following is true: - * * Any flags to forcibly consume caption insets are enabled. - * * Top activity have configuration coupled with insets. - * * Task is not resizeable or [ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS] - * is enabled. - */ + /** @see DesktopModeCompatUtils.shouldExcludeCaptionFromAppBounds */ fun shouldExcludeCaptionFromAppBounds(taskInfo: TaskInfo): Boolean = - DesktopModeFlags.EXCLUDE_CAPTION_FROM_APP_BOUNDS.isTrue - && isAnyForceConsumptionFlagsEnabled() - && taskInfo.topActivityInfo?.let { - isInsetsCoupledWithConfiguration(it) && (!taskInfo.isResizeable || it.isChangeEnabled( - OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS - )) + taskInfo.topActivityInfo?.let { + DesktopModeCompatUtils.shouldExcludeCaptionFromAppBounds(it, taskInfo.isResizeable, + taskInfo.appCompatTaskInfo.hasOptOutEdgeToEdge()) } ?: false /** @@ -91,16 +86,17 @@ class DesktopModeCompatPolicy(private val context: Context) { private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage // Checks if the app for the given package has the SYSTEM_ALERT_WINDOW permission. - private fun hasFullscreenTransparentPermission(packageName: String?): Boolean { + private fun hasFullscreenTransparentPermission(packageName: String?, userId: Int): Boolean { if (DesktopModeFlags.ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS.isTrue) { if (packageName == null) { return false } - return packageInfoCache.getOrPut(packageName) { + return packageInfoCache.getOrPut("$userId@$packageName") { try { - val packageInfo = pkgManager.getPackageInfo( + val packageInfo = pkgManager.getPackageInfoAsUser( packageName, - PackageManager.GET_PERMISSIONS + PackageManager.GET_PERMISSIONS, + userId ) packageInfo?.requestedPermissions?.contains(SYSTEM_ALERT_WINDOW) == true } catch (e: PackageManager.NameNotFoundException) { @@ -118,12 +114,4 @@ class DesktopModeCompatPolicy(private val context: Context) { */ private fun isPartOfDefaultHomePackageOrNoHomeAvailable(packageName: String?) = defaultHomePackage == null || (packageName != null && packageName == defaultHomePackage) - - private fun isAnyForceConsumptionFlagsEnabled(): Boolean = - DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isTrue - || DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue - - private fun isInsetsCoupledWithConfiguration(info: ActivityInfo): Boolean = - !(info.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION) - || info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)) } 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 53dede6bd227..7f8cfaeb9c03 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 @@ -448,7 +448,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(); if (!shouldDispatchToAnimator && mActiveCallback != null) { mCurrentTracker.updateStartLocation(); - tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null)); + tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent()); if (mBackNavigationInfo != null && !isAppProgressGenerationAllowed()) { tryPilferPointers(); } @@ -604,7 +604,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback(); // App is handling back animation. Cancel system animation latency tracking. cancelLatencyTracking(); - tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null)); + tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent()); if (!isAppProgressGenerationAllowed()) { tryPilferPointers(); } @@ -1041,7 +1041,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont () -> mShellExecutor.execute(this::onBackAnimationFinished)); if (mApps.length >= 1) { - BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]); + BackMotionEvent startEvent = mCurrentTracker.createStartEvent(); dispatchOnBackStarted(mActiveCallback, startEvent); if (startEvent.getSwipeEdge() == EDGE_NONE) { // TODO(b/373544911): onBackStarted is dispatched here so that 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 efc952644f0b..912de813cf59 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 @@ -2644,7 +2644,8 @@ public class BubbleController implements ConfigurationChangeListener, } private void moveBubbleToFullscreen(String key) { - // TODO b/388858013: convert the bubble to full screen + Bubble b = mBubbleData.getBubbleInStackWithKey(key); + mBubbleTransitions.startDraggedBubbleIconToFullscreen(b); } private boolean isDeviceLocked() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 8ac9230c36c3..cbd1e9671825 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -606,6 +606,10 @@ public class BubbleExpandedView extends LinearLayout { updateManageButtonIfExists(); } + public float getCornerRadius() { + return mCornerRadius; + } + /** * Updates the size and visuals of the pointer if {@link #mPointerView} is initialized. * Does nothing otherwise. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 70340d7032b4..03d6b0a8075d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -103,7 +103,9 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider { private int mManageButtonHeight; private int mOverflowHeight; private int mMinimumFlyoutWidthLargeScreen; - private int mBubbleBarExpandedViewDropTargetPadding; + private int mBarExpViewDropTargetPaddingTop; + private int mBarExpViewDropTargetPaddingBottom; + private int mBarExpViewDropTargetPaddingHorizontal; private PointF mRestingStackPosition; @@ -173,8 +175,12 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider { res.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width), mPositionRect.width() - 2 * mExpandedViewPadding ); - mBubbleBarExpandedViewDropTargetPadding = res.getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_drop_target_padding); + mBarExpViewDropTargetPaddingTop = res.getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_drop_target_padding_top); + mBarExpViewDropTargetPaddingBottom = res.getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_drop_target_padding_bottom); + mBarExpViewDropTargetPaddingHorizontal = res.getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_drop_target_padding_horizontal); if (mShowingInBubbleBar) { mExpandedViewLargeScreenWidth = mExpandedViewBubbleBarWidth; @@ -986,8 +992,15 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider { public Rect getBubbleBarExpandedViewDropTargetBounds(boolean onLeft) { Rect bounds = new Rect(); getBubbleBarExpandedViewBounds(onLeft, false, bounds); - bounds.inset(mBubbleBarExpandedViewDropTargetPadding, - mBubbleBarExpandedViewDropTargetPadding); + // Drop target bounds are based on expanded view bounds with some padding added + int leftPadding = onLeft ? 0 : mBarExpViewDropTargetPaddingHorizontal; + int rightPadding = onLeft ? mBarExpViewDropTargetPaddingHorizontal : 0; + bounds.inset( + leftPadding, + mBarExpViewDropTargetPaddingTop, + rightPadding, + mBarExpViewDropTargetPaddingBottom + ); return bounds; } } 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 92724178cf84..dd5a23aae7f9 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 @@ -91,6 +91,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; 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.DeviceConfig; import com.android.wm.shell.shared.bubbles.DismissView; import com.android.wm.shell.shared.bubbles.RelativeTouchListener; @@ -1319,7 +1320,7 @@ public class BubbleStackView extends FrameLayout mBubbleContainer.bringToFront(); } - // TODO: Create ManageMenuView and move setup / animations there + // TODO (b/402196554) : Create ManageMenuView and move setup / animations there private void setUpManageMenu() { if (mManageMenu != null) { removeView(mManageMenu); @@ -1377,6 +1378,22 @@ public class BubbleStackView extends FrameLayout mManageSettingsIcon = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_icon); mManageSettingsText = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_name); + View fullscreenView = mManageMenu.findViewById( + R.id.bubble_manage_menu_fullscreen_container); + if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) { + fullscreenView.setVisibility(VISIBLE); + fullscreenView.setOnClickListener( + view -> { + showManageMenu(false /* show */); + BubbleExpandedView expandedView = getExpandedView(); + if (expandedView != null && expandedView.getTaskView() != null) { + expandedView.getTaskView().moveToFullscreen(); + } + }); + } else { + fullscreenView.setVisibility(GONE); + } + // The menu itself should respect locale direction so the icons are on the correct side. mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE); addView(mManageMenu); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java index 51a5b12edb84..c1841c707a2f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java @@ -113,6 +113,11 @@ public class BubbleTransitions { return convert; } + /** Starts a transition that converts a dragged bubble icon to a full screen task. */ + public BubbleTransition startDraggedBubbleIconToFullscreen(Bubble bubble) { + return new DraggedBubbleIconToFullscreen(bubble); + } + /** * Plucks the task-surface out of an ancestor view while making the view invisible. This helper * attempts to do this seamlessly (ie. view becomes invisible in sync with task reparent). @@ -607,8 +612,7 @@ public class BubbleTransitions { mTaskLeash = taskChg.getLeash(); mRootLeash = info.getRoot(0).getLeash(); - SurfaceControl dest = - mBubble.getBubbleBarExpandedView().getViewRootImpl().getSurfaceControl(); + SurfaceControl dest = getExpandedView(mBubble).getViewRootImpl().getSurfaceControl(); final Runnable onPlucked = () -> { // Need to remove the taskview AFTER applying the startTransaction because // it isn't synchronized. @@ -618,12 +622,12 @@ public class BubbleTransitions { mBubbleData.setExpanded(false /* expanded */); }; if (dest != null) { - pluck(mTaskLeash, mBubble.getBubbleBarExpandedView(), dest, + pluck(mTaskLeash, getExpandedView(mBubble), dest, taskChg.getStartAbsBounds().left - info.getRoot(0).getOffset().x, taskChg.getStartAbsBounds().top - info.getRoot(0).getOffset().y, - mBubble.getBubbleBarExpandedView().getCornerRadius(), startTransaction, + getCornerRadius(mBubble), startTransaction, onPlucked); - mBubble.getBubbleBarExpandedView().post(() -> mTransitions.dispatchTransition( + getExpandedView(mBubble).post(() -> mTransitions.dispatchTransition( mTransition, info, startTransaction, finishTransaction, finishCallback, null)); } else { @@ -644,5 +648,130 @@ public class BubbleTransitions { t.reparent(mTaskLeash, mRootLeash); t.apply(); } + + private View getExpandedView(@NonNull Bubble bubble) { + if (bubble.getBubbleBarExpandedView() != null) { + return bubble.getBubbleBarExpandedView(); + } + return bubble.getExpandedView(); + } + + private float getCornerRadius(@NonNull Bubble bubble) { + if (bubble.getBubbleBarExpandedView() != null) { + return bubble.getBubbleBarExpandedView().getCornerRadius(); + } + return bubble.getExpandedView().getCornerRadius(); + } + } + + /** + * A transition that converts a dragged bubble icon to a full screen window. + * + * <p>This transition assumes that the bubble is invisible so it is simply sent to front. + */ + class DraggedBubbleIconToFullscreen implements Transitions.TransitionHandler, BubbleTransition { + + IBinder mTransition; + final Bubble mBubble; + + DraggedBubbleIconToFullscreen(Bubble bubble) { + mBubble = bubble; + bubble.setPreparingTransition(this); + WindowContainerToken token = bubble.getTaskView().getTaskInfo().getToken(); + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setAlwaysOnTop(token, false); + wct.setWindowingMode(token, WINDOWING_MODE_UNDEFINED); + wct.reorder(token, /* onTop= */ true); + wct.setHidden(token, false); + mTaskOrganizer.setInterceptBackPressedOnTaskRoot(token, false); + mTaskViewTransitions.enqueueExternal(bubble.getTaskView().getController(), () -> { + mTransition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct, this); + return mTransition; + }); + } + + @Override + public void skip() { + mBubble.setPreparingTransition(null); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mTransition != transition) { + return false; + } + + final TaskViewTaskController taskViewTaskController = + mBubble.getTaskView().getController(); + if (taskViewTaskController == null) { + mTaskViewTransitions.onExternalDone(transition); + finishCallback.onTransitionFinished(null); + return true; + } + + TransitionInfo.Change change = findTransitionChange(info); + if (change == null) { + Slog.w(TAG, "Expected a TaskView transition to front but didn't find " + + "one, cleaning up the task view"); + taskViewTaskController.setTaskNotFound(); + mTaskViewTransitions.onExternalDone(transition); + finishCallback.onTransitionFinished(null); + return true; + } + mRepository.remove(taskViewTaskController); + + startTransaction.apply(); + finishCallback.onTransitionFinished(null); + taskViewTaskController.notifyTaskRemovalStarted(mBubble.getTaskView().getTaskInfo()); + mTaskViewTransitions.onExternalDone(transition); + return true; + } + + private TransitionInfo.Change findTransitionChange(TransitionInfo info) { + TransitionInfo.Change result = null; + WindowContainerToken token = mBubble.getTaskView().getTaskInfo().getToken(); + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() == null) { + continue; + } + if (change.getMode() != TRANSIT_TO_FRONT) { + continue; + } + if (!token.equals(change.getTaskInfo().token)) { + continue; + } + result = change; + break; + } + return result; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + } + + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + return null; + } + + @Override + public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, + @Nullable SurfaceControl.Transaction finishTransaction) { + if (!aborted) { + return; + } + mTransition = null; + mTaskViewTransitions.onExternalDone(transition); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java index b7761ec75782..69009fd1606a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java @@ -28,9 +28,9 @@ import android.view.View; import android.view.ViewGroup; import com.android.app.animation.Interpolators; -import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; +import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; import java.util.ArrayList; @@ -263,7 +263,7 @@ class BubbleBarMenuViewController { } )); - if (Flags.enableBubbleAnything() || Flags.enableBubbleToFullscreen()) { + if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) { menuActions.add(new BubbleBarMenuView.MenuAction( Icon.createWithResource(resources, R.drawable.desktop_mode_ic_handle_menu_fullscreen), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java index c4696d5f44f4..a8e6b593f20d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java @@ -20,17 +20,14 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL; import android.annotation.BinderThread; import android.annotation.NonNull; -import android.os.RemoteException; import android.util.Slog; import android.view.SurfaceControl; -import android.view.WindowManager; import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; import android.window.WindowOrganizer; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.shared.TransactionPool; -import com.android.wm.shell.transition.LegacyTransitions; import java.util.ArrayList; @@ -87,25 +84,6 @@ public final class SyncTransactionQueue { } /** - * Queues a legacy transition to be sent serially to WM - */ - public void queue(LegacyTransitions.ILegacyTransition transition, - @WindowManager.TransitionType int type, WindowContainerTransaction wct) { - if (wct.isEmpty()) { - if (DEBUG) Slog.d(TAG, "Skip queue due to transaction change is empty"); - return; - } - SyncCallback cb = new SyncCallback(transition, type, wct); - synchronized (mQueue) { - if (DEBUG) Slog.d(TAG, "Queueing up legacy transition " + wct); - mQueue.add(cb); - if (mQueue.size() == 1) { - cb.send(); - } - } - } - - /** * Queues a sync transaction only if there are already sync transaction(s) queued or in flight. * Otherwise just returns without queueing. * @return {@code true} if queued, {@code false} if not. @@ -168,17 +146,9 @@ public final class SyncTransactionQueue { private class SyncCallback extends WindowContainerTransactionCallback { int mId = -1; final WindowContainerTransaction mWCT; - final LegacyTransitions.LegacyTransition mLegacyTransition; SyncCallback(WindowContainerTransaction wct) { mWCT = wct; - mLegacyTransition = null; - } - - SyncCallback(LegacyTransitions.ILegacyTransition legacyTransition, - @WindowManager.TransitionType int type, WindowContainerTransaction wct) { - mWCT = wct; - mLegacyTransition = new LegacyTransitions.LegacyTransition(type, legacyTransition); } // Must be sychronized on mQueue @@ -194,12 +164,7 @@ public final class SyncTransactionQueue { } if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT); try { - if (mLegacyTransition != null) { - mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(), - mLegacyTransition.getAdapter(), this, mWCT); - } else { - mId = new WindowOrganizer().applySyncTransaction(mWCT, this); - } + mId = new WindowOrganizer().applySyncTransaction(mWCT, this); } catch (RuntimeException e) { Slog.e(TAG, "Send failed", e); // Finish current sync callback immediately. @@ -228,18 +193,10 @@ public final class SyncTransactionQueue { if (DEBUG) Slog.d(TAG, "onTransactionReady id=" + mId); mQueue.remove(this); onTransactionReceived(t); - if (mLegacyTransition != null) { - try { - mLegacyTransition.getSyncCallback().onTransactionReady(mId, t); - } catch (RemoteException e) { - Slog.e(TAG, "Error sending callback to legacy transition: " + mId, e); - } - } else { - ProtoLog.v(WM_SHELL, - "SyncTransactionQueue.onTransactionReady(): syncId=%d apply", id); - t.apply(); - t.close(); - } + ProtoLog.v(WM_SHELL, + "SyncTransactionQueue.onTransactionReady(): syncId=%d apply", id); + t.apply(); + t.close(); if (!mQueue.isEmpty()) { mQueue.get(0).send(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java index 453ca167557a..1128fb2259b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java @@ -86,8 +86,7 @@ public class PipDesktopState { return false; } final int displayId = mPipDisplayLayoutState.getDisplayId(); - return mDesktopUserRepositoriesOptional.get().getCurrent().isAnyDeskActive(displayId) - || isDisplayInFreeform(); + return mDesktopUserRepositoriesOptional.get().getCurrent().isAnyDeskActive(displayId); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java index 4cbb78f2dae2..d36201a4ac9e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java @@ -52,24 +52,15 @@ public class PipDoubleTapHelper { public static final int SIZE_SPEC_MAX = 1; public static final int SIZE_SPEC_CUSTOM = 2; - /** - * Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from. - * - * <p>Each double tap toggles back and forth between {@code PipSizeSpec.CUSTOM} and - * either {@code PipSizeSpec.MAX} or {@code PipSizeSpec.DEFAULT}. The choice between - * the latter two sizes is determined based on the current state of the pip screen.</p> - * - * @param mPipBoundsState current state of the pip screen - */ @PipSizeSpec - private static int getMaxOrDefaultPipSizeSpec(@NonNull PipBoundsState mPipBoundsState) { + private static int getMaxOrDefaultPipSizeSpec(@NonNull PipBoundsState pipBoundsState) { // determine the average pip screen width - int averageWidth = (mPipBoundsState.getMaxSize().x - + mPipBoundsState.getMinSize().x) / 2; + int averageWidth = (pipBoundsState.getMaxSize().x + + pipBoundsState.getMinSize().x) / 2; // If pip screen width is above average, DEFAULT is the size spec we need to // toggle to. Otherwise, we choose MAX. - return (mPipBoundsState.getBounds().width() > averageWidth) + return (pipBoundsState.getBounds().width() > averageWidth) ? SIZE_SPEC_DEFAULT : SIZE_SPEC_MAX; } @@ -77,35 +68,33 @@ public class PipDoubleTapHelper { /** * Determines the {@link PipSizeSpec} to toggle to on double tap. * - * @param mPipBoundsState current state of the pip screen + * @param pipBoundsState current state of the pip bounds * @param userResizeBounds latest user resized bounds (by pinching in/out) - * @return pip screen size to switch to */ @PipSizeSpec - public static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState, + public static int nextSizeSpec(@NonNull PipBoundsState pipBoundsState, @NonNull Rect userResizeBounds) { - // is pip screen at its maximum - boolean isScreenMax = mPipBoundsState.getBounds().width() - == mPipBoundsState.getMaxSize().x; - - // is pip screen at its normal default size - boolean isScreenDefault = (mPipBoundsState.getBounds().width() - == mPipBoundsState.getNormalBounds().width()) - && (mPipBoundsState.getBounds().height() - == mPipBoundsState.getNormalBounds().height()); + boolean isScreenMax = pipBoundsState.getBounds().width() == pipBoundsState.getMaxSize().x + && pipBoundsState.getBounds().height() == pipBoundsState.getMaxSize().y; + boolean isScreenDefault = (pipBoundsState.getBounds().width() + == pipBoundsState.getNormalBounds().width()) + && (pipBoundsState.getBounds().height() + == pipBoundsState.getNormalBounds().height()); // edge case 1 // if user hasn't resized screen yet, i.e. CUSTOM size does not exist yet // or if user has resized exactly to DEFAULT, then we just want to maximize if (isScreenDefault - && userResizeBounds.width() == mPipBoundsState.getNormalBounds().width()) { + && userResizeBounds.width() == pipBoundsState.getNormalBounds().width() + && userResizeBounds.height() == pipBoundsState.getNormalBounds().height()) { return SIZE_SPEC_MAX; } // edge case 2 - // if user has maximized, then we want to toggle to DEFAULT + // if user has resized to max, then we want to toggle to DEFAULT if (isScreenMax - && userResizeBounds.width() == mPipBoundsState.getMaxSize().x) { + && userResizeBounds.width() == pipBoundsState.getMaxSize().x + && userResizeBounds.height() == pipBoundsState.getMaxSize().y) { return SIZE_SPEC_DEFAULT; } @@ -113,9 +102,6 @@ public class PipDoubleTapHelper { if (isScreenDefault || isScreenMax) { return SIZE_SPEC_CUSTOM; } - - // if we are currently in user resized CUSTOM size state - // then we toggle either to MAX or DEFAULT depending on the current pip screen state - return getMaxOrDefaultPipSizeSpec(mPipBoundsState); + return getMaxOrDefaultPipSizeSpec(pipBoundsState); } } 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 318cdeec5bc1..f62fd819319e 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 @@ -95,6 +95,7 @@ import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopUserRepositories; +import com.android.wm.shell.desktopmode.common.DefaultHomePackageSupplier; import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; @@ -260,8 +261,14 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static DesktopModeCompatPolicy provideDesktopModeCompatPolicy(Context context) { - return new DesktopModeCompatPolicy(context); + static DesktopModeCompatPolicy provideDesktopModeCompatPolicy( + Context context, + ShellInit shellInit, + @ShellMainThread Handler mainHandler) { + final DesktopModeCompatPolicy policy = new DesktopModeCompatPolicy(context); + policy.setDefaultHomePackageSupplier(new DefaultHomePackageSupplier( + context, shellInit, mainHandler)); + return policy; } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index bc2ed3f35b45..67a4d6cf89bf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -98,6 +98,7 @@ import com.android.wm.shell.desktopmode.DesktopModeKeyGestureHandler; import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver; import com.android.wm.shell.desktopmode.DesktopModeMoveToDisplayTransitionHandler; import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger; +import com.android.wm.shell.desktopmode.DesktopPipTransitionObserver; import com.android.wm.shell.desktopmode.DesktopTaskChangeListener; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksLimiter; @@ -780,6 +781,7 @@ public abstract class WMShellModule { OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver, DesksOrganizer desksOrganizer, Optional<DesksTransitionObserver> desksTransitionObserver, + Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver, UserProfileContexts userProfileContexts, DesktopModeCompatPolicy desktopModeCompatPolicy, DragToDisplayTransitionHandler dragToDisplayTransitionHandler, @@ -823,6 +825,7 @@ public abstract class WMShellModule { overviewToDesktopTransitionObserver, desksOrganizer, desksTransitionObserver.get(), + desktopPipTransitionObserver, userProfileContexts, desktopModeCompatPolicy, dragToDisplayTransitionHandler, @@ -1225,6 +1228,7 @@ public abstract class WMShellModule { Transitions transitions, ShellTaskOrganizer shellTaskOrganizer, Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler, + Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver, Optional<BackAnimationController> backAnimationController, DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, ShellInit shellInit) { @@ -1237,6 +1241,7 @@ public abstract class WMShellModule { transitions, shellTaskOrganizer, desktopMixedTransitionHandler.get(), + desktopPipTransitionObserver, backAnimationController.get(), desktopWallpaperActivityTokenProvider, shellInit))); @@ -1258,6 +1263,19 @@ public abstract class WMShellModule { @WMSingleton @Provides + static Optional<DesktopPipTransitionObserver> provideDesktopPipTransitionObserver( + Context context + ) { + if (DesktopModeStatus.canEnterDesktopMode(context) + && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue()) { + return Optional.of( + new DesktopPipTransitionObserver()); + } + return Optional.empty(); + } + + @WMSingleton + @Provides static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler( Context context, Transitions transitions, @@ -1474,6 +1492,7 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, InputManager inputManager, + DisplayController displayController, @ShellMainThread Handler mainHandler ) { if (!DesktopModeStatus.canEnterDesktopMode(context)) { @@ -1488,6 +1507,7 @@ public abstract class WMShellModule { shellTaskOrganizer, desktopWallpaperActivityTokenProvider, inputManager, + displayController, mainHandler)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt index 904d86282c39..489e4f0aed01 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt @@ -35,9 +35,11 @@ import com.android.internal.annotations.VisibleForTesting 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.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.transition.Transitions /** Controls the display windowing mode in desktop mode */ @@ -49,6 +51,7 @@ class DesktopDisplayModeController( private val shellTaskOrganizer: ShellTaskOrganizer, private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, private val inputManager: InputManager, + private val displayController: DisplayController, @ShellMainThread private val mainHandler: Handler, ) { @@ -111,37 +114,70 @@ class DesktopDisplayModeController( transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) } + // Do not directly use this method to check the state of desktop-first mode. Check the display + // windowing mode instead. + private fun canDesktopFirstModeBeEnabledOnDefaultDisplay(): Boolean { + if (isDefaultDisplayDesktopEligible()) { + if (isExtendedDisplayEnabled() && hasExternalDisplay()) { + return true + } + if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { + if (isInClamshellMode()) { + return true + } + } + } + return false + } + @VisibleForTesting fun getTargetWindowingModeForDefaultDisplay(): Int { - if (isExtendedDisplayEnabled() && hasExternalDisplay()) { + if (canDesktopFirstModeBeEnabledOnDefaultDisplay()) { return WINDOWING_MODE_FREEFORM } - if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { - if (isInClamshellMode()) { - return WINDOWING_MODE_FREEFORM - } - return WINDOWING_MODE_FULLSCREEN - } - // If form factor-based desktop first switch is disabled, use the default display windowing - // mode here to keep the freeform mode for some form factors (e.g., FEATURE_PC). - return windowManager.getWindowingMode(DEFAULT_DISPLAY) + return if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { + WINDOWING_MODE_FULLSCREEN + } else { + // If form factor-based desktop first switch is disabled, use the default display + // windowing mode here to keep the freeform mode for some form factors (e.g., + // FEATURE_PC). + windowManager.getWindowingMode(DEFAULT_DISPLAY) + } } - // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available. - private fun isExtendedDisplayEnabled() = - 0 != + private fun isExtendedDisplayEnabled(): Boolean { + if (DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue) { + return rootTaskDisplayAreaOrganizer + .getDisplayIds() + .filter { it != DEFAULT_DISPLAY } + .any { displayId -> + displayController.getDisplay(displayId)?.let { display -> + DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display) + } ?: false + } + } + + return 0 != Settings.Global.getInt( context.contentResolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0, ) + } private fun hasExternalDisplay() = rootTaskDisplayAreaOrganizer.getDisplayIds().any { it != DEFAULT_DISPLAY } private fun isInClamshellMode() = inputManager.isInTabletMode() == InputManager.SWITCH_STATE_OFF + private fun isDefaultDisplayDesktopEligible(): Boolean { + val display = requireNotNull(displayController.getDisplay(DEFAULT_DISPLAY)) { + "Display object of DEFAULT_DISPLAY must be non-null." + } + return DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display) + } + private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt new file mode 100644 index 000000000000..efd3866e1bc4 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt @@ -0,0 +1,81 @@ +/* + * 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.wm.shell.desktopmode + +import android.app.WindowConfiguration.WINDOWING_MODE_PINNED +import android.os.IBinder +import android.window.DesktopModeFlags +import android.window.TransitionInfo +import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE + +/** + * Observer of PiP in Desktop Mode transitions. At the moment, this is specifically tracking a PiP + * transition for a task that is entering PiP via the minimize button on the caption bar. + */ +class DesktopPipTransitionObserver { + private val pendingPipTransitions = mutableMapOf<IBinder, PendingPipTransition>() + + /** Adds a pending PiP transition to be tracked. */ + fun addPendingPipTransition(transition: PendingPipTransition) { + if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) return + pendingPipTransitions[transition.token] = transition + } + + /** + * Called when any transition is ready, which may include transitions not tracked by this + * observer. + */ + fun onTransitionReady(transition: IBinder, info: TransitionInfo) { + if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) return + val pipTransition = pendingPipTransitions.remove(transition) ?: return + + logD("Desktop PiP transition ready: %s", transition) + for (change in info.changes) { + val taskInfo = change.taskInfo + if (taskInfo == null || taskInfo.taskId == -1) { + continue + } + + if ( + taskInfo.taskId == pipTransition.taskId && + taskInfo.windowingMode == WINDOWING_MODE_PINNED + ) { + logD("Desktop PiP transition was successful") + pipTransition.onSuccess() + return + } + } + logD("Change with PiP task not found in Desktop PiP transition; likely failed") + } + + /** + * Data tracked for a pending PiP transition. + * + * @property token the PiP transition that is started. + * @property taskId task id of the task entering PiP. + * @property onSuccess callback to be invoked if the PiP transition is successful. + */ + data class PendingPipTransition(val token: IBinder, val taskId: Int, val onSuccess: () -> Unit) + + private fun logD(msg: String, vararg arguments: Any?) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + private companion object { + private const val TAG = "DesktopPipTransitionObserver" + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index 8636bc1f56c2..6cb26b54e802 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -68,7 +68,6 @@ class DesktopRepository( * @property topTransparentFullscreenTaskId the task id of any current top transparent * fullscreen task launched on top of the desk. Cleared when the transparent task is closed or * sent to back. (top is at index 0). - * @property pipTaskId the task id of PiP task entered while in Desktop Mode. */ private data class Desk( val deskId: Int, @@ -81,7 +80,6 @@ class DesktopRepository( val freeformTasksInZOrder: ArrayList<Int> = ArrayList(), var fullImmersiveTaskId: Int? = null, var topTransparentFullscreenTaskId: Int? = null, - var pipTaskId: Int? = null, ) { fun deepCopy(): Desk = Desk( @@ -94,7 +92,6 @@ class DesktopRepository( freeformTasksInZOrder = ArrayList(freeformTasksInZOrder), fullImmersiveTaskId = fullImmersiveTaskId, topTransparentFullscreenTaskId = topTransparentFullscreenTaskId, - pipTaskId = pipTaskId, ) // TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't @@ -107,7 +104,6 @@ class DesktopRepository( freeformTasksInZOrder.clear() fullImmersiveTaskId = null topTransparentFullscreenTaskId = null - pipTaskId = null } } @@ -127,9 +123,6 @@ class DesktopRepository( /* Tracks last bounds of task before toggled to immersive state. */ private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>() - /* Callback for when a pending PiP transition has been aborted. */ - private var onPipAbortedCallback: ((Int, Int) -> Unit)? = null - private var desktopGestureExclusionListener: Consumer<Region>? = null private var desktopGestureExclusionExecutor: Executor? = null @@ -339,7 +332,7 @@ class DesktopRepository( val affectedDisplays = mutableSetOf<Int>() desktopData .desksSequence() - .filter { desk -> desk.displayId != excludedDeskId } + .filter { desk -> desk.deskId != excludedDeskId } .forEach { desk -> val removed = removeActiveTaskFromDesk(desk.deskId, taskId, notifyListeners = false) if (removed) { @@ -611,57 +604,6 @@ class DesktopRepository( } /** - * Set whether the given task is the Desktop-entered PiP task in this display's active desk. - * - * TODO: b/389960283 - add explicit [deskId] argument. - */ - fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) { - val activeDesk = - desktopData.getActiveDesk(displayId) - ?: error("Expected active desk in display: $displayId") - if (enterPip) { - activeDesk.pipTaskId = taskId - } else { - activeDesk.pipTaskId = - if (activeDesk.pipTaskId == taskId) null - else { - logW( - "setTaskInPip: taskId=%d did not match saved taskId=%d", - taskId, - activeDesk.pipTaskId, - ) - activeDesk.pipTaskId - } - } - } - - /** - * Returns whether the given task is the Desktop-entered PiP task in this display's active desk. - * - * TODO: b/389960283 - add explicit [deskId] argument. - */ - fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean = - desktopData.getActiveDesk(displayId)?.pipTaskId == taskId - - /** - * Saves callback to handle a pending PiP transition being aborted. - * - * TODO: b/389960283 - add explicit [deskId] argument. - */ - fun setOnPipAbortedCallback(callbackIfPipAborted: ((displayId: Int, pipTaskId: Int) -> Unit)?) { - onPipAbortedCallback = callbackIfPipAborted - } - - /** - * Invokes callback to handle a pending PiP transition with the given task id being aborted. - * - * TODO: b/389960283 - add explicit [deskId] argument. - */ - fun onPipAborted(displayId: Int, pipTaskId: Int) { - onPipAbortedCallback?.invoke(displayId, pipTaskId) - } - - /** * Set whether the given task is the full-immersive task in this display's active desk. * * TODO: b/389960283 - consider forcing callers to use [setTaskInFullImmersiveStateInDesk] with diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index c28fdcb44f5b..cfc1541d6388 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -32,8 +32,6 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.content.Context import android.content.Intent -import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.graphics.Point import android.graphics.PointF import android.graphics.Rect @@ -45,6 +43,7 @@ import android.os.IBinder import android.os.SystemProperties import android.os.UserHandle import android.util.Slog +import android.view.Display import android.view.Display.DEFAULT_DISPLAY import android.view.DragEvent import android.view.MotionEvent @@ -215,6 +214,7 @@ class DesktopTasksController( private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver, private val desksOrganizer: DesksOrganizer, private val desksTransitionObserver: DesksTransitionObserver, + private val desktopPipTransitionObserver: Optional<DesktopPipTransitionObserver>, private val userProfileContexts: UserProfileContexts, private val desktopModeCompatPolicy: DesktopModeCompatPolicy, private val dragToDisplayTransitionHandler: DragToDisplayTransitionHandler, @@ -465,6 +465,10 @@ class DesktopTasksController( /** Creates a new desk in the given display. */ fun createDesk(displayId: Int) { + if (displayId == Display.INVALID_DISPLAY) { + logW("createDesk attempt with invalid displayId", displayId) + return + } if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desksOrganizer.createDesk(displayId) { deskId -> taskRepository.addDesk(displayId = displayId, deskId = deskId) @@ -653,6 +657,7 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, dragToDesktopValueAnimator: MoveToDesktopAnimator, taskSurface: SurfaceControl, + dragInterruptedCallback: Runnable, ) { logV("startDragToDesktop taskId=%d", taskInfo.taskId) val jankConfigBuilder = @@ -668,6 +673,7 @@ class DesktopTasksController( taskInfo, dragToDesktopValueAnimator, visualIndicator, + dragInterruptedCallback, ) } @@ -788,10 +794,31 @@ class DesktopTasksController( fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) { val wct = WindowContainerTransaction() - + val taskId = taskInfo.taskId + val displayId = taskInfo.displayId + val deskId = + taskRepository.getDeskIdForTask(taskInfo.taskId) + ?: if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + logW("minimizeTask: desk not found for task: ${taskInfo.taskId}") + return + } else { + getDefaultDeskId(taskInfo.displayId) + } + val isLastTask = + if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + taskRepository.isOnlyVisibleNonClosingTaskInDesk( + taskId = taskId, + deskId = checkNotNull(deskId) { "Expected non-null deskId" }, + displayId = displayId, + ) + } else { + taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId) + } val isMinimizingToPip = DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue && - (taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false) + desktopPipTransitionObserver.isPresent && + (taskInfo.pictureInPictureParams?.isAutoEnterEnabled ?: false) + // If task is going to PiP, start a PiP transition instead of a minimize transition if (isMinimizingToPip) { val requestInfo = @@ -805,75 +832,63 @@ class DesktopTasksController( ) val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null) wct.merge(requestRes.second, true) - freeformTaskTransitionStarter.startPipTransition(wct) - taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true) - taskRepository.setOnPipAbortedCallback { displayId, taskId -> - minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!, minimizeReason) - taskRepository.setTaskInPip(displayId, taskId, enterPip = false) - } - return - } - - minimizeTaskInner(taskInfo, minimizeReason) - } - private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) { - val taskId = taskInfo.taskId - val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId) - if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { - logW("minimizeTaskInner: desk not found for task: ${taskInfo.taskId}") - return - } - val displayId = taskInfo.displayId - val wct = WindowContainerTransaction() - - snapEventHandler.removeTaskIfTiled(displayId, taskId) - val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false) - val desktopExitRunnable = - performDesktopExitCleanUp( - wct = wct, - deskId = deskId, - displayId = displayId, - willExitDesktop = willExitDesktop, - ) - // Notify immersive handler as it might need to exit immersive state. - val exitResult = - desktopImmersiveController.exitImmersiveIfApplicable( - wct = wct, - taskInfo = taskInfo, - reason = DesktopImmersiveController.ExitReason.MINIMIZED, - ) - if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { - desksOrganizer.minimizeTask( - wct = wct, - deskId = checkNotNull(deskId) { "Expected non-null deskId" }, - task = taskInfo, + desktopPipTransitionObserver.get().addPendingPipTransition( + DesktopPipTransitionObserver.PendingPipTransition( + token = freeformTaskTransitionStarter.startPipTransition(wct), + taskId = taskInfo.taskId, + onSuccess = { + onDesktopTaskEnteredPip( + taskId = taskId, + deskId = deskId, + displayId = taskInfo.displayId, + taskIsLastVisibleTaskBeforePip = isLastTask, + ) + }, + ) ) } else { - wct.reorder(taskInfo.token, /* onTop= */ false) - } - val isLastTask = + snapEventHandler.removeTaskIfTiled(displayId, taskId) + val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false) + val desktopExitRunnable = + performDesktopExitCleanUp( + wct = wct, + deskId = deskId, + displayId = displayId, + willExitDesktop = willExitDesktop, + ) + // Notify immersive handler as it might need to exit immersive state. + val exitResult = + desktopImmersiveController.exitImmersiveIfApplicable( + wct = wct, + taskInfo = taskInfo, + reason = DesktopImmersiveController.ExitReason.MINIMIZED, + ) if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { - taskRepository.isOnlyVisibleNonClosingTaskInDesk( - taskId = taskId, + desksOrganizer.minimizeTask( + wct = wct, deskId = checkNotNull(deskId) { "Expected non-null deskId" }, - displayId = displayId, + task = taskInfo, ) } else { - taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId) + wct.reorder(taskInfo.token, /* onTop= */ false) } - val transition = - freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask) - desktopTasksLimiter.ifPresent { - it.addPendingMinimizeChange( - transition = transition, - displayId = displayId, - taskId = taskId, - minimizeReason = minimizeReason, - ) + val transition = + freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask) + desktopTasksLimiter.ifPresent { + it.addPendingMinimizeChange( + transition = transition, + displayId = displayId, + taskId = taskId, + minimizeReason = minimizeReason, + ) + } + exitResult.asExit()?.runOnTransitionStart?.invoke(transition) + desktopExitRunnable?.invoke(transition) } - exitResult.asExit()?.runOnTransitionStart?.invoke(transition) - desktopExitRunnable?.invoke(transition) + taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( + doesAnyTaskRequireTaskbarRounding(displayId, taskId) + ) } /** Move a task with given `taskId` to fullscreen */ @@ -1256,8 +1271,7 @@ class DesktopTasksController( wct.reparent(task.token, displayAreaInfo.token, /* onTop= */ true) } - // TODO: b/391485148 - pass in the moving-to-desk |task| here to apply task-limit policy. - val activationRunnable = addDeskActivationChanges(destinationDeskId, wct) + val activationRunnable = addDeskActivationChanges(destinationDeskId, wct, task) if (Flags.enableDisplayFocusInShellTransitions()) { // Bring the destination display to top with includingParents=true, so that the @@ -1841,7 +1855,11 @@ class DesktopTasksController( displayId: Int, forceExitDesktop: Boolean, ): Boolean { - if (forceExitDesktop && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + if ( + forceExitDesktop && + (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue || + DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) + ) { // |forceExitDesktop| is true when the callers knows we'll exit desktop, such as when // explicitly going fullscreen, so there's no point in checking the desktop state. return true @@ -1858,6 +1876,33 @@ class DesktopTasksController( return true } + /** Potentially perform Desktop cleanup after a task successfully enters PiP. */ + @VisibleForTesting + fun onDesktopTaskEnteredPip( + taskId: Int, + deskId: Int, + displayId: Int, + taskIsLastVisibleTaskBeforePip: Boolean, + ) { + if ( + !willExitDesktop(taskId, displayId, forceExitDesktop = taskIsLastVisibleTaskBeforePip) + ) { + return + } + + val wct = WindowContainerTransaction() + val desktopExitRunnable = + performDesktopExitCleanUp( + wct = wct, + deskId = deskId, + displayId = displayId, + willExitDesktop = true, + ) + + val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) + desktopExitRunnable?.invoke(transition) + } + private fun performDesktopExitCleanupIfNeeded( taskId: Int, deskId: Int? = null, @@ -2944,6 +2989,11 @@ class DesktopTasksController( removeDesk(displayId = displayId, deskId = deskId) } + /** Removes all the available desks on all displays. */ + fun removeAllDesks() { + taskRepository.getAllDeskIds().forEach { deskId -> removeDesk(deskId) } + } + private fun removeDesk(displayId: Int, deskId: Int) { if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return logV("removeDesk deskId=%d from displayId=%d", deskId, displayId) @@ -3710,6 +3760,18 @@ class DesktopTasksController( } } + override fun removeDesk(deskId: Int) { + executeRemoteCallWithTaskPermission(controller, "removeDesk") { c -> + c.removeDesk(deskId) + } + } + + override fun removeAllDesks() { + executeRemoteCallWithTaskPermission(controller, "removeAllDesks") { c -> + c.removeAllDesks() + } + } + override fun activateDesk(deskId: Int, remoteTransition: RemoteTransition?) { executeRemoteCallWithTaskPermission(controller, "activateDesk") { c -> c.activateDesk(deskId, remoteTransition) @@ -3773,8 +3835,8 @@ class DesktopTasksController( } } - override fun removeDesktop(displayId: Int) { - executeRemoteCallWithTaskPermission(controller, "removeDesktop") { c -> + override fun removeDefaultDeskInDisplay(displayId: Int) { + executeRemoteCallWithTaskPermission(controller, "removeDefaultDeskInDisplay") { c -> c.removeDefaultDeskInDisplay(displayId) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index 7dabeb7c9d15..df4d18f8c803 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -23,7 +23,6 @@ import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_OPEN -import android.view.WindowManager.TRANSIT_PIP import android.view.WindowManager.TRANSIT_TO_BACK import android.window.DesktopExperienceFlags import android.window.DesktopModeFlags @@ -38,11 +37,12 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktop import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil +import com.android.wm.shell.shared.TransitionUtil.isClosingMode +import com.android.wm.shell.shared.TransitionUtil.isOpeningMode import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions -import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP -import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP +import java.util.Optional /** * A [Transitions.TransitionObserver] that observes shell transitions and updates the @@ -55,6 +55,7 @@ class DesktopTasksTransitionObserver( private val transitions: Transitions, private val shellTaskOrganizer: ShellTaskOrganizer, private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler, + private val desktopPipTransitionObserver: Optional<DesktopPipTransitionObserver>, private val backAnimationController: BackAnimationController, private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, shellInit: ShellInit, @@ -63,8 +64,6 @@ class DesktopTasksTransitionObserver( data class CloseWallpaperTransition(val transition: IBinder, val displayId: Int) private var transitionToCloseWallpaper: CloseWallpaperTransition? = null - /* Pending PiP transition and its associated display id and task id. */ - private var pendingPipTransitionAndPipTask: Triple<IBinder, Int, Int>? = null private var currentProfileId: Int init { @@ -98,33 +97,7 @@ class DesktopTasksTransitionObserver( removeTaskIfNeeded(info) } removeWallpaperOnLastTaskClosingIfNeeded(transition, info) - - val desktopRepository = desktopUserRepositories.getProfile(currentProfileId) - info.changes.forEach { change -> - change.taskInfo?.let { taskInfo -> - if ( - DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue && - desktopRepository.isTaskMinimizedPipInDisplay( - taskInfo.displayId, - taskInfo.taskId, - ) - ) { - when (info.type) { - TRANSIT_PIP -> - pendingPipTransitionAndPipTask = - Triple(transition, taskInfo.displayId, taskInfo.taskId) - - TRANSIT_EXIT_PIP, - TRANSIT_REMOVE_PIP -> - desktopRepository.setTaskInPip( - taskInfo.displayId, - taskInfo.taskId, - enterPip = false, - ) - } - } - } - } + desktopPipTransitionObserver.ifPresent { it.onTransitionReady(transition, info) } } private fun removeTaskIfNeeded(info: TransitionInfo) { @@ -299,18 +272,6 @@ class DesktopTasksTransitionObserver( } } transitionToCloseWallpaper = null - } else if (pendingPipTransitionAndPipTask?.first == transition) { - val desktopRepository = desktopUserRepositories.getProfile(currentProfileId) - if (aborted) { - pendingPipTransitionAndPipTask?.let { - desktopRepository.onPipAborted( - /*displayId=*/ it.second, - /* taskId=*/ it.third, - ) - } - } - desktopRepository.setOnPipAbortedCallback(null) - pendingPipTransitionAndPipTask = null } } @@ -345,18 +306,29 @@ class DesktopTasksTransitionObserver( } private fun updateTopTransparentFullscreenTaskId(info: TransitionInfo) { - info.changes.forEach { change -> - change.taskInfo?.let { task -> - val desktopRepository = desktopUserRepositories.getProfile(task.userId) - val displayId = task.displayId - // Clear `topTransparentFullscreenTask` information from repository if task - // is closed or sent to back. - if ( - TransitionUtil.isClosingMode(change.mode) && - task.taskId == - desktopRepository.getTopTransparentFullscreenTaskId(displayId) - ) { - desktopRepository.clearTopTransparentFullscreenTaskId(displayId) + run forEachLoop@{ + info.changes.forEach { change -> + change.taskInfo?.let { task -> + val desktopRepository = desktopUserRepositories.getProfile(task.userId) + val displayId = task.displayId + val transparentTaskId = + desktopRepository.getTopTransparentFullscreenTaskId(displayId) + if (transparentTaskId == null) return@forEachLoop + val changeMode = change.mode + val taskId = task.taskId + val isTopTransparentFullscreenTaskClosing = + taskId == transparentTaskId && isClosingMode(changeMode) + val isNonTopTransparentFullscreenTaskOpening = + taskId != transparentTaskId && isOpeningMode(changeMode) + // Clear `topTransparentFullscreenTask` information from repository if task + // is closed, sent to back or if a different task is opened, brought to front. + if ( + isTopTransparentFullscreenTaskClosing || + isNonTopTransparentFullscreenTaskOpening + ) { + desktopRepository.clearTopTransparentFullscreenTaskId(displayId) + return@forEachLoop + } } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index 24b2e4879546..c6f74728fd81 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -2,6 +2,7 @@ package com.android.wm.shell.desktopmode import android.animation.Animator import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet import android.animation.RectEvaluator import android.animation.ValueAnimator import android.app.ActivityManager.RunningTaskInfo @@ -23,6 +24,7 @@ import android.os.IBinder import android.os.SystemClock import android.os.SystemProperties import android.os.UserHandle +import android.view.Choreographer import android.view.SurfaceControl import android.view.SurfaceControl.Transaction import android.view.WindowManager.TRANSIT_CLOSE @@ -48,6 +50,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKT import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil +import com.android.wm.shell.shared.animation.Interpolators import com.android.wm.shell.shared.animation.PhysicsAnimator import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT @@ -122,6 +125,7 @@ sealed class DragToDesktopTransitionHandler( taskInfo: RunningTaskInfo, dragToDesktopAnimator: MoveToDesktopAnimator, visualIndicator: DesktopModeVisualIndicator?, + dragCancelCallback: Runnable, ) { if (inProgress) { logV("Drag to desktop transition already in progress.") @@ -168,6 +172,7 @@ sealed class DragToDesktopTransitionHandler( startTransitionToken = startTransitionToken, otherSplitTask = otherTask, visualIndicator = visualIndicator, + dragCancelCallback = dragCancelCallback, ) } else { TransitionState.FromFullscreen( @@ -175,6 +180,7 @@ sealed class DragToDesktopTransitionHandler( dragAnimator = dragToDesktopAnimator, startTransitionToken = startTransitionToken, visualIndicator = visualIndicator, + dragCancelCallback = dragCancelCallback, ) } } @@ -203,8 +209,9 @@ sealed class DragToDesktopTransitionHandler( } if (state.startInterrupted) { logV("finishDragToDesktop: start was interrupted, returning") - // We should only have interrupted the start transition after receiving a cancel/end - // request, let that existing request play out and just return here. + // If start was interrupted we've either already requested a cancel/end transition - so + // we should let that request play out, or we're cancelling the drag-to-desktop + // transition altogether, so just return here. return null } state.endTransitionToken = @@ -221,6 +228,7 @@ sealed class DragToDesktopTransitionHandler( */ fun cancelDragToDesktopTransition(cancelState: CancelState) { if (!inProgress) { + logV("cancelDragToDesktop: not in progress, returning") // Don't attempt to cancel a drag to desktop transition since there is no transition in // progress which means that the drag to desktop transition was never successfully // started. @@ -228,14 +236,17 @@ sealed class DragToDesktopTransitionHandler( } val state = requireTransitionState() if (state.startAborted) { + logV("cancelDragToDesktop: start was aborted, clearing state") // Don't attempt to cancel the drag-to-desktop since the start transition didn't // succeed as expected. Just reset the state as if nothing happened. clearState() return } if (state.startInterrupted) { - // We should only have interrupted the start transition after receiving a cancel/end - // request, let that existing request play out and just return here. + logV("cancelDragToDesktop: start was interrupted, returning") + // If start was interrupted we've either already requested a cancel/end transition - so + // we should let that request play out, or we're cancelling the drag-to-desktop + // transition altogether, so just return here. return } state.cancelState = cancelState @@ -706,11 +717,7 @@ sealed class DragToDesktopTransitionHandler( // end-transition, or if the end-transition is running on its own, then just wait until that // finishes instead. If we've merged the cancel-transition we've finished the // start-transition and won't reach this code. - if ( - mergeTarget == state.startTransitionToken && - isCancelOrEndTransitionRequested(state) && - !state.mergedEndTransition - ) { + if (mergeTarget == state.startTransitionToken && !state.mergedEndTransition) { interruptStartTransition(state) } } @@ -722,9 +729,23 @@ sealed class DragToDesktopTransitionHandler( if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue) { return } - logV("interruptStartTransition") - state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null) - state.dragAnimator.cancelAnimator() + if (isCancelOrEndTransitionRequested(state)) { + logV("interruptStartTransition, bookend requested -> finish start transition") + // Finish the start-drag transition, we will finish the overall transition properly when + // receiving #startAnimation for Cancel/End. + state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null) + state.dragAnimator.cancelAnimator() + } else { + logV("interruptStartTransition, bookend not requested -> animate to Home") + // Animate to Home, and then finish the start-drag transition. Since there is no other + // (end/cancel) transition requested that will be the end of the overall transition. + state.dragAnimator.cancelAnimator() + state.dragCancelCallback?.run() + createInterruptToHomeAnimator(transactionSupplier.get(), state) { + state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null) + clearState() + } + } state.activeCancelAnimation?.removeAllListeners() state.activeCancelAnimation?.cancel() state.activeCancelAnimation = null @@ -738,6 +759,46 @@ sealed class DragToDesktopTransitionHandler( .onActionCancel(LatencyTracker.ACTION_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG) } + private fun createInterruptToHomeAnimator( + transaction: Transaction, + state: TransitionState, + endCallback: Runnable, + ) { + val homeLeash = state.homeChange?.leash ?: error("Expected home leash to be non-null") + val draggedTaskLeash = + state.draggedTaskChange?.leash ?: error("Expected dragged leash to be non-null") + val homeAnimator = createInterruptAlphaAnimator(transaction, homeLeash, toShow = true) + val draggedTaskAnimator = + createInterruptAlphaAnimator(transaction, draggedTaskLeash, toShow = false) + val animatorSet = AnimatorSet() + animatorSet.playTogether(homeAnimator, draggedTaskAnimator) + animatorSet.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + endCallback.run() + } + } + ) + animatorSet.start() + } + + private fun createInterruptAlphaAnimator( + transaction: Transaction, + leash: SurfaceControl, + toShow: Boolean, + ) = + ValueAnimator.ofFloat(if (toShow) 0f else 1f, if (toShow) 1f else 0f).apply { + transaction.show(leash) + duration = DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS + interpolator = Interpolators.LINEAR + addUpdateListener { animation -> + transaction + .setAlpha(leash, animation.animatedValue as Float) + .setFrameTimeline(Choreographer.getInstance().vsyncId) + .apply() + } + } + protected open fun setupEndDragToDesktop( info: TransitionInfo, startTransaction: SurfaceControl.Transaction, @@ -1060,6 +1121,7 @@ sealed class DragToDesktopTransitionHandler( abstract var endTransitionToken: IBinder? abstract var mergedEndTransition: Boolean abstract var activeCancelAnimation: Animator? + abstract var dragCancelCallback: Runnable? data class FromFullscreen( override val draggedTaskId: Int, @@ -1079,6 +1141,7 @@ sealed class DragToDesktopTransitionHandler( override var endTransitionToken: IBinder? = null, override var mergedEndTransition: Boolean = false, override var activeCancelAnimation: Animator? = null, + override var dragCancelCallback: Runnable? = null, var otherRootChanges: MutableList<Change> = mutableListOf(), ) : TransitionState() @@ -1100,6 +1163,7 @@ sealed class DragToDesktopTransitionHandler( override var endTransitionToken: IBinder? = null, override var mergedEndTransition: Boolean = false, override var activeCancelAnimation: Animator? = null, + override var dragCancelCallback: Runnable? = null, var splitRootChange: Change? = null, var otherSplitTask: Int, ) : TransitionState() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index 44f7e16e98c3..5f7fbd9843d4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -35,6 +35,12 @@ interface IDesktopMode { /** Activates the desk whose ID is `deskId` on whatever display it currently exists on. */ oneway void activateDesk(int deskId, in RemoteTransition remoteTransition); + /** Removes the desk with the given `deskId`. */ + oneway void removeDesk(int deskId); + + /** Removes all the available desks on all displays. */ + oneway void removeAllDesks(); + /** Show apps on the desktop on the given display */ void showDesktopApps(int displayId, in RemoteTransition remoteTransition); @@ -64,8 +70,11 @@ interface IDesktopMode { in @nullable RemoteTransition remoteTransition, in @nullable IMoveToDesktopCallback callback); - /** Remove desktop on the given display */ - oneway void removeDesktop(int displayId); + /** + * Removes the default desktop on the given display. + * @deprecated with multi-desks, we should use `removeDesk()`. + */ + oneway void removeDefaultDeskInDisplay(int displayId); /** Move a task with given `taskId` to external display */ void moveToExternalDisplay(int taskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt new file mode 100644 index 000000000000..8ce624e103ef --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt @@ -0,0 +1,65 @@ +/* + * 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.wm.shell.desktopmode.common + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Handler +import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.sysui.ShellInit +import java.util.function.Supplier + +/** + * This supplies the package name of default home in an efficient way. The query to package manager + * only executes on initialization and when the preferred activity (e.g. default home) is changed. + */ +class DefaultHomePackageSupplier( + private val context: Context, + shellInit: ShellInit, + @ShellMainThread private val mainHandler: Handler, +) : BroadcastReceiver(), Supplier<String?> { + + private var defaultHomePackage: String? = null + + init { + shellInit.addInitCallback({ onInit() }, this) + } + + private fun onInit() { + context.registerReceiver( + this, + IntentFilter(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED), + null /* broadcastPermission */, + mainHandler, + ) + } + + private fun updateDefaultHomePackage(): String? { + defaultHomePackage = context.packageManager.getHomeActivities(ArrayList())?.packageName + return defaultHomePackage + } + + override fun onReceive(contxt: Context?, intent: Intent?) { + updateDefaultHomePackage() + } + + override fun get(): String? { + return defaultHomePackage ?: updateDefaultHomePackage() + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java index f89ba0a168d1..0bf2ea61b0a4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java @@ -174,7 +174,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs SurfaceControl.Transaction finishT) { mTaskChangeListener.ifPresent(listener -> listener.onTaskChanging(change.getTaskInfo())); mWindowDecorViewModel.onTaskChanging( - change.getTaskInfo(), change.getLeash(), startT, finishT); + change.getTaskInfo(), change.getLeash(), startT, finishT, change.getMode()); } private void onToFrontTransitionReady( @@ -184,7 +184,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs mTaskChangeListener.ifPresent( listener -> listener.onTaskMovingToFront(change.getTaskInfo())); mWindowDecorViewModel.onTaskChanging( - change.getTaskInfo(), change.getLeash(), startT, finishT); + change.getTaskInfo(), change.getLeash(), startT, finishT, change.getMode()); } private void onToBackTransitionReady( @@ -194,7 +194,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs mTaskChangeListener.ifPresent( listener -> listener.onTaskMovingToBack(change.getTaskInfo())); mWindowDecorViewModel.onTaskChanging( - change.getTaskInfo(), change.getLeash(), startT, finishT); + change.getTaskInfo(), change.getLeash(), startT, finishT, change.getMode()); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index f81f330e50c4..a02a51f92b1b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -180,10 +180,17 @@ public class PipScheduler implements PipTransitionState.PipTransitionStateChange return; } WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setBounds(pipTaskToken, toBounds); if (configAtEnd) { wct.deferConfigToTransitionEnd(pipTaskToken); + + if (mPipBoundsState.getBounds().width() == toBounds.width() + && mPipBoundsState.getBounds().height() == toBounds.height()) { + // TODO (b/393159816): Config-at-End causes a flicker without size change. + // If PiP size isn't changing enforce a minimal one-pixel change as a workaround. + --toBounds.bottom; + } } + wct.setBounds(pipTaskToken, toBounds); mPipTransitionController.startResizeTransition(wct, duration); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 6fdfecaf15d5..d1bc450c3c4d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -934,6 +934,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha } // the size to toggle to after a double tap + mPipBoundsState.setNormalBounds(getAdjustedNormalBounds()); int nextSize = PipDoubleTapHelper .nextSizeSpec(mPipBoundsState, getUserResizeBounds()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 75c09829e551..a7cba76ea91f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -123,6 +123,7 @@ import android.view.SurfaceControl; import android.view.WindowManager; import android.widget.Toast; import android.window.DesktopExperienceFlags; +import android.window.DesktopModeFlags; import android.window.DisplayAreaInfo; import android.window.RemoteTransition; import android.window.TransitionInfo; @@ -675,7 +676,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!enteredSplitSelect) { return null; } - if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) { + if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue() + && !DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) { mTaskOrganizer.applyTransaction(wct); return null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java index 34d1011bac0e..f652e3149d9e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java @@ -85,8 +85,8 @@ class WindowlessSnapshotWindowCreator { final ActivityManager.TaskDescription taskDescription = SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo); - final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, wlw.mChildSurface, - taskDescription.getBackgroundColor(), snapshot.hasImeSurface(), + final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, rootSurface, + wlw.mChildSurface, taskDescription.getBackgroundColor(), snapshot.hasImeSurface(), runningTaskInfo.topActivityType, removeExecutor, taskId, mStartingWindowRecordManager); mStartingWindowRecordManager.addRecord(taskId, record); @@ -96,14 +96,16 @@ class WindowlessSnapshotWindowCreator { private class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord { private SurfaceControlViewHost mViewHost; private SurfaceControl mChildSurface; + private SurfaceControl mRootSurface; private final boolean mHasImeSurface; - SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl childSurface, - int bgColor, boolean hasImeSurface, int activityType, + SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl rootSurface, + SurfaceControl childSurface, int bgColor, boolean hasImeSurface, int activityType, ShellExecutor removeExecutor, int id, StartingSurfaceDrawer.StartingWindowRecordManager recordManager) { super(activityType, removeExecutor, id, recordManager); mViewHost = viewHost; + mRootSurface = rootSurface; mChildSurface = childSurface; mBGColor = bgColor; mHasImeSurface = hasImeSurface; @@ -145,6 +147,10 @@ class WindowlessSnapshotWindowCreator { mTransactionPool.release(t); mChildSurface = null; } + if (mRootSurface != null && mRootSurface.isValid()) { + mRootSurface.release(); + } + mRootSurface = null; if (mViewHost != null) { mViewHost.release(); mViewHost = null; 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 f5aaaad93229..ce98b03b77a6 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 @@ -137,8 +137,7 @@ public class DefaultSurfaceAnimator { if (mClipRect != null) { boolean needCrop = false; mAnimClipRect.set(mClipRect); - if (transformation.hasClipRect() - && com.android.window.flags.Flags.respectAnimationClip()) { + if (transformation.hasClipRect()) { mAnimClipRect.intersectUnchecked(transformation.getClipRect()); needCrop = true; } 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 938885cc1684..23dfb41d52c1 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 @@ -18,6 +18,7 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.view.Display.DEFAULT_DISPLAY; +import static android.window.DesktopModeFlags.ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP; @@ -50,6 +51,7 @@ public class HomeTransitionObserver implements TransitionObserver, private @NonNull final Context mContext; private @NonNull final ShellExecutor mMainExecutor; + private IBinder mPendingStartDragTransition; private Boolean mPendingHomeVisibilityUpdate; public HomeTransitionObserver(@NonNull Context context, @@ -63,31 +65,42 @@ public class HomeTransitionObserver implements TransitionObserver, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction) { - if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) { - handleTransitionReadyWithBubbleAnything(info); - } else { - handleTransitionReady(info); + Boolean homeVisibilityUpdate = getHomeVisibilityUpdate(info); + + if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) { + // Do not apply at the start of desktop drag as that updates launcher UI visibility. + // Store the value and apply with a next transition or when cancelling the + // desktop-drag transition. + storePendingHomeVisibilityUpdate(transition, homeVisibilityUpdate); + return; + } + + if (BubbleAnythingFlagHelper.enableBubbleToFullscreen() + && info.getType() == TRANSIT_CONVERT_TO_BUBBLE + && homeVisibilityUpdate == null) { + // We are converting to bubble and we did not get a change to home visibility in this + // transition. Apply the value from start of drag. + homeVisibilityUpdate = mPendingHomeVisibilityUpdate; + } + + if (homeVisibilityUpdate != null) { + mPendingHomeVisibilityUpdate = null; + mPendingStartDragTransition = null; + notifyHomeVisibilityChanged(homeVisibilityUpdate); } } - private void handleTransitionReady(@NonNull TransitionInfo info) { - for (TransitionInfo.Change change : info.getChanges()) { - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (taskInfo == null - || info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP - || taskInfo.displayId != DEFAULT_DISPLAY - || taskInfo.taskId == -1 - || !taskInfo.isRunning) { - continue; - } - Boolean homeVisibilityUpdate = getHomeVisibilityUpdate(info, change, taskInfo); - if (homeVisibilityUpdate != null) { - notifyHomeVisibilityChanged(homeVisibilityUpdate); - } + private void storePendingHomeVisibilityUpdate( + IBinder transition, Boolean homeVisibilityUpdate) { + if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen() + && !ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue()) { + return; } + mPendingHomeVisibilityUpdate = homeVisibilityUpdate; + mPendingStartDragTransition = transition; } - private void handleTransitionReadyWithBubbleAnything(@NonNull TransitionInfo info) { + private Boolean getHomeVisibilityUpdate(TransitionInfo info) { Boolean homeVisibilityUpdate = null; for (TransitionInfo.Change change : info.getChanges()) { final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); @@ -97,29 +110,12 @@ public class HomeTransitionObserver implements TransitionObserver, || !taskInfo.isRunning) { continue; } - Boolean update = getHomeVisibilityUpdate(info, change, taskInfo); if (update != null) { homeVisibilityUpdate = update; } } - - if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) { - // Do not apply at the start of desktop drag as that updates launcher UI visibility. - // Store the value and apply with a next transition if needed. - mPendingHomeVisibilityUpdate = homeVisibilityUpdate; - return; - } - - if (info.getType() == TRANSIT_CONVERT_TO_BUBBLE && homeVisibilityUpdate == null) { - // We are converting to bubble and we did not get a change to home visibility in this - // transition. Apply the value from start of drag. - homeVisibilityUpdate = mPendingHomeVisibilityUpdate; - } - if (homeVisibilityUpdate != null) { - mPendingHomeVisibilityUpdate = null; - notifyHomeVisibilityChanged(homeVisibilityUpdate); - } + return homeVisibilityUpdate; } private Boolean getHomeVisibilityUpdate(TransitionInfo info, @@ -146,7 +142,24 @@ public class HomeTransitionObserver implements TransitionObserver, @Override public void onTransitionFinished(@NonNull IBinder transition, - boolean aborted) {} + boolean aborted) { + if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue()) { + return; + } + // Handle the case where the DragToDesktop START transition is interrupted and we never + // receive a CANCEL/END transition. + if (mPendingStartDragTransition == null + || mPendingStartDragTransition != transition) { + return; + } + mPendingStartDragTransition = null; + if (aborted) return; + + if (mPendingHomeVisibilityUpdate != null) { + notifyHomeVisibilityChanged(mPendingHomeVisibilityUpdate); + mPendingHomeVisibilityUpdate = null; + } + } /** * Sets the home transition listener that receives any transitions resulting in a change of diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java deleted file mode 100644 index 978b8da2eb6d..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2021 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.wm.shell.transition; - -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; - -import android.annotation.NonNull; -import android.os.RemoteException; -import android.view.IRemoteAnimationFinishedCallback; -import android.view.IRemoteAnimationRunner; -import android.view.RemoteAnimationAdapter; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.view.WindowManager; -import android.window.IWindowContainerTransactionCallback; - -import com.android.internal.protolog.ProtoLog; - -/** - * Utilities and interfaces for transition-like usage on top of the legacy app-transition and - * synctransaction tools. - */ -public class LegacyTransitions { - - /** - * Interface for a "legacy" transition. Effectively wraps a sync callback + remoteAnimation - * into one callback. - */ - public interface ILegacyTransition { - /** - * Called when both the associated sync transaction finishes and the remote animation is - * ready. - */ - void onAnimationStart(int transit, RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, - IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t); - } - - /** - * Makes sure that a remote animation and corresponding sync callback are called together - * such that the sync callback is called first. This assumes that both the callback receiver - * and the remoteanimation are in the same process so that order is preserved on both ends. - */ - public static class LegacyTransition { - private final ILegacyTransition mLegacyTransition; - private int mSyncId = -1; - private SurfaceControl.Transaction mTransaction; - private int mTransit; - private RemoteAnimationTarget[] mApps; - private RemoteAnimationTarget[] mWallpapers; - private RemoteAnimationTarget[] mNonApps; - private IRemoteAnimationFinishedCallback mFinishCallback = null; - private boolean mCancelled = false; - private final SyncCallback mSyncCallback = new SyncCallback(); - private final RemoteAnimationAdapter mAdapter = - new RemoteAnimationAdapter(new RemoteAnimationWrapper(), 0, 0); - - public LegacyTransition(@WindowManager.TransitionType int type, - @NonNull ILegacyTransition legacyTransition) { - mLegacyTransition = legacyTransition; - mTransit = type; - } - - public @WindowManager.TransitionType int getType() { - return mTransit; - } - - public IWindowContainerTransactionCallback getSyncCallback() { - return mSyncCallback; - } - - public RemoteAnimationAdapter getAdapter() { - return mAdapter; - } - - private class SyncCallback extends IWindowContainerTransactionCallback.Stub { - @Override - public void onTransactionReady(int id, SurfaceControl.Transaction t) - throws RemoteException { - ProtoLog.v(WM_SHELL_TRANSITIONS, - "LegacyTransitions.onTransactionReady(): syncId=%d", id); - mSyncId = id; - mTransaction = t; - checkApply(true /* log */); - } - } - - private class RemoteAnimationWrapper extends IRemoteAnimationRunner.Stub { - @Override - public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, - IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { - mTransit = transit; - mApps = apps; - mWallpapers = wallpapers; - mNonApps = nonApps; - mFinishCallback = finishedCallback; - checkApply(false /* log */); - } - - @Override - public void onAnimationCancelled() throws RemoteException { - mCancelled = true; - mApps = mWallpapers = mNonApps = null; - checkApply(false /* log */); - } - } - - - private void checkApply(boolean log) throws RemoteException { - if (mSyncId < 0 || (mFinishCallback == null && !mCancelled)) { - if (log) { - ProtoLog.v(WM_SHELL_TRANSITIONS, "\tSkipping hasFinishedCb=%b canceled=%b", - mFinishCallback != null, mCancelled); - } - return; - } - if (log) { - ProtoLog.v(WM_SHELL_TRANSITIONS, "\tapply"); - } - mLegacyTransition.onAnimationStart(mTransit, mApps, mWallpapers, - mNonApps, mFinishCallback, mTransaction); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 7871179a50de..42321e56e72b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -49,6 +49,7 @@ import android.view.SurfaceControl; import android.view.View; import android.view.ViewConfiguration; import android.window.DisplayAreaInfo; +import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -233,7 +234,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT RunningTaskInfo taskInfo, SurfaceControl taskSurface, SurfaceControl.Transaction startT, - SurfaceControl.Transaction finishT) { + SurfaceControl.Transaction finishT, + @TransitionInfo.TransitionMode int changeMode) { final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); if (!shouldShowWindowDecor(taskInfo)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 2d7fec3a5302..5e8c1fe2aa8d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -445,4 +445,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) { return R.dimen.freeform_decor_caption_height; } + + @Override + int getCaptionViewId() { + return R.id.caption; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java index 2b2cdf84005c..4511fbe10764 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java @@ -31,6 +31,7 @@ import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.SurfaceControl; import android.view.View; +import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -159,7 +160,8 @@ public abstract class CarWindowDecorViewModel RunningTaskInfo taskInfo, SurfaceControl taskSurface, SurfaceControl.Transaction startT, - SurfaceControl.Transaction finishT) { + SurfaceControl.Transaction finishT, + @TransitionInfo.TransitionMode int changeMode) { final CarWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); if (!shouldShowWindowDecor(taskInfo)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java index 88f64bca280d..3182745d813e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java @@ -108,6 +108,11 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou return new Rect(); } + @Override + int getCaptionViewId() { + return R.id.caption; + } + private void updateRelayoutParams( RelayoutParams relayoutParams, ActivityManager.RunningTaskInfo taskInfo, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index a1d2774ee428..f2ff39627362 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -27,6 +27,7 @@ import static android.view.MotionEvent.ACTION_HOVER_EXIT; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowInsets.Type.statusBars; +import static android.view.WindowManager.TRANSIT_TO_BACK; import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU; import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; @@ -79,6 +80,7 @@ import android.view.ViewConfiguration; import android.view.ViewRootImpl; import android.window.DesktopModeFlags; import android.window.TaskSnapshot; +import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -150,18 +152,19 @@ import com.android.wm.shell.windowdecor.extension.InsetsStateKt; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel; import com.android.wm.shell.windowdecor.tiling.SnapEventHandler; +import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; import kotlin.Pair; import kotlin.Unit; import kotlin.jvm.functions.Function1; -import org.jetbrains.annotations.NotNull; - import kotlinx.coroutines.CoroutineScope; import kotlinx.coroutines.ExperimentalCoroutinesApi; import kotlinx.coroutines.MainCoroutineDispatcher; +import org.jetbrains.annotations.NotNull; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; @@ -206,6 +209,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private final AppToWebEducationController mAppToWebEducationController; private final AppHandleAndHeaderVisibilityHelper mAppHandleAndHeaderVisibilityHelper; private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory; + private final AppHandleViewHolder.Factory mAppHandleViewHolderFactory; private boolean mTransitionDragActive; private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>(); @@ -331,6 +335,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, new InputMonitorFactory(), SurfaceControl.Transaction::new, new AppHeaderViewHolder.Factory(), + new AppHandleViewHolder.Factory(), rootTaskDisplayAreaOrganizer, new SparseArray<>(), interactionJankMonitor, @@ -380,6 +385,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, InputMonitorFactory inputMonitorFactory, Supplier<SurfaceControl.Transaction> transactionFactory, AppHeaderViewHolder.Factory appHeaderViewHolderFactory, + AppHandleViewHolder.Factory appHandleViewHolderFactory, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId, InteractionJankMonitor interactionJankMonitor, @@ -422,6 +428,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mInputMonitorFactory = inputMonitorFactory; mTransactionFactory = transactionFactory; mAppHeaderViewHolderFactory = appHeaderViewHolderFactory; + mAppHandleViewHolderFactory = appHandleViewHolderFactory; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mGenericLinksParser = genericLinksParser; mInputManager = mContext.getSystemService(InputManager.class); @@ -486,7 +493,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, new DesktopModeOnTaskResizeAnimationListener()); mDesktopTasksController.setOnTaskRepositionAnimationListener( new DesktopModeOnTaskRepositionAnimationListener()); - if (DesktopModeFlags.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()) { + if (DesktopModeFlags.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue() + || DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) { mRecentsTransitionHandler.addTransitionStateListener( new DesktopModeRecentsTransitionStateListener()); } @@ -587,7 +595,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, RunningTaskInfo taskInfo, SurfaceControl taskSurface, SurfaceControl.Transaction startT, - SurfaceControl.Transaction finishT) { + SurfaceControl.Transaction finishT, + @TransitionInfo.TransitionMode int changeMode) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); if (!shouldShowWindowDecor(taskInfo)) { if (decoration != null) { @@ -601,8 +610,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } else { decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */, - mFocusTransitionObserver.hasGlobalFocus(taskInfo), - mExclusionRegion); + mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion, + /*isMovingToBack= */ changeMode == TRANSIT_TO_BACK); } } @@ -617,7 +626,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */, mFocusTransitionObserver.hasGlobalFocus(taskInfo), - mExclusionRegion); + mExclusionRegion, /* isMovingToBack= */ false); } @Override @@ -817,9 +826,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, return; } decoration.closeHandleMenu(); - // When the app enters split-select, the handle will no longer be visible, meaning - // we shouldn't receive input for it any longer. - decoration.disposeStatusBarInputLayer(); mDesktopTasksController.requestSplit(decoration.mTaskInfo, false /* leftOrTop */); mDesktopModeUiEventLogger.log(decoration.mTaskInfo, DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_SPLIT_SCREEN); @@ -979,6 +985,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private boolean mIsCustomHeaderGesture; private boolean mIsResizeGesture; private boolean mIsDragging; + private boolean mDragInterrupted; private boolean mLongClickDisabled; private int mDragPointerId = -1; private MotionEvent mMotionEvent; @@ -1216,7 +1223,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, View v, MotionEvent e) { final int id = v.getId(); if (id == R.id.caption_handle) { - handleCaptionThroughStatusBar(e, decoration); + handleCaptionThroughStatusBar(e, decoration, + /* interruptDragCallback= */ + () -> { + mDragInterrupted = true; + setIsDragging(decoration, /* isDragging= */ false); + }); final boolean wasDragging = mIsDragging; updateDragStatus(decoration, e); final boolean upOrCancel = e.getActionMasked() == ACTION_UP @@ -1333,11 +1345,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { + mDragInterrupted = false; setIsDragging(decor, false /* isDragging */); break; } case MotionEvent.ACTION_MOVE: { - setIsDragging(decor, true /* isDragging */); + if (!mDragInterrupted) { + setIsDragging(decor, true /* isDragging */); + } break; } } @@ -1458,7 +1473,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, if (!mInImmersiveMode && (relevantDecor == null || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM || mTransitionDragActive)) { - handleCaptionThroughStatusBar(ev, relevantDecor); + handleCaptionThroughStatusBar(ev, relevantDecor, + /* interruptDragCallback= */ () -> {}); } } handleEventOutsideCaption(ev, relevantDecor); @@ -1498,7 +1514,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, * Turn on desktop mode if handle is dragged below status bar. */ private void handleCaptionThroughStatusBar(MotionEvent ev, - DesktopModeWindowDecoration relevantDecor) { + DesktopModeWindowDecoration relevantDecor, Runnable interruptDragCallback) { if (relevantDecor == null) { if (ev.getActionMasked() == ACTION_UP) { mMoveToDesktopAnimator = null; @@ -1599,7 +1615,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mContext, mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo, relevantDecor.mTaskSurface); mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo, - mMoveToDesktopAnimator, relevantDecor.mTaskSurface); + mMoveToDesktopAnimator, relevantDecor.mTaskSurface, + /* dragInterruptedCallback= */ () -> { + // Don't call into DesktopTasksController to cancel the + // transition here - the transition handler already handles + // that (including removing the visual indicator). + mTransitionDragActive = false; + mMoveToDesktopAnimator = null; + relevantDecor.handleDragInterrupted(); + interruptDragCallback.run(); + }); } } if (mMoveToDesktopAnimator != null) { @@ -1761,6 +1786,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mMainChoreographer, mSyncQueue, mAppHeaderViewHolderFactory, + mAppHandleViewHolderFactory, mRootTaskDisplayAreaOrganizer, mGenericLinksParser, mAssistContentRequester, @@ -1852,12 +1878,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, windowDecoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */, mFocusTransitionObserver.hasGlobalFocus(taskInfo), - mExclusionRegion); + mExclusionRegion, /* isMovingToBack= */ false); if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) { incrementEventReceiverTasks(taskInfo.displayId); } } + @Nullable private RunningTaskInfo getOtherSplitTask(int taskId) { @SplitPosition int remainingTaskPosition = mSplitScreenController .getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 1b5fdbb973d5..bcf9396ff0c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -190,6 +190,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private ExclusionRegionListener mExclusionRegionListener; private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory; + private final AppHandleViewHolder.Factory mAppHandleViewHolderFactory; private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private final MaximizeMenuFactory mMaximizeMenuFactory; private final HandleMenuFactory mHandleMenuFactory; @@ -212,6 +213,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private boolean mIsDragging = false; private Runnable mLoadAppInfoRunnable; private Runnable mSetAppInfoRunnable; + private boolean mIsMovingToBack; public DesktopModeWindowDecoration( Context context, @@ -231,6 +233,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Choreographer choreographer, SyncTransactionQueue syncQueue, AppHeaderViewHolder.Factory appHeaderViewHolderFactory, + AppHandleViewHolder.Factory appHandleViewHolderFactory, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, @@ -242,10 +245,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin this (context, userContext, displayController, taskResourceLoader, splitScreenController, desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, handler, mainExecutor, mainDispatcher, bgScope, bgExecutor, choreographer, syncQueue, - appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser, - assistContentRequester, SurfaceControl.Builder::new, - SurfaceControl.Transaction::new, WindowContainerTransaction::new, - SurfaceControl::new, new WindowManagerWrapper( + appHeaderViewHolderFactory, appHandleViewHolderFactory, + rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, + SurfaceControl.Builder::new, SurfaceControl.Transaction::new, + WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper( context.getSystemService(WindowManager.class)), new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier, @@ -273,6 +276,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Choreographer choreographer, SyncTransactionQueue syncQueue, AppHeaderViewHolder.Factory appHeaderViewHolderFactory, + AppHandleViewHolder.Factory appHandleViewHolderFactory, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, @@ -302,6 +306,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mChoreographer = choreographer; mSyncQueue = syncQueue; mAppHeaderViewHolderFactory = appHeaderViewHolderFactory; + mAppHandleViewHolderFactory = appHandleViewHolderFactory; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mGenericLinksParser = genericLinksParser; mAssistContentRequester = assistContentRequester; @@ -462,7 +467,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // causes flickering. See b/270202228. final boolean applyTransactionOnDraw = taskInfo.isFreeform(); relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop, - hasGlobalFocus, displayExclusionRegion); + hasGlobalFocus, displayExclusionRegion, mIsMovingToBack); if (!applyTransactionOnDraw) { t.apply(); } @@ -489,7 +494,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin void relayout(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, - boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) { + boolean hasGlobalFocus, @NonNull Region displayExclusionRegion, + boolean isMovingToBack) { Trace.beginSection("DesktopModeWindowDecoration#relayout"); if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_APP_TO_WEB.isTrue()) { @@ -512,12 +518,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final boolean inFullImmersive = mDesktopUserRepositories.getProfile(taskInfo.userId) .isTaskInFullImmersiveState(taskInfo.taskId); + mIsMovingToBack = isMovingToBack; updateRelayoutParams(mRelayoutParams, mContext, taskInfo, mSplitScreenController, applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive, mIsDragging, mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus, - displayExclusionRegion, mIsRecentsTransitionRunning, - mDesktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(taskInfo)); + displayExclusionRegion, + /* shouldIgnoreCornerRadius= */ mIsRecentsTransitionRunning + && DesktopModeFlags + .ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue(), + mDesktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(taskInfo), + mIsRecentsTransitionRunning, mIsMovingToBack); final WindowDecorLinearLayout oldRootView = mResult.mRootView; final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; @@ -542,7 +553,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return; } - if (oldRootView != mResult.mRootView) { + if (DesktopModeFlags.SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX.isTrue() + ? (oldRootView != mResult.mRootView && taskInfo.isVisibleRequested) + : oldRootView != mResult.mRootView) { disposeStatusBarInputLayer(); mWindowDecorViewHolder = createViewHolder(); // Load these only when first creating the view. @@ -558,29 +571,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin }); } - final Point position = new Point(); - if (isAppHandle(mWindowDecorViewHolder)) { - position.set(determineHandlePosition()); - } if (canEnterDesktopMode(mContext) && isEducationEnabled()) { notifyCaptionStateChanged(); } Trace.beginSection("DesktopModeWindowDecoration#relayout-bindData"); if (isAppHandle(mWindowDecorViewHolder)) { - mWindowDecorViewHolder.bindData(new AppHandleViewHolder.HandleData( - mTaskInfo, position, mResult.mCaptionWidth, mResult.mCaptionHeight, - isCaptionVisible() - )); + updateAppHandleViewHolder(); } else { - mWindowDecorViewHolder.bindData(new AppHeaderViewHolder.HeaderData( - mTaskInfo, - DesktopModeUtils.isTaskMaximized(mTaskInfo, mDisplayController), - inFullImmersive, - hasGlobalFocus, - /* maximizeHoverEnabled= */ canOpenMaximizeMenu( - /* animatingTaskResizeOrReposition= */ false) - )); + updateAppHeaderViewHolder(inFullImmersive, hasGlobalFocus); } Trace.endSection(); @@ -857,9 +856,31 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer(); } + /** Update the view holder for app handle. */ + private void updateAppHandleViewHolder() { + if (!isAppHandle(mWindowDecorViewHolder)) return; + asAppHandle(mWindowDecorViewHolder).bindData(new AppHandleViewHolder.HandleData( + mTaskInfo, determineHandlePosition(), mResult.mCaptionWidth, + mResult.mCaptionHeight, isCaptionVisible() + )); + } + + /** Update the view holder for app header. */ + private void updateAppHeaderViewHolder(boolean inFullImmersive, boolean hasGlobalFocus) { + if (!isAppHeader(mWindowDecorViewHolder)) return; + asAppHeader(mWindowDecorViewHolder).bindData(new AppHeaderViewHolder.HeaderData( + mTaskInfo, + DesktopModeUtils.isTaskMaximized(mTaskInfo, mDisplayController), + inFullImmersive, + hasGlobalFocus, + /* maximizeHoverEnabled= */ canOpenMaximizeMenu( + /* animatingTaskResizeOrReposition= */ false) + )); + } + private WindowDecorationViewHolder createViewHolder() { if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) { - return new AppHandleViewHolder( + return mAppHandleViewHolderFactory.create( mResult.mRootView, mOnCaptionTouchListener, mOnCaptionButtonClickListener, @@ -886,6 +907,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return viewHolder instanceof AppHandleViewHolder; } + private boolean isAppHeader(WindowDecorationViewHolder viewHolder) { + return viewHolder instanceof AppHeaderViewHolder; + } + @Nullable private AppHandleViewHolder asAppHandle(WindowDecorationViewHolder viewHolder) { if (viewHolder instanceof AppHandleViewHolder) { @@ -918,7 +943,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin boolean hasGlobalFocus, @NonNull Region displayExclusionRegion, boolean shouldIgnoreCornerRadius, - boolean shouldExcludeCaptionFromAppBounds) { + boolean shouldExcludeCaptionFromAppBounds, + boolean isRecentsTransitionRunning, + boolean isMovingToBack) { final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode()); final boolean isAppHeader = captionLayoutId == R.layout.desktop_mode_app_header; @@ -935,11 +962,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // the first frame. relayoutParams.mAsyncViewHost = isAppHandle; - final boolean showCaption; - if (DesktopModeFlags.ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX.isTrue() && isDragging) { + boolean showCaption; + // If this relayout is occurring from an observed TRANSIT_TO_BACK transition, do not + // show caption (this includes split select transition). + if (DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue() + && isMovingToBack && !isDragging) { + showCaption = false; + } else if (DesktopModeFlags.ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX.isTrue() && isDragging) { // If the task is being dragged, the caption should not be hidden so that it continues // receiving input showCaption = true; + } else if (DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue() + && isRecentsTransitionRunning) { + // Caption should not be visible in recents. + showCaption = false; } else if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) { if (inFullImmersiveMode) { showCaption = (isStatusBarVisible && !isKeyguardVisibleAndOccluded); @@ -1683,6 +1719,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } } + /** + * Indicates that an app handle drag has been interrupted, this can happen e.g. if we receive an + * unknown transition during the drag-to-desktop transition. + */ + void handleDragInterrupted() { + if (mResult.mRootView == null) return; + final View handle = mResult.mRootView.findViewById(R.id.caption_handle); + handle.setHovered(false); + handle.setPressed(false); + } + private boolean pointInView(View v, float x, float y) { return v != null && v.getLeft() <= x && v.getRight() >= x && v.getTop() <= y && v.getBottom() >= y; @@ -1776,6 +1823,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode)); } + @Override + int getCaptionViewId() { + return R.id.desktop_mode_caption; + } + void setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition) { if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) return; final boolean inFullImmersive = @@ -1795,9 +1847,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * <p> When a Recents transition is active we allow that transition to take ownership of the * corner radius of its task surfaces, so each window decoration should stop updating the corner * radius of its task surface during that time. + * + * We should not allow input to reach the input layer during a Recents transition, so + * update the handle view holder accordingly if transition status changes. */ void setIsRecentsTransitionRunning(boolean isRecentsTransitionRunning) { - mIsRecentsTransitionRunning = isRecentsTransitionRunning; + if (mIsRecentsTransitionRunning != isRecentsTransitionRunning) { + mIsRecentsTransitionRunning = isRecentsTransitionRunning; + if (DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) { + // We don't relayout decor on recents transition, so we need to call it directly. + relayout(mTaskInfo, mHasGlobalFocus, mRelayoutParams.mDisplayExclusionRegion); + } + } } /** @@ -1862,6 +1923,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Choreographer choreographer, SyncTransactionQueue syncQueue, AppHeaderViewHolder.Factory appHeaderViewHolderFactory, + AppHandleViewHolder.Factory appHandleViewHolderFactory, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, @@ -1889,6 +1951,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin choreographer, syncQueue, appHeaderViewHolderFactory, + appHandleViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 7a4a834e9dc2..0b86d1dbbc58 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -23,12 +23,12 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERL import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; +import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP; -import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEdgeResizePermitted; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEventFromTouchscreen; @@ -127,7 +127,9 @@ class DragResizeInputListener implements AutoCloseable { Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, DisplayController displayController, - DesktopModeEventLogger desktopModeEventLogger) { + DesktopModeEventLogger desktopModeEventLogger, + InputChannel inputChannel, + InputChannel sinkInputChannel) { mContext = context; mWindowSession = windowSession; mBgExecutor = bgExecutor; @@ -136,7 +138,11 @@ class DragResizeInputListener implements AutoCloseable { mHandler = handler; mChoreographer = choreographer; mDisplayId = displayId; - mDecorationSurface = decorationSurface; + // Creates a new SurfaceControl pointing the same underlying surface with decorationSurface + // to ensure that mDecorationSurface will not be released while it's used on the background + // thread. Note that the empty name will be overridden by the next copyFrom call. + mDecorationSurface = surfaceControlBuilderSupplier.get().setName("").build(); + mDecorationSurface.copyFrom(decorationSurface, "DragResizeInputListener"); mDragPositioningCallback = callback; mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier; mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; @@ -154,9 +160,13 @@ class DragResizeInputListener implements AutoCloseable { final InputSetUpResult result = setUpInputChannels(mDisplayId, mWindowSession, mDecorationSurface, mClientToken, mSinkClientToken, mSurfaceControlBuilderSupplier, - mSurfaceControlTransactionSupplier); + mSurfaceControlTransactionSupplier, inputChannel, sinkInputChannel); mainExecutor.execute(() -> { if (mClosed) { + result.mInputChannel.dispose(); + result.mSinkInputChannel.dispose(); + mSurfaceControlTransactionSupplier.get().remove( + result.mInputSinkSurface).apply(); return; } mInputSinkSurface = result.mInputSinkSurface; @@ -208,7 +218,7 @@ class DragResizeInputListener implements AutoCloseable { new DefaultTaskResizeInputEventReceiverFactory(), taskInfo, handler, choreographer, displayId, decorationSurface, callback, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, - displayController, desktopModeEventLogger); + displayController, desktopModeEventLogger, new InputChannel(), new InputChannel()); } DragResizeInputListener( @@ -251,11 +261,11 @@ class DragResizeInputListener implements AutoCloseable { @NonNull IBinder clientToken, @NonNull IBinder sinkClientToken, @NonNull Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, - @NonNull Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) { + @NonNull Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, + @NonNull InputChannel inputChannel, + @NonNull InputChannel sinkInputChannel) { Trace.beginSection("DragResizeInputListener#setUpInputChannels"); final InputTransferToken inputTransferToken = new InputTransferToken(); - final InputChannel inputChannel = new InputChannel(); - final InputChannel sinkInputChannel = new InputChannel(); try { windowSession.grantInputChannel( displayId, @@ -421,6 +431,9 @@ class DragResizeInputListener implements AutoCloseable { } catch (RemoteException e) { e.rethrowFromSystemServer(); } + // Removing this surface on the background thread to ensure that mInitInputChannels has + // already been finished. + mSurfaceControlTransactionSupplier.get().remove(mDecorationSurface).apply(); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java index 2d6f7459e0ae..732f04259fd3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java @@ -173,6 +173,9 @@ class FluidResizeTaskPositioner implements TaskPositioner, Transitions.Transitio return new Rect(mRepositionTaskBounds); } + @Override + public void close() {} + private boolean isResizing() { return (mCtrlType & CTRL_TYPE_TOP) != 0 || (mCtrlType & CTRL_TYPE_BOTTOM) != 0 || (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0; 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 c544468f5191..9cc64ac9c276 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 @@ -108,19 +108,19 @@ class HandleMenu( private val isViewAboveStatusBar: Boolean get() = (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() && !taskInfo.isFreeform) - private val pillElevation: Int = loadDimensionPixelSize( - R.dimen.desktop_mode_handle_menu_pill_elevation) private val pillTopMargin: Int = loadDimensionPixelSize( - R.dimen.desktop_mode_handle_menu_pill_spacing_margin) - private val menuWidth = loadDimensionPixelSize( - R.dimen.desktop_mode_handle_menu_width) + pillElevation + R.dimen.desktop_mode_handle_menu_pill_spacing_margin + ) + private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_width) private val menuHeight = getHandleMenuHeight() private val marginMenuTop = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_margin_top) private val marginMenuStart = loadDimensionPixelSize( - R.dimen.desktop_mode_handle_menu_margin_start) + R.dimen.desktop_mode_handle_menu_margin_start + ) @VisibleForTesting var handleMenuViewContainer: AdditionalViewContainer? = null + @VisibleForTesting var handleMenuView: HandleMenuView? = null @@ -139,7 +139,7 @@ class HandleMenu( private val shouldShowMoreActionsPill: Boolean get() = SHOULD_SHOW_SCREENSHOT_BUTTON || shouldShowNewWindowButton || - shouldShowManageWindowsButton || shouldShowChangeAspectRatioButton + shouldShowManageWindowsButton || shouldShowChangeAspectRatioButton private var loadAppInfoJob: Job? = null @@ -243,7 +243,8 @@ class HandleMenu( val y = handleMenuPosition.y.toInt() handleMenuViewContainer = if ((!taskInfo.isFreeform && DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) - || forceShowSystemBars) { + || forceShowSystemBars + ) { AdditionalSystemViewContainer( windowManagerWrapper = windowManagerWrapper, taskId = taskInfo.taskId, @@ -254,7 +255,11 @@ class HandleMenu( flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, view = handleMenuView.rootView, - forciblyShownTypes = if (forceShowSystemBars) { systemBars() } else { 0 }, + forciblyShownTypes = if (forceShowSystemBars) { + systemBars() + } else { + 0 + }, ignoreCutouts = Flags.showAppHandleLargeScreens() || BubbleAnythingFlagHelper.enableBubbleToFullscreen() ) @@ -372,7 +377,8 @@ class HandleMenu( inputPoint.y - globalMenuPosition.y ) if (splitScreenController.getSplitPosition(taskInfo.taskId) - == SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT) { + == SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT + ) { val leftStageBounds = Rect() splitScreenController.getStageBounds(leftStageBounds, Rect()) inputRelativeToMenu.x += leftStageBounds.width().toFloat() @@ -398,11 +404,11 @@ class HandleMenu( * Determines handle menu height based the max size and the visibility of pills. */ private fun getHandleMenuHeight(): Int { - var menuHeight = loadDimensionPixelSize( - R.dimen.desktop_mode_handle_menu_height) + pillElevation + var menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_height) if (!shouldShowWindowingPill) { menuHeight -= loadDimensionPixelSize( - R.dimen.desktop_mode_handle_menu_windowing_pill_height) + R.dimen.desktop_mode_handle_menu_windowing_pill_height + ) menuHeight -= pillTopMargin } if (!SHOULD_SHOW_SCREENSHOT_BUTTON) { @@ -422,14 +428,16 @@ class HandleMenu( } if (!shouldShowChangeAspectRatioButton) { menuHeight -= loadDimensionPixelSize( - R.dimen.desktop_mode_handle_menu_change_aspect_ratio_height) + R.dimen.desktop_mode_handle_menu_change_aspect_ratio_height + ) } if (!shouldShowMoreActionsPill) { menuHeight -= pillTopMargin } if (!shouldShowBrowserPill) { menuHeight -= loadDimensionPixelSize( - R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height) + R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height + ) menuHeight -= pillTopMargin } return menuHeight @@ -472,48 +480,66 @@ class HandleMenu( // Insets for ripple effect of App Info Pill. and Windowing Pill. buttons val iconButtondrawableShiftInset = context.resources.getDimensionPixelSize( - R.dimen.desktop_mode_handle_menu_icon_button_ripple_inset_shift) + R.dimen.desktop_mode_handle_menu_icon_button_ripple_inset_shift + ) val iconButtondrawableBaseInset = context.resources.getDimensionPixelSize( - R.dimen.desktop_mode_handle_menu_icon_button_ripple_inset_base) + R.dimen.desktop_mode_handle_menu_icon_button_ripple_inset_base + ) private val iconButtonRippleRadius = context.resources.getDimensionPixelSize( - R.dimen.desktop_mode_handle_menu_icon_button_ripple_radius) - private val iconButtonDrawableInsetsBase = DrawableInsets(t = iconButtondrawableBaseInset, + R.dimen.desktop_mode_handle_menu_icon_button_ripple_radius + ) + private val iconButtonDrawableInsetsBase = DrawableInsets( + t = iconButtondrawableBaseInset, b = iconButtondrawableBaseInset, l = iconButtondrawableBaseInset, - r = iconButtondrawableBaseInset) - private val iconButtonDrawableInsetsLeft = DrawableInsets(t = iconButtondrawableBaseInset, - b = iconButtondrawableBaseInset, l = iconButtondrawableShiftInset, r = 0) - private val iconButtonDrawableInsetsRight = DrawableInsets(t = iconButtondrawableBaseInset, - b = iconButtondrawableBaseInset, l = 0, r = iconButtondrawableShiftInset) + r = iconButtondrawableBaseInset + ) + private val iconButtonDrawableInsetsLeft = DrawableInsets( + t = iconButtondrawableBaseInset, + b = iconButtondrawableBaseInset, l = iconButtondrawableShiftInset, r = 0 + ) + private val iconButtonDrawableInsetsRight = DrawableInsets( + t = iconButtondrawableBaseInset, + b = iconButtondrawableBaseInset, l = 0, r = iconButtondrawableShiftInset + ) // App Info Pill. private val appInfoPill = rootView.requireViewById<View>(R.id.app_info_pill) private val collapseMenuButton = appInfoPill.requireViewById<HandleMenuImageButton>( - R.id.collapse_menu_button) + R.id.collapse_menu_button + ) + @VisibleForTesting val appIconView = appInfoPill.requireViewById<ImageView>(R.id.application_icon) + @VisibleForTesting val appNameView = appInfoPill.requireViewById<MarqueedTextView>(R.id.application_name) // Windowing Pill. private val windowingPill = rootView.requireViewById<View>(R.id.windowing_pill) private val fullscreenBtn = windowingPill.requireViewById<ImageButton>( - R.id.fullscreen_button) + R.id.fullscreen_button + ) private val splitscreenBtn = windowingPill.requireViewById<ImageButton>( - R.id.split_screen_button) + R.id.split_screen_button + ) private val floatingBtn = windowingPill.requireViewById<ImageButton>(R.id.floating_button) private val floatingBtnSpace = windowingPill.requireViewById<Space>( - R.id.floating_button_space) + R.id.floating_button_space + ) private val desktopBtn = windowingPill.requireViewById<ImageButton>(R.id.desktop_button) private val desktopBtnSpace = windowingPill.requireViewById<Space>( - R.id.desktop_button_space) + R.id.desktop_button_space + ) // More Actions Pill. private val moreActionsPill = rootView.requireViewById<View>(R.id.more_actions_pill) private val screenshotBtn = moreActionsPill.requireViewById<HandleMenuActionButton>( - R.id.screenshot_button) + R.id.screenshot_button + ) private val newWindowBtn = moreActionsPill.requireViewById<HandleMenuActionButton>( - R.id.new_window_button) + R.id.new_window_button + ) private val manageWindowBtn = moreActionsPill .requireViewById<HandleMenuActionButton>(R.id.manage_windows_button) private val changeAspectRatioBtn = moreActionsPill @@ -521,11 +547,14 @@ class HandleMenu( // Open in Browser/App Pill. private val openInAppOrBrowserPill = rootView.requireViewById<View>( - R.id.open_in_app_or_browser_pill) + R.id.open_in_app_or_browser_pill + ) private val openInAppOrBrowserBtn = openInAppOrBrowserPill.requireViewById<View>( - R.id.open_in_app_or_browser_button) + R.id.open_in_app_or_browser_button + ) private val openByDefaultBtn = openInAppOrBrowserPill.requireViewById<ImageButton>( - R.id.open_by_default_button) + R.id.open_by_default_button + ) private val decorThemeUtil = DecorThemeUtil(context) private val animator = HandleMenuAnimator(rootView, menuWidth, captionHeight.toFloat()) @@ -734,9 +763,9 @@ class HandleMenu( desktopBtn.imageTintList = style.windowingButtonColor val startInsets = if (context.isRtl) iconButtonDrawableInsetsRight - else iconButtonDrawableInsetsLeft + else iconButtonDrawableInsetsLeft val endInsets = if (context.isRtl) iconButtonDrawableInsetsLeft - else iconButtonDrawableInsetsRight + else iconButtonDrawableInsetsRight fullscreenBtn.apply { background = createBackgroundDrawable( @@ -808,9 +837,11 @@ class HandleMenu( getString(R.string.open_in_browser_text) } + val buttonRoot = openInAppOrBrowserBtn.requireViewById<LinearLayout>(R.id.action_button) val label = openInAppOrBrowserBtn.requireViewById<MarqueedTextView>(R.id.label) val image = openInAppOrBrowserBtn.requireViewById<ImageView>(R.id.image) openInAppOrBrowserBtn.contentDescription = btnText + buttonRoot.contentDescription = btnText label.apply { text = btnText setTextColor(style.textColor) @@ -841,7 +872,7 @@ class HandleMenu( */ fun shouldShowChangeAspectRatioButton(taskInfo: RunningTaskInfo): Boolean = taskInfo.appCompatTaskInfo.eligibleForUserAspectRatioButton() && - taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN + taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt index 4b2e473d6ec2..a723a7a4ac20 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt @@ -23,9 +23,7 @@ import android.util.AttributeSet import android.view.LayoutInflater import android.widget.ImageView import android.widget.LinearLayout -import android.widget.TextView import androidx.core.content.withStyledAttributes -import androidx.core.view.isGone import com.android.wm.shell.R /** @@ -54,6 +52,7 @@ class HandleMenuActionButton @JvmOverloads constructor( context.withStyledAttributes(attrs, R.styleable.HandleMenuActionButton) { textView.text = getString(R.styleable.HandleMenuActionButton_android_text) + rootElement.contentDescription = getString(R.styleable.HandleMenuActionButton_android_text) textView.setTextColor(getColor(R.styleable.HandleMenuActionButton_android_textColor, 0)) iconView.setImageResource(getResourceId( R.styleable.HandleMenuActionButton_android_src, 0)) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt index 1b0e0f70ed21..eb324f74ca82 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt @@ -294,6 +294,10 @@ class MultiDisplayVeiledResizeTaskPositioner( return Rect(repositionTaskBounds) } + override fun close() { + displayController.removeDisplayWindowListener(this) + } + private fun resetVeilIfVisible() { if (isResizingOrAnimatingResize) { desktopWindowDecoration.hideResizeVeil() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java index 63b288d133dd..06e5380fa1de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java @@ -41,4 +41,10 @@ public interface TaskDragResizer { */ void removeDragEventListener( DragPositioningCallbackUtility.DragEventListener dragEventListener); + + /** + * Releases any resources associated with this TaskDragResizer. This should be called when the + * associated window is closed. + */ + void close(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index d2c79d76e6c1..7e941ec0d31a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -205,6 +205,9 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T return new Rect(mRepositionTaskBounds); } + @Override + public void close() {} + private boolean isResizing() { return (mCtrlType & CTRL_TYPE_TOP) != 0 || (mCtrlType & CTRL_TYPE_BOTTOM) != 0 || (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java index 1563259f4a1a..5e4a0a5860f0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java @@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor; import android.app.ActivityManager; import android.view.SurfaceControl; +import android.window.TransitionInfo; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -83,12 +84,14 @@ public interface WindowDecorViewModel { * @param taskSurface the surface of the task * @param startT the start transaction to be applied before the transition * @param finishT the finish transaction to restore states after the transition + * @param changeMode the type of change to the task */ void onTaskChanging( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, SurfaceControl.Transaction startT, - SurfaceControl.Transaction finishT); + SurfaceControl.Transaction finishT, + @TransitionInfo.TransitionMode int changeMode); /** * Notifies that the given task is about to close to give the window decoration a chance to diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 68e3d6e277e5..91a899c09407 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -629,16 +629,32 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> */ private void updateCaptionVisibility(View rootView, @NonNull RelayoutParams params) { mIsCaptionVisible = params.mIsCaptionVisible; + setCaptionVisibility(rootView, mIsCaptionVisible); } void setTaskDragResizer(TaskDragResizer taskDragResizer) { mTaskDragResizer = taskDragResizer; } + // TODO(b/346441962): Move these three methods closer to implementing or View-level classes to + // keep implementation details more encapsulated. + private void setCaptionVisibility(View rootView, boolean visible) { + if (rootView == null) { + return; + } + final int v = visible ? View.VISIBLE : View.GONE; + final View captionView = rootView.findViewById(getCaptionViewId()); + captionView.setVisibility(v); + } + int getCaptionHeightId(@WindowingMode int windowingMode) { return Resources.ID_NULL; } + int getCaptionViewId() { + return Resources.ID_NULL; + } + /** * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or * registers {@link #mOnDisplaysChangedListener} if it doesn't. @@ -683,6 +699,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> public void close() { Trace.beginSection("WindowDecoration#close"); mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener); + if (mTaskDragResizer != null) { + mTaskDragResizer.close(); + } final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get(); releaseViews(wct); mTaskOrganizer.applyTransaction(wct); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt index 9c55f0ecda93..7c5f34f979cd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt @@ -152,6 +152,8 @@ class DesktopTilingWindowDecoration( endBounds = destinationBounds, callback, ) + } else { + callback.invoke() } } return isTiled diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleAnimator.kt deleted file mode 100644 index f0a85306d177..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleAnimator.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.wm.shell.windowdecor - -import android.animation.ObjectAnimator -import android.view.View -import android.view.View.Visibility -import android.view.animation.PathInterpolator -import android.widget.ImageButton -import androidx.core.animation.doOnEnd -import com.android.wm.shell.shared.animation.Interpolators - -/** - * Animates the Desktop View's app handle. - */ -class AppHandleAnimator( - private val appHandleView: View, - private val captionHandle: ImageButton, -) { - companion object { - // Constants for animating the whole caption - private const val APP_HANDLE_ALPHA_FADE_IN_ANIMATION_DURATION_MS: Long = 275L - private const val APP_HANDLE_ALPHA_FADE_OUT_ANIMATION_DURATION_MS: Long = 340 - private val APP_HANDLE_ANIMATION_INTERPOLATOR = PathInterpolator( - 0.4f, - 0f, - 0.2f, - 1f - ) - - // Constants for animating the caption's handle - private const val HANDLE_ANIMATION_DURATION: Long = 100 - private val HANDLE_ANIMATION_INTERPOLATOR = Interpolators.FAST_OUT_SLOW_IN - } - - private var animator: ObjectAnimator? = null - - /** Animates the given caption view to the given visibility after a visibility change. */ - fun animateVisibilityChange(@Visibility visible: Int) { - when (visible) { - View.VISIBLE -> animateShowAppHandle() - else -> animateHideAppHandle() - } - } - - /** Animate appearance/disappearance of caption's handle. */ - fun animateCaptionHandleAlpha(startValue: Float, endValue: Float) { - cancel() - animator = ObjectAnimator.ofFloat(captionHandle, View.ALPHA, startValue, endValue).apply { - duration = HANDLE_ANIMATION_DURATION - interpolator = HANDLE_ANIMATION_INTERPOLATOR - start() - } - } - - private fun animateShowAppHandle() { - cancel() - appHandleView.alpha = 0f - appHandleView.visibility = View.VISIBLE - animator = ObjectAnimator.ofFloat(appHandleView, View.ALPHA, 1f).apply { - duration = APP_HANDLE_ALPHA_FADE_IN_ANIMATION_DURATION_MS - interpolator = APP_HANDLE_ANIMATION_INTERPOLATOR - start() - } - } - - private fun animateHideAppHandle() { - cancel() - animator = ObjectAnimator.ofFloat(appHandleView, View.ALPHA, 0f).apply { - duration = APP_HANDLE_ALPHA_FADE_OUT_ANIMATION_DURATION_MS - interpolator = APP_HANDLE_ANIMATION_INTERPOLATOR - doOnEnd { - appHandleView.visibility = View.GONE - } - start() - } - } - - /** - * Cancels any active animations. - */ - fun cancel() { - animator?.removeAllListeners() - animator?.cancel() - animator = null - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt index 7ab3303be618..0985587a330e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt @@ -15,6 +15,7 @@ */ package com.android.wm.shell.windowdecor.viewholder +import android.animation.ObjectAnimator import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.res.ColorStateList @@ -39,8 +40,8 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.Accessibilit import com.android.internal.policy.SystemBarUtils import com.android.window.flags.Flags import com.android.wm.shell.R +import com.android.wm.shell.shared.animation.Interpolators import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper -import com.android.wm.shell.windowdecor.AppHandleAnimator import com.android.wm.shell.windowdecor.WindowManagerWrapper import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer @@ -48,7 +49,7 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystem * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split). * It hosts a simple handle bar from which to initiate a drag motion to enter desktop mode. */ -internal class AppHandleViewHolder( +class AppHandleViewHolder( rootView: View, onCaptionTouchListener: View.OnTouchListener, onCaptionButtonClickListener: OnClickListener, @@ -56,19 +57,22 @@ internal class AppHandleViewHolder( private val handler: Handler ) : WindowDecorationViewHolder<AppHandleViewHolder.HandleData>(rootView) { + companion object { + private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100 + } + data class HandleData( val taskInfo: RunningTaskInfo, val position: Point, val width: Int, val height: Int, - val isCaptionVisible: Boolean + val showInputLayer: Boolean ) : Data() private lateinit var taskInfo: RunningTaskInfo private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption) private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle) private val inputManager = context.getSystemService(InputManager::class.java) - private val animator: AppHandleAnimator = AppHandleAnimator(rootView, captionHandle) private var statusBarInputLayerExists = false // An invisible View that takes up the same coordinates as captionHandle but is layered @@ -97,7 +101,7 @@ internal class AppHandleViewHolder( } override fun bindData(data: HandleData) { - bindData(data.taskInfo, data.position, data.width, data.height, data.isCaptionVisible) + bindData(data.taskInfo, data.position, data.width, data.height, data.showInputLayer) } private fun bindData( @@ -105,15 +109,13 @@ internal class AppHandleViewHolder( position: Point, width: Int, height: Int, - isCaptionVisible: Boolean + showInputLayer: Boolean ) { - setVisibility(isCaptionVisible) captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo)) this.taskInfo = taskInfo // If handle is not in status bar region(i.e., bottom stage in vertical split), // do not create an input layer - if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return - if (!isCaptionVisible) { + if (position.y >= SystemBarUtils.getStatusBarHeight(context) || !showInputLayer) { disposeStatusBarInputLayer() return } @@ -129,11 +131,11 @@ internal class AppHandleViewHolder( } override fun onHandleMenuOpened() { - animator.animateCaptionHandleAlpha(startValue = 1f, endValue = 0f) + animateCaptionHandleAlpha(startValue = 1f, endValue = 0f) } override fun onHandleMenuClosed() { - animator.animateCaptionHandleAlpha(startValue = 0f, endValue = 1f) + animateCaptionHandleAlpha(startValue = 0f, endValue = 1f) } private fun createStatusBarInputLayer(handlePosition: Point, @@ -237,16 +239,6 @@ internal class AppHandleViewHolder( } } - private fun setVisibility(visible: Boolean) { - val v = if (visible) View.VISIBLE else View.GONE - if (captionView.visibility == v) return - if (!DesktopModeFlags.ENABLE_DESKTOP_APP_HANDLE_ANIMATION.isTrue()) { - captionView.visibility = v - return - } - animator.animateVisibilityChange(v) - } - private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int { return if (shouldUseLightCaptionColors(taskInfo)) { context.getColor(R.color.desktop_mode_caption_handle_bar_light) @@ -272,7 +264,36 @@ internal class AppHandleViewHolder( } ?: false } - override fun close() { - animator.cancel() + /** Animate appearance/disappearance of caption handle as the handle menu is animated. */ + private fun animateCaptionHandleAlpha(startValue: Float, endValue: Float) { + val animator = + ObjectAnimator.ofFloat(captionHandle, View.ALPHA, startValue, endValue).apply { + duration = CAPTION_HANDLE_ANIMATION_DURATION + interpolator = Interpolators.FAST_OUT_SLOW_IN + } + animator.start() + } + + override fun close() {} + + /** Factory class for creating [AppHandleViewHolder] objects. */ + class Factory { + /** + * Create a [AppHandleViewHolder] object to handle caption view and status bar + * input layer logic. + */ + fun create( + rootView: View, + onCaptionTouchListener: View.OnTouchListener, + onCaptionButtonClickListener: OnClickListener, + windowManagerWrapper: WindowManagerWrapper, + handler: Handler, + ): AppHandleViewHolder = AppHandleViewHolder( + rootView, + onCaptionTouchListener, + onCaptionButtonClickListener, + windowManagerWrapper, + handler, + ) } } diff --git a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt new file mode 100644 index 000000000000..68f7ef09ee70 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt @@ -0,0 +1,168 @@ +/* + * Copyright 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.wm.shell + +import android.graphics.Point +import android.hardware.display.DisplayManager +import android.hardware.display.DisplayManager.DisplayListener +import android.os.Handler +import android.os.Looper +import android.provider.Settings +import android.util.Log +import androidx.test.platform.app.InstrumentationRegistry +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeoutOrNull +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * A TestRule to manage multiple simulated connected overlay displays. + */ +class SimulatedConnectedDisplayTestRule : TestRule { + + private val context = InstrumentationRegistry.getInstrumentation().targetContext + private val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation + private val displayManager = context.getSystemService(DisplayManager::class.java) + private val addedDisplays = mutableListOf<Int>() + + override fun apply(base: Statement, description: Description): Statement = + object : Statement() { + override fun evaluate() { + try { + base.evaluate() + } finally { + teardown() + } + } + } + + private fun teardown() { + cleanupTestDisplays() + } + + /** + * Adds multiple overlay displays with specified dimensions. Any existing overlay displays + * will be removed before adding the new ones. + * + * @param displays A list of [Point] objects, where each [Point] represents the + * width and height of a simulated display. + * @return List of displayIds of added displays. + */ + fun setupTestDisplays(displays: List<Point>): List<Int> = runBlocking { + // Cleanup any existing overlay displays. + cleanupTestDisplays() + + if (displays.isEmpty()) { + Log.w(TAG, "setupTestDisplays called with an empty list. No displays created.") + return@runBlocking emptyList() + } + + val displayAddedFlow: Flow<Int> = callbackFlow { + val listener = object : DisplayListener { + override fun onDisplayAdded(displayId: Int) { + trySend(displayId) + } + + override fun onDisplayRemoved(displayId: Int) {} + override fun onDisplayChanged(displayId: Int) {} + } + + val handler = Handler(Looper.getMainLooper()) + displayManager.registerDisplayListener(listener, handler) + + awaitClose { + displayManager.unregisterDisplayListener(listener) + } + } + + val displaySettings = displays.joinToString(separator = ";") { size -> + "${size.x}x${size.y}/$DEFAULT_DENSITY" + } + + // Add the overlay displays + Settings.Global.putString( + InstrumentationRegistry.getInstrumentation().context.contentResolver, + Settings.Global.OVERLAY_DISPLAY_DEVICES, displaySettings + ) + withTimeoutOrNull(TIMEOUT) { + displayAddedFlow.take(displays.size).collect { displayId -> + addedDisplays.add(displayId) + } + } ?: error("Timed out waiting for displays to be added.") + addedDisplays + } + + /** + * Adds multiple overlay displays with default dimensions. Any existing overlay displays + * will be removed before adding the new ones. + * + * @param count number of displays to add. + * @return List of displayIds of added displays. + */ + fun setupTestDisplays(count: Int): List<Int> { + val displays = List(count) { Point(DEFAULT_WIDTH, DEFAULT_HEIGHT) } + return setupTestDisplays(displays) + } + + private fun cleanupTestDisplays() = runBlocking { + if (addedDisplays.isEmpty()) { + return@runBlocking + } + + val displayRemovedFlow: Flow<Int> = callbackFlow { + val listener = object : DisplayListener { + override fun onDisplayAdded(displayId: Int) {} + override fun onDisplayRemoved(displayId: Int) { + trySend(displayId) + } + + override fun onDisplayChanged(displayId: Int) {} + } + val handler = Handler(Looper.getMainLooper()) + displayManager.registerDisplayListener(listener, handler) + + awaitClose { + displayManager.unregisterDisplayListener(listener) + } + } + + // Remove overlay displays + Settings.Global.putString( + InstrumentationRegistry.getInstrumentation().context.contentResolver, + Settings.Global.OVERLAY_DISPLAY_DEVICES, null) + + withTimeoutOrNull(TIMEOUT) { + displayRemovedFlow.take(addedDisplays.size).collect { displayId -> + addedDisplays.remove(displayId) + } + } ?: error("Timed out waiting for displays to be removed.") + } + + private companion object { + const val DEFAULT_WIDTH = 1280 + const val DEFAULT_HEIGHT = 720 + const val DEFAULT_DENSITY = 160 + const val TAG = "SimulatedConnectedDisplayTestRule" + val TIMEOUT = 10.seconds + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 05750a54f566..b139c000a1a9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -889,8 +889,8 @@ public class BackAnimationControllerTest extends ShellTestCase { */ private void doStartEvents(int startX, int moveX) { doMotionEvent(MotionEvent.ACTION_DOWN, startX); - mController.onThresholdCrossed(); doMotionEvent(MotionEvent.ACTION_MOVE, moveX); + mController.onThresholdCrossed(); } private void simulateRemoteAnimationStart() throws RemoteException { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java index 2ef6c558b0b5..43bcc3b61124 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java @@ -58,8 +58,7 @@ public class BackProgressAnimatorTest extends ShellTestCase { /* frameTime = */ 0, /* progress = */ progress, /* triggerBack = */ false, - /* swipeEdge = */ BackEvent.EDGE_LEFT, - /* departingAnimationTarget = */ null); + /* swipeEdge = */ BackEvent.EDGE_LEFT); } @Before diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt index 2cc52c5ab9ad..9d4cc49a7a65 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt @@ -224,8 +224,7 @@ class CustomCrossActivityBackAnimationTest : ShellTestCase() { /* frameTime = */ 0, /* progress = */ progress, /* triggerBack = */ false, - /* swipeEdge = */ BackEvent.EDGE_LEFT, - /* departingAnimationTarget = */ null + /* swipeEdge = */ BackEvent.EDGE_LEFT ) private fun createAnimationTarget(open: Boolean): RemoteAnimationTarget { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 7a7d88b80ce3..b3d2db6da6d5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -28,7 +28,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.app.Notification; @@ -804,7 +803,7 @@ public class BubbleDataTest extends ShellTestCase { mBubbleData.setListener(mListener); changeExpandedStateAtTime(true, 2000L); - verifyZeroInteractions(mListener); + verifyNoMoreInteractions(mListener); } /** diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java index 925ca0f1638d..b136bed3c942 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java @@ -16,6 +16,7 @@ package com.android.wm.shell.bubbles; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE; @@ -52,6 +53,7 @@ import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestSyncExecutor; +import com.android.wm.shell.bubbles.BubbleTransitions.DraggedBubbleIconToFullscreen; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.common.ShellExecutor; @@ -136,6 +138,7 @@ public class BubbleTransitionsTest extends ShellTestCase { when(tvtc.getTaskInfo()).thenReturn(taskInfo); when(tv.getController()).thenReturn(tvtc); when(mBubble.getTaskView()).thenReturn(tv); + when(tv.getTaskInfo()).thenReturn(taskInfo); mRepository.add(tvtc); return taskInfo; } @@ -285,4 +288,62 @@ public class BubbleTransitionsTest extends ShellTestCase { // directly tested. assertFalse(mTaskViewTransitions.hasPending()); } + + @Test + public void convertDraggedBubbleToFullscreen() { + ActivityManager.RunningTaskInfo taskInfo = setupBubble(); + final DraggedBubbleIconToFullscreen bt = + (DraggedBubbleIconToFullscreen) mBubbleTransitions + .startDraggedBubbleIconToFullscreen(mBubble); + verify(mTransitions).startTransition(anyInt(), any(), eq(bt)); + + final TransitionInfo info = new TransitionInfo(TRANSIT_TO_FRONT, 0); + final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token, + mock(SurfaceControl.class)); + chg.setMode(TRANSIT_TO_FRONT); + chg.setTaskInfo(taskInfo); + info.addChange(chg); + info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0)); + SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + Transitions.TransitionFinishCallback finishCb = wct -> {}; + bt.startAnimation(bt.mTransition, info, startT, finishT, finishCb); + verify(startT).apply(); + assertFalse(mTaskViewTransitions.hasPending()); + } + + @Test + public void convertFloatingBubbleToFullscreen() { + final BubbleExpandedView bev = mock(BubbleExpandedView.class); + final ViewRootImpl vri = mock(ViewRootImpl.class); + when(bev.getViewRootImpl()).thenReturn(vri); + when(mBubble.getBubbleBarExpandedView()).thenReturn(null); + when(mBubble.getExpandedView()).thenReturn(bev); + + ActivityManager.RunningTaskInfo taskInfo = setupBubble(); + final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertFromBubble( + mBubble, taskInfo); + final BubbleTransitions.ConvertFromBubble cfb = (BubbleTransitions.ConvertFromBubble) bt; + verify(mTransitions).startTransition(anyInt(), any(), eq(cfb)); + verify(mBubble).setPreparingTransition(eq(bt)); + assertTrue(mTaskViewTransitions.hasPending()); + + final TransitionInfo info = new TransitionInfo(TRANSIT_CHANGE, 0); + final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token, + mock(SurfaceControl.class)); + chg.setMode(TRANSIT_CHANGE); + chg.setTaskInfo(taskInfo); + info.addChange(chg); + info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0)); + SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + Transitions.TransitionFinishCallback finishCb = wct -> {}; + cfb.startAnimation(cfb.mTransition, info, startT, finishT, finishCb); + + // Can really only verify that it interfaces with the taskViewTransitions queue. + // The actual functioning of this is tightly-coupled with SurfaceFlinger and renderthread + // in order to properly synchronize surface manipulation with drawing and thus can't be + // directly tested. + assertFalse(mTaskViewTransitions.hasPending()); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java index c4b9c9ba43f1..b4b96791298d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java @@ -25,7 +25,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.floatThat; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import android.os.SystemClock; import android.testing.AndroidTestingRunner; @@ -84,7 +83,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase { verify(mMotionEventListener).onMove(0, -600); // Check that velocity up is about 5000 verify(mMotionEventListener).onUp(eq(0f), floatThat(f -> Math.round(f) == -5000)); - verifyZeroInteractions(mMotionEventListener); + verifyNoMoreInteractions(mMotionEventListener); verify(mInterceptTouchRunnable).run(); } @@ -94,8 +93,8 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase { mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 100)); mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 100)); - verifyZeroInteractions(mMotionEventListener); - verifyZeroInteractions(mInterceptTouchRunnable); + verifyNoMoreInteractions(mMotionEventListener); + verifyNoMoreInteractions(mInterceptTouchRunnable); } @Test @@ -107,7 +106,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase { verify(mMotionEventListener).onDown(0, 990); verify(mMotionEventListener).onMove(100, 0); verify(mMotionEventListener).onUp(0, 0); - verifyZeroInteractions(mMotionEventListener); + verifyNoMoreInteractions(mMotionEventListener); verify(mInterceptTouchRunnable).run(); } @@ -119,7 +118,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase { verify(mMotionEventListener).onDown(0, 990); verifyNoMoreInteractions(mMotionEventListener); - verifyZeroInteractions(mInterceptTouchRunnable); + verifyNoMoreInteractions(mInterceptTouchRunnable); } @Test @@ -129,7 +128,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase { verify(mMotionEventListener).onDown(0, 990); verify(mMotionEventListener).onCancel(); verifyNoMoreInteractions(mMotionEventListener); - verifyZeroInteractions(mInterceptTouchRunnable); + verifyNoMoreInteractions(mInterceptTouchRunnable); } private MotionEvent newEvent(int actionDown, float x, float y) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java index 3323740697f3..1472464e8143 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java @@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.content.Context; @@ -105,6 +105,6 @@ public class DevicePostureControllerTest extends ShellTestCase { int sameDevicePosture = mDevicePostureCaptor.getValue(); mDevicePostureController.onDevicePostureChanged(sameDevicePosture); - verifyZeroInteractions(mOnDevicePostureChangedListener); + verifyNoMoreInteractions(mOnDevicePostureChangedListener); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java index ee9d17706372..a53277a3764e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -32,7 +32,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.graphics.Insets; import android.graphics.Point; @@ -108,26 +108,26 @@ public class DisplayImeControllerTest extends ShellTestCase { public void insetsControlChanged_schedulesNoWorkOnExecutor() { Looper.prepare(); mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl()); - verifyZeroInteractions(mExecutor); + verifyNoMoreInteractions(mExecutor); } @Test public void insetsChanged_schedulesNoWorkOnExecutor() { Looper.prepare(); mPerDisplay.insetsChanged(insetsStateWithIme(false)); - verifyZeroInteractions(mExecutor); + verifyNoMoreInteractions(mExecutor); } @Test public void showInsets_schedulesNoWorkOnExecutor() { mPerDisplay.showInsets(ime(), true /* fromIme */, ImeTracker.Token.empty()); - verifyZeroInteractions(mExecutor); + verifyNoMoreInteractions(mExecutor); } @Test public void hideInsets_schedulesNoWorkOnExecutor() { mPerDisplay.hideInsets(ime(), true /* fromIme */, ImeTracker.Token.empty()); - verifyZeroInteractions(mExecutor); + verifyNoMoreInteractions(mExecutor); } // With the refactor, the control's isInitiallyVisible is used to apply to the IME, therefore @@ -135,7 +135,7 @@ public class DisplayImeControllerTest extends ShellTestCase { @Test @RequiresFlagsDisabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER) public void reappliesVisibilityToChangedLeash() { - verifyZeroInteractions(mT); + verifyNoMoreInteractions(mT); mPerDisplay.mImeShowing = false; mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java index 96d202ce3a85..7d1866975848 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java @@ -29,7 +29,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; @@ -145,7 +145,7 @@ public class TabletopModeControllerTest extends ShellTestCase { mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0); mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration); - verifyZeroInteractions(mOnTabletopModeChangedListener); + verifyNoMoreInteractions(mOnTabletopModeChangedListener); } // Test cases starting from folded state (DEVICE_POSTURE_CLOSED) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java index e92e243172f7..a2066dbf7a5f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java @@ -21,7 +21,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.AppOpsManager; @@ -157,7 +157,7 @@ public class PipAppOpsListenerTest { opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE), packageName); - verifyZeroInteractions(mMockExecutor); + verifyNoMoreInteractions(mMockExecutor); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java index 4cdb1e5b5d10..25dbc64f83de 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.window.flags.Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP; +import static com.android.wm.shell.Flags.FLAG_ENABLE_PIP2; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -119,12 +120,28 @@ public class PipDesktopStateTest { } @Test - @EnableFlags(FLAG_ENABLE_CONNECTED_DISPLAYS_PIP) + @EnableFlags({ + FLAG_ENABLE_CONNECTED_DISPLAYS_PIP, FLAG_ENABLE_PIP2 + }) public void isConnectedDisplaysPipEnabled_returnsTrue() { assertTrue(mPipDesktopState.isConnectedDisplaysPipEnabled()); } @Test + public void isPipInDesktopMode_anyDeskActive_returnsTrue() { + when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true); + + assertTrue(mPipDesktopState.isPipInDesktopMode()); + } + + @Test + public void isPipInDesktopMode_noDeskActive_returnsFalse() { + when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false); + + assertFalse(mPipDesktopState.isPipInDesktopMode()); + } + + @Test public void getOutPipWindowingMode_exitToDesktop_displayFreeform_returnsUndefined() { when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true); setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java index 1756aad8fc9b..cc23d9a75fcd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java @@ -21,7 +21,6 @@ import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_DEFAU import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_MAX; import static com.android.wm.shell.common.pip.PipDoubleTapHelper.nextSizeSpec; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.graphics.Point; @@ -41,131 +40,75 @@ import org.mockito.Mock; */ @RunWith(AndroidTestingRunner.class) public class PipDoubleTapHelperTest extends ShellTestCase { - // represents the current pip window state and has information on current - // max, min, and normal sizes - @Mock private PipBoundsState mBoundStateMock; - // tied to boundsStateMock.getBounds() in setUp() - @Mock private Rect mBoundsMock; - - // represents the most recent manually resized bounds - // i.e. dimensions from the most recent pinch in/out - @Mock private Rect mUserResizeBoundsMock; - - // actual dimensions of the pip screen bounds - private static final int MAX_WIDTH = 100; - private static final int DEFAULT_WIDTH = 40; - private static final int MIN_WIDTH = 10; - - private static final int AVERAGE_WIDTH = (MAX_WIDTH + MIN_WIDTH) / 2; - - /** - * Initializes mocks and assigns values for different pip screen bounds. - */ + @Mock private PipBoundsState mMockPipBoundsState; + + // Actual dimension guidelines of the PiP bounds. + private static final int MAX_EDGE_SIZE = 100; + private static final int DEFAULT_EDGE_SIZE = 60; + private static final int MIN_EDGE_SIZE = 50; + private static final int AVERAGE_EDGE_SIZE = (MAX_EDGE_SIZE + MIN_EDGE_SIZE) / 2; + @Before public void setUp() { // define pip bounds - when(mBoundStateMock.getMaxSize()).thenReturn(new Point(MAX_WIDTH, 20)); - when(mBoundStateMock.getMinSize()).thenReturn(new Point(MIN_WIDTH, 2)); + when(mMockPipBoundsState.getMaxSize()).thenReturn(new Point(MAX_EDGE_SIZE, MAX_EDGE_SIZE)); + when(mMockPipBoundsState.getMinSize()).thenReturn(new Point(MIN_EDGE_SIZE, MIN_EDGE_SIZE)); - Rect rectMock = mock(Rect.class); - when(rectMock.width()).thenReturn(DEFAULT_WIDTH); - when(mBoundStateMock.getNormalBounds()).thenReturn(rectMock); + final Rect normalBounds = new Rect(0, 0, DEFAULT_EDGE_SIZE, DEFAULT_EDGE_SIZE); + when(mMockPipBoundsState.getNormalBounds()).thenReturn(normalBounds); + } - when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH); - when(mBoundStateMock.getBounds()).thenReturn(mBoundsMock); + @Test + public void nextSizeSpec_resizedWiderThanAverage_returnDefaultThenCustom() { + final int resizeEdgeSize = (MAX_EDGE_SIZE + AVERAGE_EDGE_SIZE) / 2; + final Rect userResizeBounds = new Rect(0, 0, resizeEdgeSize, resizeEdgeSize); + when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds); + Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_DEFAULT); + + // once we toggle to DEFAULT only PiP bounds state gets updated - not the user resize bounds + when(mMockPipBoundsState.getBounds()).thenReturn( + new Rect(0, 0, DEFAULT_EDGE_SIZE, DEFAULT_EDGE_SIZE)); + Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_CUSTOM); + } + + @Test + public void nextSizeSpec_resizedSmallerThanAverage_returnMaxThenCustom() { + final int resizeEdgeSize = (AVERAGE_EDGE_SIZE + MIN_EDGE_SIZE) / 2; + final Rect userResizeBounds = new Rect(0, 0, resizeEdgeSize, resizeEdgeSize); + when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds); + Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_MAX); + + // Once we toggle to MAX our screen size gets updated but not the user resize bounds + when(mMockPipBoundsState.getBounds()).thenReturn( + new Rect(0, 0, MAX_EDGE_SIZE, MAX_EDGE_SIZE)); + Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_CUSTOM); } - /** - * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}. - * - * <p>when the user resizes the screen to a larger than the average but not the maximum width, - * then we toggle between {@code PipSizeSpec.CUSTOM} and {@code PipSizeSpec.DEFAULT} - */ @Test - public void testNextScreenSize_resizedWiderThanAverage_returnDefaultThenCustom() { - // make the user resize width in between MAX and average - when(mUserResizeBoundsMock.width()).thenReturn((MAX_WIDTH + AVERAGE_WIDTH) / 2); - // make current bounds same as resized bound since no double tap yet - when(mBoundsMock.width()).thenReturn((MAX_WIDTH + AVERAGE_WIDTH) / 2); - - // then nextScreenSize() i.e. double tapping should - // toggle to DEFAULT state - Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock), - SIZE_SPEC_DEFAULT); - - // once we toggle to DEFAULT our screen size gets updated - // but not the user resize bounds - when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH); - - // then nextScreenSize() i.e. double tapping should - // toggle to CUSTOM state - Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock), - SIZE_SPEC_CUSTOM); + public void nextSizeSpec_resizedToMax_returnDefault() { + final Rect userResizeBounds = new Rect(0, 0, MAX_EDGE_SIZE, MAX_EDGE_SIZE); + when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds); + Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_DEFAULT); } - /** - * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}. - * - * <p>when the user resizes the screen to a smaller than the average but not the default width, - * then we toggle between {@code PipSizeSpec.CUSTOM} and {@code PipSizeSpec.MAX} - */ @Test - public void testNextScreenSize_resizedNarrowerThanAverage_returnMaxThenCustom() { - // make the user resize width in between MIN and average - when(mUserResizeBoundsMock.width()).thenReturn((MIN_WIDTH + AVERAGE_WIDTH) / 2); - // make current bounds same as resized bound since no double tap yet - when(mBoundsMock.width()).thenReturn((MIN_WIDTH + AVERAGE_WIDTH) / 2); - - // then nextScreenSize() i.e. double tapping should - // toggle to MAX state - Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock), - SIZE_SPEC_MAX); - - // once we toggle to MAX our screen size gets updated - // but not the user resize bounds - when(mBoundsMock.width()).thenReturn(MAX_WIDTH); - - // then nextScreenSize() i.e. double tapping should - // toggle to CUSTOM state - Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock), - SIZE_SPEC_CUSTOM); + public void nextSizeSpec_resizedToDefault_returnMax() { + final Rect userResizeBounds = new Rect(0, 0, DEFAULT_EDGE_SIZE, DEFAULT_EDGE_SIZE); + when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds); + Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_MAX); } - /** - * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}. - * - * <p>when the user resizes the screen to exactly the maximum width - * then we toggle to {@code PipSizeSpec.DEFAULT} - */ @Test - public void testNextScreenSize_resizedToMax_returnDefault() { - // the resized width is the same as MAX_WIDTH - when(mUserResizeBoundsMock.width()).thenReturn(MAX_WIDTH); - // the current bounds are also at MAX_WIDTH - when(mBoundsMock.width()).thenReturn(MAX_WIDTH); - - // then nextScreenSize() i.e. double tapping should - // toggle to DEFAULT state - Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock), - SIZE_SPEC_DEFAULT); + public void nextSizeSpec_resizedToAlmostMax_returnDefault() { + final Rect userResizeBounds = new Rect(0, 0, MAX_EDGE_SIZE, MAX_EDGE_SIZE - 1); + when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds); + Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_DEFAULT); } - /** - * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}. - * - * <p>when the user resizes the screen to exactly the default width - * then we toggle to {@code PipSizeSpec.MAX} - */ @Test - public void testNextScreenSize_resizedToDefault_returnMax() { - // the resized width is the same as DEFAULT_WIDTH - when(mUserResizeBoundsMock.width()).thenReturn(DEFAULT_WIDTH); - // the current bounds are also at DEFAULT_WIDTH - when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH); - - // then nextScreenSize() i.e. double tapping should - // toggle to MAX state - Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock), - SIZE_SPEC_MAX); + public void nextSizeSpec_resizedToAlmostMin_returnMax() { + final Rect userResizeBounds = new Rect(0, 0, MIN_EDGE_SIZE, MIN_EDGE_SIZE + 1); + when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds); + Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_MAX); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt index 275d7b73a112..2aebcdcc3bf5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt @@ -61,10 +61,10 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { @Mock lateinit var testExecutor: ShellExecutor @Mock lateinit var displayController: DisplayController @Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories - @Mock private lateinit var mockDesktopRepositoryInitializer: DesktopRepositoryInitializer @Mock private lateinit var mockDesktopRepository: DesktopRepository @Mock private lateinit var mockDesktopTasksController: DesktopTasksController @Mock private lateinit var desktopDisplayModeController: DesktopDisplayModeController + private val desktopRepositoryInitializer = FakeDesktopRepositoryInitializer() private val testScope = TestScope() private lateinit var mockitoSession: StaticMockitoSession @@ -90,7 +90,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { shellInit, testScope.backgroundScope, displayController, - mockDesktopRepositoryInitializer, + desktopRepositoryInitializer, mockDesktopUserRepositories, mockDesktopTasksController, desktopDisplayModeController, @@ -109,12 +109,10 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { @Test fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_createsDesk() = testScope.runTest { - val stateFlow = MutableStateFlow(false) - whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow) whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) - stateFlow.emit(true) + desktopRepositoryInitializer.initialize(mockDesktopUserRepositories) runCurrent() verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY) @@ -123,8 +121,6 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { @Test fun testDisplayAdded_supportsDesks_desktopRepositoryNotInitialized_doesNotCreateDesk() = testScope.runTest { - val stateFlow = MutableStateFlow(false) - whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow) whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) @@ -136,13 +132,11 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { @Test fun testDisplayAdded_supportsDesks_desktopRepositoryInitializedTwice_createsDeskOnce() = testScope.runTest { - val stateFlow = MutableStateFlow(false) - whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow) whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) - stateFlow.emit(true) - stateFlow.emit(true) + desktopRepositoryInitializer.initialize(mockDesktopUserRepositories) + desktopRepositoryInitializer.initialize(mockDesktopUserRepositories) runCurrent() verify(mockDesktopTasksController, times(1)).createDesk(DEFAULT_DISPLAY) @@ -151,13 +145,11 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { @Test fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_deskExists_doesNotCreateDesk() = testScope.runTest { - val stateFlow = MutableStateFlow(false) - whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow) whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(1) onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) - stateFlow.emit(true) + desktopRepositoryInitializer.initialize(mockDesktopUserRepositories) runCurrent() verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY) @@ -203,4 +195,15 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId) verify(desktopDisplayModeController).refreshDisplayWindowingMode() } + + private class FakeDesktopRepositoryInitializer : DesktopRepositoryInitializer { + override var deskRecreationFactory: DesktopRepositoryInitializer.DeskRecreationFactory = + DesktopRepositoryInitializer.DeskRecreationFactory { _, _, deskId -> deskId } + + override val isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false) + + override fun initialize(userRepositories: DesktopUserRepositories) { + isInitialized.value = true + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt index 450989dd334d..da6a67c679ff 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt @@ -26,28 +26,36 @@ import android.os.Binder import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.annotations.UsesFlags import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS +import android.view.Display import android.view.Display.DEFAULT_DISPLAY import android.view.IWindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.window.DisplayAreaInfo import android.window.WindowContainerTransaction import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never +import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.server.display.feature.flags.Flags as DisplayFlags import com.android.window.flags.Flags import com.android.wm.shell.MockToken import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.common.DisplayController import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -60,6 +68,7 @@ import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness /** * Test class for [DesktopDisplayModeController] @@ -68,6 +77,7 @@ import org.mockito.kotlin.whenever */ @SmallTest @RunWith(TestParameterInjector::class) +@UsesFlags(com.android.server.display.feature.flags.Flags::class) class DesktopDisplayModeControllerTest( @TestParameter(valuesProvider = FlagsParameterizationProvider::class) flags: FlagsParameterization @@ -79,6 +89,7 @@ class DesktopDisplayModeControllerTest( private val desktopWallpaperActivityTokenProvider = mock<DesktopWallpaperActivityTokenProvider>() private val inputManager = mock<InputManager>() + private val displayController = mock<DisplayController>() private val mainHandler = mock<Handler>() private lateinit var controller: DesktopDisplayModeController @@ -90,6 +101,13 @@ class DesktopDisplayModeControllerTest( TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build() private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) private val wallpaperToken = MockToken().token() + private val defaultDisplay = mock<Display>() + private val externalDisplay = mock<Display>() + + private lateinit var extendedDisplaySettingsRestoreSession: + ExtendedDisplaySettingsRestoreSession + + private lateinit var mockitoSession: StaticMockitoSession init { mSetFlagsRule.setFlagsParameterization(flags) @@ -97,6 +115,13 @@ class DesktopDisplayModeControllerTest( @Before fun setUp() { + mockitoSession = + mockitoSession() + .strictness(Strictness.LENIENT) + .mockStatic(DesktopModeStatus::class.java) + .startMocking() + extendedDisplaySettingsRestoreSession = + ExtendedDisplaySettingsRestoreSession(context.contentResolver) whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder()) whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) .thenReturn(defaultTDA) @@ -109,42 +134,57 @@ class DesktopDisplayModeControllerTest( shellTaskOrganizer, desktopWallpaperActivityTokenProvider, inputManager, + displayController, mainHandler, ) runningTasks.add(freeformTask) runningTasks.add(fullscreenTask) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks)) whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken) + whenever(displayController.getDisplay(DEFAULT_DISPLAY)).thenReturn(defaultDisplay) + whenever(displayController.getDisplay(EXTERNAL_DISPLAY_ID)).thenReturn(externalDisplay) setTabletModeStatus(SwitchState.UNKNOWN) + whenever( + DesktopModeStatus.isDesktopModeSupportedOnDisplay( + context, + defaultDisplay + ) + ).thenReturn(true) + } + + @After + fun tearDown() { + extendedDisplaySettingsRestoreSession.restore() + mockitoSession.finishMocking() } private fun testDisplayWindowingModeSwitchOnDisplayConnected(expectToSwitch: Boolean) { defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN) - ExtendedDisplaySettingsSession(context.contentResolver, 1).use { - connectExternalDisplay() - if (expectToSwitch) { - // Assumes [connectExternalDisplay] properly triggered the switching transition. - // Will verify the transition later along with [disconnectExternalDisplay]. - defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM - } - disconnectExternalDisplay() + setExtendedMode(true) + + connectExternalDisplay() + if (expectToSwitch) { + // Assumes [connectExternalDisplay] properly triggered the switching transition. + // Will verify the transition later along with [disconnectExternalDisplay]. + defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + } + disconnectExternalDisplay() - if (expectToSwitch) { - val arg = argumentCaptor<WindowContainerTransaction>() - verify(transitions, times(2)) - .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) - assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) - assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) - assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) - } else { - verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull()) - } + if (expectToSwitch) { + val arg = argumentCaptor<WindowContainerTransaction>() + verify(transitions, times(2)) + .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) + assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + } else { + verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull()) } } @@ -176,15 +216,16 @@ class DesktopDisplayModeControllerTest( disconnectExternalDisplay() } setTabletModeStatus(tabletModeStatus) - - ExtendedDisplaySettingsSession( - context.contentResolver, - if (param.extendedDisplayEnabled) 1 else 0, + setExtendedMode(param.extendedDisplayEnabled) + whenever( + DesktopModeStatus.isDesktopModeSupportedOnDisplay( + context, + defaultDisplay ) - .use { - assertThat(controller.getTargetWindowingModeForDefaultDisplay()) - .isEqualTo(param.expectedWindowingMode) - } + ).thenReturn(param.isDefaultDisplayDesktopEligible) + + assertThat(controller.getTargetWindowingModeForDefaultDisplay()) + .isEqualTo(param.expectedWindowingMode) } @Test @@ -199,15 +240,16 @@ class DesktopDisplayModeControllerTest( disconnectExternalDisplay() } setTabletModeStatus(param.tabletModeStatus) - - ExtendedDisplaySettingsSession( - context.contentResolver, - if (param.extendedDisplayEnabled) 1 else 0, + setExtendedMode(param.extendedDisplayEnabled) + whenever( + DesktopModeStatus.isDesktopModeSupportedOnDisplay( + context, + defaultDisplay ) - .use { - assertThat(controller.getTargetWindowingModeForDefaultDisplay()) - .isEqualTo(param.expectedWindowingMode) - } + ).thenReturn(param.isDefaultDisplayDesktopEligible) + + assertThat(controller.getTargetWindowingModeForDefaultDisplay()) + .isEqualTo(param.expectedWindowingMode) } @Test @@ -215,18 +257,16 @@ class DesktopDisplayModeControllerTest( fun displayWindowingModeSwitch_existingTasksOnConnected() { defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN) + setExtendedMode(true) - ExtendedDisplaySettingsSession(context.contentResolver, 1).use { - connectExternalDisplay() + connectExternalDisplay() - val arg = argumentCaptor<WindowContainerTransaction>() - verify(transitions, times(1)) - .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) - assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) - assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) - } + val arg = argumentCaptor<WindowContainerTransaction>() + verify(transitions, times(1)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) + assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) } @Test @@ -236,18 +276,16 @@ class DesktopDisplayModeControllerTest( whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { WINDOWING_MODE_FULLSCREEN } + setExtendedMode(true) - ExtendedDisplaySettingsSession(context.contentResolver, 1).use { - disconnectExternalDisplay() + disconnectExternalDisplay() - val arg = argumentCaptor<WindowContainerTransaction>() - verify(transitions, times(1)) - .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) - assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) - } + val arg = argumentCaptor<WindowContainerTransaction>() + verify(transitions, times(1)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) + assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) } private fun connectExternalDisplay() { @@ -266,18 +304,30 @@ class DesktopDisplayModeControllerTest( whenever(inputManager.isInTabletMode()).thenReturn(status.value) } - private class ExtendedDisplaySettingsSession( - private val contentResolver: ContentResolver, - private val overrideValue: Int, - ) : AutoCloseable { + private fun setExtendedMode(enabled: Boolean) { + if (DisplayFlags.enableDisplayContentModeManagement()) { + whenever( + DesktopModeStatus.isDesktopModeSupportedOnDisplay( + context, + externalDisplay + ) + ).thenReturn(enabled) + } else { + Settings.Global.putInt( + context.contentResolver, + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, + if (enabled) 1 else 0, + ) + } + } + + private class ExtendedDisplaySettingsRestoreSession( + private val contentResolver: ContentResolver + ) { private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0) - init { - Settings.Global.putInt(contentResolver, settingName, overrideValue) - } - - override fun close() { + fun restore() { Settings.Global.putInt(contentResolver, settingName, initialValue) } } @@ -287,7 +337,8 @@ class DesktopDisplayModeControllerTest( context: TestParameterValuesProvider.Context ): List<FlagsParameterization> { return FlagsParameterization.allCombinationsOf( - Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH + Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH, + DisplayFlags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT, ) } } @@ -305,54 +356,119 @@ class DesktopDisplayModeControllerTest( val defaultWindowingMode: Int, val hasExternalDisplay: Boolean, val extendedDisplayEnabled: Boolean, + val isDefaultDisplayDesktopEligible: Boolean, val expectedWindowingMode: Int, ) { - FREEFORM_EXTERNAL_EXTENDED( + FREEFORM_EXTERNAL_EXTENDED_NO_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = true, extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + FULLSCREEN_EXTERNAL_EXTENDED_NO_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = true, + extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + FREEFORM_NO_EXTERNAL_EXTENDED_NO_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + hasExternalDisplay = false, + extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + FULLSCREEN_NO_EXTERNAL_EXTENDED_NO_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = false, + extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + FREEFORM_EXTERNAL_MIRROR_NO_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + hasExternalDisplay = true, + extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + FULLSCREEN_EXTERNAL_MIRROR_NO_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = true, + extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + FREEFORM_NO_EXTERNAL_MIRROR_NO_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + hasExternalDisplay = false, + extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - FULLSCREEN_EXTERNAL_EXTENDED( + FULLSCREEN_NO_EXTERNAL_MIRROR_NO_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = false, + extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + FREEFORM_EXTERNAL_EXTENDED_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = true, extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - FREEFORM_NO_EXTERNAL_EXTENDED( + FULLSCREEN_EXTERNAL_EXTENDED_PROJECTED( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = true, + extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + FREEFORM_NO_EXTERNAL_EXTENDED_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = false, extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - FULLSCREEN_NO_EXTERNAL_EXTENDED( + FULLSCREEN_NO_EXTERNAL_EXTENDED_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = false, extendedDisplayEnabled = true, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - FREEFORM_EXTERNAL_MIRROR( + FREEFORM_EXTERNAL_MIRROR_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = true, extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - FULLSCREEN_EXTERNAL_MIRROR( + FULLSCREEN_EXTERNAL_MIRROR_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = true, extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - FREEFORM_NO_EXTERNAL_MIRROR( + FREEFORM_NO_EXTERNAL_MIRROR_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = false, extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - FULLSCREEN_NO_EXTERNAL_MIRROR( + FULLSCREEN_NO_EXTERNAL_MIRROR_PROJECTED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = false, extendedDisplayEnabled = false, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), } @@ -361,78 +477,175 @@ class DesktopDisplayModeControllerTest( val hasExternalDisplay: Boolean, val extendedDisplayEnabled: Boolean, val tabletModeStatus: SwitchState, + val isDefaultDisplayDesktopEligible: Boolean, val expectedWindowingMode: Int, ) { - EXTERNAL_EXTENDED_TABLET( + EXTERNAL_EXTENDED_TABLET_NO_PROJECTED( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - NO_EXTERNAL_EXTENDED_TABLET( + NO_EXTERNAL_EXTENDED_TABLET_NO_PROJECTED( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_MIRROR_TABLET( + EXTERNAL_MIRROR_TABLET_NO_PROJECTED( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - NO_EXTERNAL_MIRROR_TABLET( + NO_EXTERNAL_MIRROR_TABLET_NO_PROJECTED( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_EXTENDED_CLAMSHELL( + EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - NO_EXTERNAL_EXTENDED_CLAMSHELL( + NO_EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - EXTERNAL_MIRROR_CLAMSHELL( + EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - NO_EXTERNAL_MIRROR_CLAMSHELL( + NO_EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - EXTERNAL_EXTENDED_UNKNOWN( + EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - NO_EXTERNAL_EXTENDED_UNKNOWN( + NO_EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = true, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_EXTENDED_TABLET_PROJECTED( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_EXTENDED_TABLET_PROJECTED( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_MIRROR_TABLET_PROJECTED( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_MIRROR_TABLET_PROJECTED( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_MIRROR_CLAMSHELL_PROJECTED( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_MIRROR_CLAMSHELL_PROJECTED( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_EXTENDED_UNKNOWN_PROJECTED( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_EXTENDED_UNKNOWN_PROJECTED( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_MIRROR_UNKNOWN( + EXTERNAL_MIRROR_UNKNOWN_PROJECTED( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - NO_EXTERNAL_MIRROR_UNKNOWN( + NO_EXTERNAL_MIRROR_UNKNOWN_PROJECTED( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt index 8a5acfa70f50..695cf600b359 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt @@ -23,7 +23,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker import com.android.dx.mockito.inline.extended.ExtendedMockito.verify -import com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions +import com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.window.flags.Flags @@ -102,7 +102,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(sessionId), ) } - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -127,7 +127,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(sessionId), ) } - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -135,7 +135,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT) verifyNoLogging() - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -157,7 +157,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(sessionId), ) } - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) assertThat(desktopModeEventLogger.currentSessionId.get()).isEqualTo(NO_SESSION_ID) } @@ -166,7 +166,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { desktopModeEventLogger.logTaskAdded(TASK_UPDATE) verifyNoLogging() - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -205,7 +205,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_FOCUS_REASON), ) } - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -213,7 +213,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { desktopModeEventLogger.logTaskRemoved(TASK_UPDATE) verifyNoLogging() - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -252,7 +252,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_FOCUS_REASON), ) } - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -260,7 +260,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE) verifyNoLogging() - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -302,7 +302,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_FOCUS_REASON), ) } - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -346,7 +346,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_FOCUS_REASON), ) } - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -390,7 +390,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_FOCUS_REASON), ) } - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -434,7 +434,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(FocusReason.UNKNOWN.reason), ) } - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -446,7 +446,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { ) verifyNoLogging() - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @@ -485,7 +485,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { ) verifyNoLogging() - verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java)) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index b7d25b5255f8..bd37610ae65b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -83,7 +83,7 @@ import org.mockito.kotlin.same import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever /** @@ -596,7 +596,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { .logTaskRemoved( eq(DEFAULT_TASK_UPDATE.copy(minimizeReason = MinimizeReason.MINIMIZE_BUTTON)) ) - verifyZeroInteractions(desktopModeEventLogger) + verifyNoMoreInteractions(desktopModeEventLogger) } @Test @@ -668,7 +668,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { .logTaskInfoChanged( eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1)) ) - verifyZeroInteractions(desktopModeEventLogger) + verifyNoMoreInteractions(desktopModeEventLogger) } @Test @@ -701,7 +701,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { ) ) ) - verifyZeroInteractions(desktopModeEventLogger) + verifyNoMoreInteractions(desktopModeEventLogger) } @Test @@ -729,7 +729,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { ) ) ) - verifyZeroInteractions(desktopModeEventLogger) + verifyNoMoreInteractions(desktopModeEventLogger) } @Test @@ -753,7 +753,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { .logTaskInfoChanged( eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2)) ) - verifyZeroInteractions(desktopModeEventLogger) + verifyNoMoreInteractions(desktopModeEventLogger) // task 2 resize val newTaskInfo2 = @@ -781,7 +781,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { ) ) ) - verifyZeroInteractions(desktopModeEventLogger) + verifyNoMoreInteractions(desktopModeEventLogger) } @Test @@ -892,14 +892,14 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { eq(taskUpdate.visibleTaskCount.toString()), ) } - verifyZeroInteractions(desktopModeEventLogger) + verifyNoMoreInteractions(desktopModeEventLogger) } private fun verifyTaskRemovedAndExitLogging(exitReason: ExitReason, taskUpdate: TaskUpdate) { assertFalse(transitionObserver.isSessionActive) verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(taskUpdate)) verify(desktopModeEventLogger, times(1)).logSessionExit(eq(exitReason)) - verifyZeroInteractions(desktopModeEventLogger) + verifyNoMoreInteractions(desktopModeEventLogger) } private companion object { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt new file mode 100644 index 000000000000..ef394d81cc57 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt @@ -0,0 +1,98 @@ +/* + * 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.wm.shell.desktopmode + +import android.app.WindowConfiguration.WINDOWING_MODE_PINNED +import android.os.Binder +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.testing.AndroidTestingRunner +import android.view.WindowManager.TRANSIT_PIP +import android.window.TransitionInfo +import androidx.test.filters.SmallTest +import com.android.window.flags.Flags +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +/** + * Tests for [DesktopPipTransitionObserver]. + * + * Build/Install/Run: atest WMShellUnitTests:DesktopPipTransitionObserverTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DesktopPipTransitionObserverTest : ShellTestCase() { + + @JvmField @Rule val setFlagsRule = SetFlagsRule() + + private lateinit var observer: DesktopPipTransitionObserver + + private val transition = Binder() + private var onSuccessInvokedCount = 0 + + @Before + fun setUp() { + observer = DesktopPipTransitionObserver() + + onSuccessInvokedCount = 0 + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun onTransitionReady_taskInPinnedWindowingMode_onSuccessInvoked() { + val taskId = 1 + val pipTransition = createPendingPipTransition(taskId) + val successfulChange = createChange(taskId, WINDOWING_MODE_PINNED) + observer.addPendingPipTransition(pipTransition) + + observer.onTransitionReady( + transition = transition, + info = TransitionInfo( + TRANSIT_PIP, /* flags= */ + 0 + ).apply { addChange(successfulChange) }, + ) + + assertThat(onSuccessInvokedCount).isEqualTo(1) + } + + private fun createPendingPipTransition( + taskId: Int + ): DesktopPipTransitionObserver.PendingPipTransition { + return DesktopPipTransitionObserver.PendingPipTransition( + token = transition, + taskId = taskId, + onSuccess = { onSuccessInvokedCount += 1 }, + ) + } + + private fun createChange(taskId: Int, windowingMode: Int): TransitionInfo.Change { + return TransitionInfo.Change(mock(), mock()).apply { + taskInfo = + TestRunningTaskInfoBuilder() + .setTaskId(taskId) + .setWindowingMode(windowingMode) + .build() + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index f84a1a38bdfc..a10aeca95bce 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -275,6 +275,18 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { } @Test + @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun removeActiveTask_excludingDesk_leavesTaskInDesk() { + repo.addDesk(displayId = 2, deskId = 11) + repo.addDesk(displayId = 3, deskId = 12) + repo.addTaskToDesk(displayId = 3, deskId = 12, taskId = 100, isVisible = true) + + repo.removeActiveTask(taskId = 100, excludedDeskId = 12) + + assertThat(repo.getActiveTaskIdsInDesk(12)).contains(100) + } + + @Test fun isActiveTask_nonExistingTask_returnsFalse() { assertThat(repo.isActiveTask(99)).isFalse() } @@ -1225,36 +1237,6 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { } @Test - fun setTaskInPip_savedAsMinimizedPipInDisplay() { - assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isFalse() - - repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true) - - assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue() - } - - @Test - fun removeTaskInPip_removedAsMinimizedPipInDisplay() { - repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true) - assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue() - - repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = false) - - assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isFalse() - } - - @Test - fun setTaskInPip_multipleDisplays_bothAreInPip() { - repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) - repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) - repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true) - repo.setTaskInPip(SECOND_DISPLAY, taskId = 2, enterPip = true) - - assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue() - assertThat(repo.isTaskMinimizedPipInDisplay(SECOND_DISPLAY, taskId = 2)).isTrue() - } - - @Test @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun addTask_deskDoesNotExists_createsDesk() { repo.addTask(displayId = 999, taskId = 6, isVisible = true) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index b2553b0bd30f..01ea986d3260 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -57,6 +57,7 @@ import android.platform.test.flag.junit.FlagsParameterization import android.testing.TestableContext import android.view.Display import android.view.Display.DEFAULT_DISPLAY +import android.view.Display.INVALID_DISPLAY import android.view.DragEvent import android.view.Gravity import android.view.MotionEvent @@ -264,6 +265,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Mock private lateinit var desksOrganizer: DesksOrganizer @Mock private lateinit var userProfileContexts: UserProfileContexts @Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver + @Mock private lateinit var desktopPipTransitionObserver: DesktopPipTransitionObserver @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var mockDisplayContext: Context @Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler @@ -392,6 +394,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken) whenever(userProfileContexts[anyInt()]).thenReturn(context) whenever(userProfileContexts.getOrCreate(anyInt())).thenReturn(context) + whenever(freeformTaskTransitionStarter.startPipTransition(any())).thenReturn(Binder()) controller = createController() controller.setSplitScreenController(splitScreenController) @@ -456,6 +459,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() overviewToDesktopTransitionObserver, desksOrganizer, desksTransitionsObserver, + Optional.of(desktopPipTransitionObserver), userProfileContexts, desktopModeCompatPolicy, dragToDisplayTransitionHandler, @@ -3123,6 +3127,36 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_toDesktopInOtherDisplay_appliesTaskLimit() { + val transition = Binder() + val sourceDeskId = 0 + val targetDeskId = 2 + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId) + taskRepository.setDeskInactive(deskId = targetDeskId) + val targetDeskTasks = + (1..MAX_TASK_LIMIT + 1).map { _ -> + setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = targetDeskId) + } + // Set up two display ids + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: second display + val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) + .thenReturn(secondDisplayArea) + whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull())) + .thenReturn(transition) + val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId) + + controller.moveToNextDisplay(task.taskId) + + val wct = getLatestTransition() + assertNotNull(wct) + verify(desksOrganizer).minimizeTask(wct, targetDeskId, targetDeskTasks[0]) + } + + @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, @@ -3191,10 +3225,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() verify(desksOrganizer).activateDesk(any(), eq(targetDeskId)) verify(desksTransitionsObserver) .addPendingTransition( - DeskTransition.ActivateDesk( + DeskTransition.ActiveDeskWithTask( token = transition, displayId = SECOND_DISPLAY, deskId = targetDeskId, + enterTaskId = task.taskId, ) ) } @@ -3470,6 +3505,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP) fun onPipTaskMinimize_autoEnterEnabled_startPipTransition() { val task = setUpPipTask(autoEnterEnabled = true) val handler = mock(TransitionHandler::class.java) @@ -3484,6 +3520,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP) fun onPipTaskMinimize_autoEnterDisabled_startMinimizeTransition() { val task = setUpPipTask(autoEnterEnabled = false) whenever( @@ -3503,6 +3540,103 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun onPipTaskMinimize_autoEnterEnabled_sendsTaskbarRoundingUpdate() { + val task = setUpPipTask(autoEnterEnabled = true) + val handler = mock(TransitionHandler::class.java) + whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) + .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) + + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) + + verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(anyBoolean()) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP, + ) + fun onDesktopTaskEnteredPip_pipIsLastTask_removesWallpaper() { + val task = setUpPipTask(autoEnterEnabled = true) + + controller.onDesktopTaskEnteredPip( + taskId = task.taskId, + deskId = DEFAULT_DISPLAY, + displayId = task.displayId, + taskIsLastVisibleTaskBeforePip = true, + ) + + // Wallpaper is moved to the back + val wct = getLatestTransition() + wct.assertReorder(wallpaperToken, /* toTop= */ false) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun onDesktopTaskEnteredPip_pipIsLastTask_deactivatesDesk() { + val deskId = DEFAULT_DISPLAY + val task = setUpPipTask(autoEnterEnabled = true, deskId = deskId) + val transition = Binder() + whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition) + + controller.onDesktopTaskEnteredPip( + taskId = task.taskId, + deskId = deskId, + displayId = task.displayId, + taskIsLastVisibleTaskBeforePip = true, + ) + + verify(desksOrganizer).deactivateDesk(any(), eq(deskId)) + verify(desksTransitionsObserver) + .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId)) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun onDesktopTaskEnteredPip_pipIsLastTask_launchesHome() { + val task = setUpPipTask(autoEnterEnabled = true) + + controller.onDesktopTaskEnteredPip( + taskId = task.taskId, + deskId = DEFAULT_DISPLAY, + displayId = task.displayId, + taskIsLastVisibleTaskBeforePip = true, + ) + + val wct = getLatestTransition() + wct.assertPendingIntent(launchHomeIntent(DEFAULT_DISPLAY)) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun onDesktopTaskEnteredPip_pipIsNotLastTask_doesntExitDesktopMode() { + val task = setUpPipTask(autoEnterEnabled = true) + val deskId = DEFAULT_DISPLAY + setUpFreeformTask(deskId = deskId) // launch another freeform task + val transition = Binder() + whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition) + + controller.onDesktopTaskEnteredPip( + taskId = task.taskId, + deskId = deskId, + displayId = task.displayId, + taskIsLastVisibleTaskBeforePip = false, + ) + + // No transition to exit Desktop mode is started + verifyWCTNotExecuted() + verify(desktopModeEnterExitTransitionListener, never()) + .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) + verify(desksOrganizer, never()).deactivateDesk(any(), eq(deskId)) + verify(desksTransitionsObserver, never()) + .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId)) + } + + @Test fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() { val task = setUpFreeformTask(active = true) val transition = Binder() @@ -3720,6 +3854,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + fun onDesktopWindowMinimize_sendsTaskbarRoundingUpdate() { + val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val transition = Binder() + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) + .thenReturn(transition) + + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) + + verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(anyBoolean()) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_fullscreenTask_switchToDesktop_movesTaskToDesk() { taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 5) @@ -4983,6 +5135,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION) fun handleRequest_closeTransition_singleTaskNoToken_secondaryDisplay_launchesHome() { taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) @@ -7320,6 +7473,14 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun testCreateDesk_invalidDisplay_dropsRequest() { + controller.createDesk(INVALID_DISPLAY) + + verify(desksOrganizer, never()).createDesk(any(), any()) + } + + @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, @@ -7615,8 +7776,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() return task } - private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo = - setUpFreeformTask().apply { + private fun setUpPipTask( + autoEnterEnabled: Boolean, + displayId: Int = DEFAULT_DISPLAY, + deskId: Int = DEFAULT_DISPLAY, + ): RunningTaskInfo = + setUpFreeformTask(displayId = displayId, deskId = deskId).apply { pictureInPictureParams = PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build() } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt index a7dc706eb6c9..5ef1ace7873d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -22,7 +22,6 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.content.ComponentName import android.content.Context import android.content.Intent -import android.os.Binder import android.os.IBinder import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags @@ -30,7 +29,6 @@ import android.view.Display.DEFAULT_DISPLAY import android.view.WindowManager import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_OPEN -import android.view.WindowManager.TRANSIT_PIP import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.IWindowContainerToken @@ -41,7 +39,6 @@ import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.window.flags.Flags -import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP import com.android.wm.shell.MockToken import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.back.BackAnimationController @@ -51,10 +48,9 @@ import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpape import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions -import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP -import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage +import java.util.Optional import org.junit.Before import org.junit.Rule import org.junit.Test @@ -90,6 +86,7 @@ class DesktopTasksTransitionObserverTest { private val userRepositories = mock<DesktopUserRepositories>() private val taskRepository = mock<DesktopRepository>() private val mixedHandler = mock<DesktopMixedTransitionHandler>() + private val pipTransitionObserver = mock<DesktopPipTransitionObserver>() private val backAnimationController = mock<BackAnimationController>() private val desktopWallpaperActivityTokenProvider = mock<DesktopWallpaperActivityTokenProvider>() @@ -114,6 +111,7 @@ class DesktopTasksTransitionObserverTest { transitions, shellTaskOrganizer, mixedHandler, + Optional.of(pipTransitionObserver), backAnimationController, desktopWallpaperActivityTokenProvider, shellInit, @@ -336,67 +334,61 @@ class DesktopTasksTransitionObserverTest { } @Test - fun transitCloseWallpaper_wallpaperActivityVisibilitySaved() { - val wallpaperTask = createWallpaperTaskInfo() - - transitionObserver.onTransitionReady( - transition = mock(), - info = createCloseTransition(wallpaperTask), - startTransaction = mock(), - finishTransaction = mock(), - ) - - verify(desktopWallpaperActivityTokenProvider).removeToken(wallpaperTask.displayId) - } - - @Test - @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) - fun pendingPipTransitionAborted_taskRepositoryOnPipAbortedInvoked() { - val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM) - val pipTransition = Binder() - whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + ) + fun nonTopTransparentTaskOpened_clearTopTransparentTaskIdFromRepository() { + val mockTransition = Mockito.mock(IBinder::class.java) + val topTransparentTask = createTaskInfo(1) + val nonTopTransparentTask = createTaskInfo(2) + whenever(taskRepository.getTopTransparentFullscreenTaskId(any())) + .thenReturn(topTransparentTask.taskId) transitionObserver.onTransitionReady( - transition = pipTransition, - info = createOpenChangeTransition(task, type = TRANSIT_PIP), + transition = mockTransition, + info = createOpenChangeTransition(nonTopTransparentTask), startTransaction = mock(), finishTransaction = mock(), ) - transitionObserver.onTransitionFinished(transition = pipTransition, aborted = true) - verify(taskRepository).onPipAborted(task.displayId, task.taskId) + verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId) } @Test - @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) - fun exitPipTransition_taskRepositoryClearTaskInPip() { - val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM) - whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + ) + fun nonTopTransparentTaskSentToFront_clearTopTransparentTaskIdFromRepository() { + val mockTransition = Mockito.mock(IBinder::class.java) + val topTransparentTask = createTaskInfo(1) + val nonTopTransparentTask = createTaskInfo(2) + whenever(taskRepository.getTopTransparentFullscreenTaskId(any())) + .thenReturn(topTransparentTask.taskId) transitionObserver.onTransitionReady( - transition = mock(), - info = createOpenChangeTransition(task, type = TRANSIT_EXIT_PIP), + transition = mockTransition, + info = createToFrontTransition(nonTopTransparentTask), startTransaction = mock(), finishTransaction = mock(), ) - verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false) + verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId) } @Test - @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) - fun removePipTransition_taskRepositoryClearTaskInPip() { - val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM) - whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true) + fun transitCloseWallpaper_wallpaperActivityVisibilitySaved() { + val wallpaperTask = createWallpaperTaskInfo() transitionObserver.onTransitionReady( transition = mock(), - info = createOpenChangeTransition(task, type = TRANSIT_REMOVE_PIP), + info = createCloseTransition(wallpaperTask), startTransaction = mock(), finishTransaction = mock(), ) - verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false) + verify(desktopWallpaperActivityTokenProvider).removeToken(wallpaperTask.displayId) } private fun createBackNavigationTransition( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index 6e7adf368155..4e2994c27729 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -54,7 +54,9 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.MockitoSession @@ -64,7 +66,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import org.mockito.quality.Strictness @@ -85,8 +87,17 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Mock private lateinit var desktopUserRepositories: DesktopUserRepositories @Mock private lateinit var bubbleController: BubbleController @Mock private lateinit var visualIndicator: DesktopModeVisualIndicator - - private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() } + @Mock private lateinit var dragCancelCallback: Runnable + @Mock + private lateinit var dragToDesktopStateListener: + DragToDesktopTransitionHandler.DragToDesktopStateListener + + private val transactionSupplier = Supplier { + val transaction = mock<SurfaceControl.Transaction>() + whenever(transaction.setAlpha(any(), anyFloat())).thenReturn(transaction) + whenever(transaction.setFrameTimeline(anyLong())).thenReturn(transaction) + transaction + } private lateinit var defaultHandler: DragToDesktopTransitionHandler private lateinit var springHandler: SpringDragToDesktopTransitionHandler @@ -104,7 +115,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { Optional.of(bubbleController), transactionSupplier, ) - .apply { setSplitScreenController(splitScreenController) } + .apply { + setSplitScreenController(splitScreenController) + dragToDesktopStateListener = + this@DragToDesktopTransitionHandlerTest.dragToDesktopStateListener + } springHandler = SpringDragToDesktopTransitionHandler( context, @@ -115,7 +130,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { Optional.of(bubbleController), transactionSupplier, ) - .apply { setSplitScreenController(splitScreenController) } + .apply { + setSplitScreenController(splitScreenController) + dragToDesktopStateListener = + this@DragToDesktopTransitionHandlerTest.dragToDesktopStateListener + } mockitoSession = ExtendedMockito.mockitoSession() .strictness(Strictness.LENIENT) @@ -438,7 +457,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) // No need to animate the cancel since the start animation couldn't even start. - verifyZeroInteractions(dragAnimator) + verifyNoMoreInteractions(dragAnimator) } @Test @@ -489,7 +508,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) // Should NOT have any transaction changes - verifyZeroInteractions(mergedStartTransaction) + verifyNoMoreInteractions(mergedStartTransaction) // Should NOT merge animation verify(finishCallback, never()).onTransitionFinished(any()) } @@ -706,8 +725,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { } @Test - @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) - fun mergeOtherTransition_cancelAndEndNotYetRequested_doesntInterruptsStartDrag() { + @DisableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) + fun mergeOtherTransition_flagDisabled_cancelAndEndNotYetRequested_doesNotInterruptStartDrag() { val finishCallback = mock<Transitions.TransitionFinishCallback>() val task = createTask() defaultHandler.onTaskResizeAnimationListener = mock() @@ -721,6 +740,39 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) + fun mergeOtherTransition_cancelAndEndNotYetRequested_interruptsStartDrag() { + val finishCallback = mock<Transitions.TransitionFinishCallback>() + val task = createTask() + defaultHandler.onTaskResizeAnimationListener = mock() + val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback) + + mergeInterruptingTransition(mergeTarget = startTransition) + + verify(dragAnimator).cancelAnimator() + verify(dragCancelCallback).run() + verify(dragToDesktopStateListener).onTransitionInterrupted() + assertThat(defaultHandler.inProgress).isTrue() + // Doesn't finish start transition yet + verify(finishCallback, never()).onTransitionFinished(/* wct= */ anyOrNull()) + } + + @Test + @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) + fun mergeOtherTransition_cancelAndEndNotYetRequested_finishesStartAfterAnimation() { + val finishCallback = mock<Transitions.TransitionFinishCallback>() + val task = createTask() + defaultHandler.onTaskResizeAnimationListener = mock() + val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback) + + mergeInterruptingTransition(mergeTarget = startTransition) + mAnimatorTestRule.advanceTimeBy(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS) + + verify(finishCallback).onTransitionFinished(/* wct= */ anyOrNull()) + assertThat(defaultHandler.inProgress).isFalse() + } + + @Test + @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) fun mergeOtherTransition_endDragAlreadyMerged_doesNotInterruptStartDrag() { val startDragFinishCallback = mock<Transitions.TransitionFinishCallback>() val task = createTask() @@ -795,6 +847,35 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { verify(dragAnimator, times(2)).startAnimation() } + @Test + @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) + fun startCancelAnimation_otherTransitionInterruptingAfterCancelRequest_finishImmediately() { + val task1 = createTask() + val startTransition = startDrag(defaultHandler, task1) + val cancelTransition = + cancelDragToDesktopTransition(defaultHandler, CancelState.STANDARD_CANCEL) + mergeInterruptingTransition(mergeTarget = startTransition) + val cancelFinishCallback = mock<Transitions.TransitionFinishCallback>() + val startTransaction = mock<SurfaceControl.Transaction>() + + val didAnimate = + defaultHandler.startAnimation( + transition = requireNotNull(cancelTransition), + info = + createTransitionInfo( + type = TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, + draggedTask = task1, + ), + startTransaction = startTransaction, + finishTransaction = mock(), + finishCallback = cancelFinishCallback, + ) + + assertThat(didAnimate).isTrue() + verify(startTransaction).apply() + verify(cancelFinishCallback).onTransitionFinished(/* wct= */ anyOrNull()) + } + private fun mergeInterruptingTransition(mergeTarget: IBinder) { defaultHandler.mergeAnimation( transition = mock<IBinder>(), @@ -942,7 +1023,12 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) ) .thenReturn(token) - handler.startDragToDesktopTransition(task, dragAnimator, visualIndicator) + handler.startDragToDesktopTransition( + task, + dragAnimator, + visualIndicator, + dragCancelCallback, + ) return token } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt index c7518d5914b4..3983bfbb2080 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt @@ -54,7 +54,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.spy import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever /** @@ -111,7 +111,7 @@ class VisualIndicatorViewContainerTest : ShellTestCase() { eq(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR), ) // Assert fadeIn, fadeOut, and animateIndicatorType were not called. - verifyZeroInteractions(spyViewContainer) + verifyNoMoreInteractions(spyViewContainer) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt index 7560945856ec..dc973d0fda77 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt @@ -90,7 +90,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { whenever(packageManager.getHomeActivities(ArrayList())).thenReturn(componentName) desktopModeCompatPolicy = DesktopModeCompatPolicy(spyContext) transitionHandler = createTransitionHandler() - allowOverlayPermission(arrayOf(SYSTEM_ALERT_WINDOW)) + allowOverlayPermissionForAllUsers(arrayOf(SYSTEM_ALERT_WINDOW)) } private fun createTransitionHandler() = @@ -200,10 +200,16 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { .isTrue() } - fun allowOverlayPermission(permissions: Array<String>) { + fun allowOverlayPermissionForAllUsers(permissions: Array<String>) { val packageInfo = mock<PackageInfo>() packageInfo.requestedPermissions = permissions - whenever(packageManager.getPackageInfo(anyString(), eq(PackageManager.GET_PERMISSIONS))) + whenever( + packageManager.getPackageInfoAsUser( + anyString(), + eq(PackageManager.GET_PERMISSIONS), + anyInt(), + ) + ) .thenReturn(packageInfo) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java index 14f9ffc52a66..2bd9afcef1bb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java @@ -24,7 +24,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; @@ -107,7 +107,7 @@ public class PipAlphaAnimatorTest { }); verify(mMockStartCallback).run(); - verifyZeroInteractions(mMockEndCallback); + verifyNoMoreInteractions(mMockEndCallback); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java index 72c466663a56..fa7ab9521dac 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java @@ -24,7 +24,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; @@ -117,7 +117,7 @@ public class PipEnterAnimatorTest { }); verify(mMockStartCallback).run(); - verifyZeroInteractions(mMockEndCallback); + verifyNoMoreInteractions(mMockEndCallback); // Check corner and shadow radii were set verify(mMockAnimateTransaction, atLeastOnce()) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java index b816f0ef041e..97133bedfa2d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java @@ -21,7 +21,7 @@ import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; @@ -143,7 +143,7 @@ public class PipExpandAnimatorTest { }); verify(mMockStartCallback).run(); - verifyZeroInteractions(mMockEndCallback); + verifyNoMoreInteractions(mMockEndCallback); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java index 23fbad05ec99..c99ca6dd7065 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java @@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.mockito.kotlin.MatchersKt.eq; @@ -118,7 +118,7 @@ public class PipResizeAnimatorTest { }); verify(mMockStartCallback).run(); - verifyZeroInteractions(mMockEndCallback); + verifyNoMoreInteractions(mMockEndCallback); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java index 5029371c3419..b6894fd0a9fa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java @@ -30,7 +30,7 @@ import static org.mockito.kotlin.MatchersKt.eq; import static org.mockito.kotlin.VerificationKt.clearInvocations; import static org.mockito.kotlin.VerificationKt.times; import static org.mockito.kotlin.VerificationKt.verify; -import static org.mockito.kotlin.VerificationKt.verifyZeroInteractions; +import static org.mockito.kotlin.VerificationKt.verifyNoMoreInteractions; import android.app.ActivityManager; import android.app.PendingIntent; @@ -176,7 +176,7 @@ public class PipTaskListenerTest { mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( aspectRatio, action1)); - verifyZeroInteractions(mMockPipParamsChangedCallback); + verifyNoMoreInteractions(mMockPipParamsChangedCallback); } @Test @@ -193,7 +193,7 @@ public class PipTaskListenerTest { clearInvocations(mMockPipParamsChangedCallback); mPipTaskListener.onTaskInfoChanged(new ActivityManager.RunningTaskInfo()); - verifyZeroInteractions(mMockPipParamsChangedCallback); + verifyNoMoreInteractions(mMockPipParamsChangedCallback); verify(mMockPipTransitionState, times(0)) .setOnIdlePipTransitionStateRunnable(any(Runnable.class)); } @@ -245,7 +245,7 @@ public class PipTaskListenerTest { mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); verify(mMockPipTransitionState).setOnIdlePipTransitionStateRunnable(any(Runnable.class)); - verifyZeroInteractions(mMockPipParamsChangedCallback); + verifyNoMoreInteractions(mMockPipParamsChangedCallback); } @Test @@ -262,7 +262,7 @@ public class PipTaskListenerTest { clearInvocations(mMockPipParamsChangedCallback); mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); - verifyZeroInteractions(mMockPipParamsChangedCallback); + verifyNoMoreInteractions(mMockPipParamsChangedCallback); verify(mMockPipTransitionState, times(0)) .setOnIdlePipTransitionStateRunnable(any(Runnable.class)); } @@ -319,7 +319,7 @@ public class PipTaskListenerTest { PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extras); - verifyZeroInteractions(mMockPipScheduler); + verifyNoMoreInteractions(mMockPipScheduler); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java index 82cdfd52d2db..51de50da6921 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java @@ -20,7 +20,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.Flags; @@ -82,7 +82,7 @@ public class PipUiStateChangeControllerTests { mPipUiStateChangeController.onPipTransitionStateChanged( PipTransitionState.SWIPING_TO_PIP, PipTransitionState.ENTERING_PIP, Bundle.EMPTY); - verifyZeroInteractions(mPictureInPictureUiStateConsumer); + verifyNoMoreInteractions(mPictureInPictureUiStateConsumer); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt index 5ac680048a7e..12785c03aa9f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt @@ -42,6 +42,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.kotlin.any import org.mockito.kotlin.eq @@ -87,7 +88,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION) fun testIsTopActivityExemptWithPermission_onlyTransparentActivitiesInStack() { - allowOverlayPermission(arrayOf(SYSTEM_ALERT_WINDOW)) + allowOverlayPermissionForAllUsers(arrayOf(SYSTEM_ALERT_WINDOW)) assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( createFreeformTask(/* displayId */ 0) .apply { @@ -101,7 +102,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION) fun testIsTopActivityExemptWithNoPermission_onlyTransparentActivitiesInStack() { - allowOverlayPermission(arrayOf()) + allowOverlayPermissionForAllUsers(arrayOf()) assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( createFreeformTask(/* displayId */ 0) .apply { @@ -115,7 +116,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION) fun testIsTopActivityExemptCachedPermissionCheckIsUsed() { - allowOverlayPermission(arrayOf()) + allowOverlayPermissionForAllUsers(arrayOf()) assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( createFreeformTask(/* displayId */ 0) .apply { @@ -123,6 +124,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() { isTopActivityNoDisplay = false numActivities = 1 baseActivity = baseActivityTest + userId = 10 })) assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( createFreeformTask(/* displayId */ 0) @@ -131,10 +133,26 @@ class DesktopModeCompatPolicyTest : ShellTestCase() { isTopActivityNoDisplay = false numActivities = 1 baseActivity = baseActivityTest + userId = 10 })) - verify(packageManager, times(1)).getPackageInfo( + assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( + createFreeformTask(/* displayId */ 0) + .apply { + isActivityStackTransparent = true + isTopActivityNoDisplay = false + numActivities = 1 + baseActivity = baseActivityTest + userId = 0 + })) + verify(packageManager, times(1)).getPackageInfoAsUser( + eq("com.test.dummypackage"), + eq(PackageManager.GET_PERMISSIONS), + eq(10) + ) + verify(packageManager, times(1)).getPackageInfoAsUser( eq("com.test.dummypackage"), - eq(PackageManager.GET_PERMISSIONS) + eq(PackageManager.GET_PERMISSIONS), + eq(0) ) } @@ -284,13 +302,14 @@ class DesktopModeCompatPolicyTest : ShellTestCase() { } } - fun allowOverlayPermission(permissions: Array<String>) { + fun allowOverlayPermissionForAllUsers(permissions: Array<String>) { val packageInfo = mock<PackageInfo>() packageInfo.requestedPermissions = permissions whenever( - packageManager.getPackageInfo( + packageManager.getPackageInfoAsUser( anyString(), - eq(PackageManager.GET_PERMISSIONS) + eq(PackageManager.GET_PERMISSIONS), + anyInt(), ) ).thenReturn(packageInfo) } 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 3099b0f5cf66..a122c3820dcb 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 @@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.window.flags.Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX; import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP; import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE; @@ -44,6 +45,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -196,6 +198,73 @@ public class HomeTransitionObserverTest extends ShellTestCase { } @Test + @DisableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX}) + public void startDragToDesktopFinished_flagDisabled_doesNotTriggerCallback() + throws RemoteException { + TransitionInfo info = mock(TransitionInfo.class); + TransitionInfo.Change change = mock(TransitionInfo.Change.class); + ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class); + when(change.getTaskInfo()).thenReturn(taskInfo); + when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change))); + when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP); + setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true); + IBinder transition = mock(IBinder.class); + mHomeTransitionObserver.onTransitionReady( + transition, + info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); + + mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ false); + + verify(mListener, never()).onHomeVisibilityChanged(/* isVisible= */ anyBoolean()); + } + + @Test + @EnableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX}) + public void startDragToDesktopAborted_doesNotTriggerCallback() throws RemoteException { + TransitionInfo info = mock(TransitionInfo.class); + TransitionInfo.Change change = mock(TransitionInfo.Change.class); + ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class); + when(change.getTaskInfo()).thenReturn(taskInfo); + when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change))); + when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP); + setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true); + IBinder transition = mock(IBinder.class); + mHomeTransitionObserver.onTransitionReady( + transition, + info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); + + mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ true); + + verify(mListener, never()).onHomeVisibilityChanged(/* isVisible= */ anyBoolean()); + } + + @Test + @EnableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX}) + public void startDragToDesktopFinished_triggersCallback() throws RemoteException { + TransitionInfo info = mock(TransitionInfo.class); + TransitionInfo.Change change = mock(TransitionInfo.Change.class); + ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class); + when(change.getTaskInfo()).thenReturn(taskInfo); + when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change))); + when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP); + setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true); + IBinder transition = mock(IBinder.class); + mHomeTransitionObserver.onTransitionReady( + transition, + info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); + + mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ false); + + verify(mListener).onHomeVisibilityChanged(/* isVisible= */ true); + } + + @Test @EnableFlags({Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE}) public void testDragTaskToBubbleOverHome_notifiesHomeIsVisible() throws RemoteException { ActivityManager.RunningTaskInfo homeTask = createTaskInfo(1, ACTIVITY_TYPE_HOME); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt index 067dcec5d65d..b1f92411c5a3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt @@ -28,6 +28,7 @@ import android.testing.TestableLooper.RunWithLooper import android.view.Display import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_CHANGE import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn @@ -109,7 +110,7 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest : onTaskOpening(task, taskSurface) assertTrue(windowDecorByTaskIdSpy.contains(task.taskId)) task.setActivityType(ACTIVITY_TYPE_UNDEFINED) - onTaskChanging(task, taskSurface) + onTaskChanging(task, taskSurface, TRANSIT_CHANGE) assertFalse(windowDecorByTaskIdSpy.contains(task.taskId)) verify(decoration).close() @@ -165,7 +166,7 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest : setLargeScreen(false) setUpMockDecorationForTask(task) - onTaskChanging(task, taskSurface) + onTaskChanging(task, taskSurface, TRANSIT_CHANGE) assertFalse(windowDecorByTaskIdSpy.contains(task.taskId)) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index d69509faf4ec..ad3426e82805 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -51,6 +51,7 @@ import android.view.SurfaceView import android.view.View import android.view.ViewRootImpl import android.view.WindowInsets.Type.statusBars +import android.view.WindowManager.TRANSIT_CHANGE import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp import androidx.test.filters.SmallTest @@ -134,7 +135,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest task.setWindowingMode(WINDOWING_MODE_UNDEFINED) task.setActivityType(ACTIVITY_TYPE_UNDEFINED) - onTaskChanging(task, taskSurface) + onTaskChanging(task, taskSurface, TRANSIT_CHANGE) assertFalse(windowDecorByTaskIdSpy.contains(task.taskId)) verify(decoration).close() @@ -149,12 +150,12 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest val taskSurface = SurfaceControl() setUpMockDecorationForTask(task) - onTaskChanging(task, taskSurface) + onTaskChanging(task, taskSurface, TRANSIT_CHANGE) assertFalse(windowDecorByTaskIdSpy.contains(task.taskId)) task.setWindowingMode(WINDOWING_MODE_FREEFORM) task.setActivityType(ACTIVITY_TYPE_STANDARD) - onTaskChanging(task, taskSurface) + onTaskChanging(task, taskSurface, TRANSIT_CHANGE) assertTrue(windowDecorByTaskIdSpy.contains(task.taskId)) } @@ -758,20 +759,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest } @Test - fun testDecor_onClickToSplitScreen_disposesStatusBarInputLayer() { - val toSplitScreenListenerCaptor = forClass(Function0::class.java) - as ArgumentCaptor<Function0<Unit>> - val decor = createOpenTaskDecoration( - windowingMode = WINDOWING_MODE_MULTI_WINDOW, - onToSplitScreenClickListenerCaptor = toSplitScreenListenerCaptor - ) - - toSplitScreenListenerCaptor.value.invoke() - - verify(decor).disposeStatusBarInputLayer() - } - - @Test fun testDecor_onClickToOpenBrowser_closeMenus() { val openInBrowserListenerCaptor = forClass(Consumer::class.java) as ArgumentCaptor<Consumer<Intent>> diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt index a1f40fdefee9..4c9c2f14d805 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt @@ -84,6 +84,7 @@ import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel +import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder import org.junit.After import org.mockito.Mockito @@ -125,6 +126,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { protected val mockShellController = mock<ShellController>() protected val testShellExecutor = TestShellExecutor() protected val mockAppHeaderViewHolderFactory = mock<AppHeaderViewHolder.Factory>() + protected val mockAppHandleViewHolderFactory = mock<AppHandleViewHolder.Factory>() protected val mockRootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>() protected val mockShellCommandHandler = mock<ShellCommandHandler>() protected val mockWindowManager = mock<IWindowManager>() @@ -222,6 +224,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { mockInputMonitorFactory, transactionFactory, mockAppHeaderViewHolderFactory, + mockAppHandleViewHolderFactory, mockRootTaskDisplayAreaOrganizer, windowDecorByTaskIdSpy, mockInteractionJankMonitor, @@ -331,7 +334,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { mockDesktopModeWindowDecorFactory.create( any(), any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), - any(), any(), any()) + any(), any(), any(), any()) ).thenReturn(decoration) decoration.mTaskInfo = task whenever(decoration.user).thenReturn(mockUserHandle) @@ -353,12 +356,17 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { ) } - protected fun onTaskChanging(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) { + protected fun onTaskChanging( + task: RunningTaskInfo, + leash: SurfaceControl = SurfaceControl(), + changeMode: Int + ) { desktopModeWindowDecorViewModel.onTaskChanging( task, leash, StubTransaction(), - StubTransaction() + StubTransaction(), + changeMode ) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 878324937f1a..f37f2fb14bea 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -115,6 +115,7 @@ import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams; import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; +import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; import kotlin.Unit; @@ -171,6 +172,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private static final boolean DEFAULT_HAS_GLOBAL_FOCUS = true; private static final boolean DEFAULT_SHOULD_IGNORE_CORNER_RADIUS = false; private static final boolean DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS = false; + private static final boolean DEFAULT_IS_RECENTS_TRANSITION_RUNNING = false; + private static final boolean DEFAULT_IS_MOVING_TO_BACK = false; + @Mock private DisplayController mMockDisplayController; @@ -191,8 +195,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private AppHeaderViewHolder.Factory mMockAppHeaderViewHolderFactory; @Mock + private AppHandleViewHolder.Factory mMockAppHandleViewHolderFactory; + @Mock private AppHeaderViewHolder mMockAppHeaderViewHolder; @Mock + private AppHandleViewHolder mMockAppHandleViewHolder; + @Mock private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer; @Mock private Supplier<SurfaceControl.Transaction> mMockTransactionSupplier; @@ -301,6 +309,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { when(mMockAppHeaderViewHolderFactory .create(any(), any(), any(), any(), any(), any(), any(), any(), any())) .thenReturn(mMockAppHeaderViewHolder); + when(mMockAppHandleViewHolderFactory + .create(any(), any(), any(), any(), any())) + .thenReturn(mMockAppHandleViewHolder); when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository); when(mMockDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository); when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay))) @@ -421,7 +432,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, /* shouldIgnoreCornerRadius= */ true, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS); } @@ -623,7 +636,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - /* shouldExcludeCaptionFromAppBounds */ true); + /* shouldExcludeCaptionFromAppBounds */ true, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); // Force consuming flags are disabled. assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue(); @@ -658,7 +673,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue(); assertThat( @@ -737,7 +754,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); // Takes status bar inset as padding, ignores caption bar inset. assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50); @@ -765,7 +784,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); assertThat(relayoutParams.mIsInsetSource).isFalse(); } @@ -792,7 +813,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); // Header is always shown because it's assumed the status bar is always visible. assertThat(relayoutParams.mIsCaptionVisible).isTrue(); @@ -819,7 +842,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); assertThat(relayoutParams.mIsCaptionVisible).isTrue(); } @@ -845,7 +870,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); assertThat(relayoutParams.mIsCaptionVisible).isFalse(); } @@ -871,7 +898,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); assertThat(relayoutParams.mIsCaptionVisible).isFalse(); } @@ -898,7 +927,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); assertThat(relayoutParams.mIsCaptionVisible).isTrue(); @@ -917,7 +948,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); assertThat(relayoutParams.mIsCaptionVisible).isFalse(); } @@ -944,7 +977,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); assertThat(relayoutParams.mIsCaptionVisible).isTrue(); } @@ -971,7 +1006,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); assertThat(relayoutParams.mIsCaptionVisible).isFalse(); } @@ -1002,6 +1039,65 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { assertThat(relayoutParams.mAsyncViewHost).isFalse(); } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX) + public void updateRelayoutParams_handle_movingToBack_captionNotVisible() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + mMockSplitScreenController, + DEFAULT_APPLY_START_TRANSACTION_ON_DRAW, + DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP, + DEFAULT_IS_STATUSBAR_VISIBLE, + DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, + DEFAULT_IS_IN_FULL_IMMERSIVE_MODE, + DEFAULT_IS_DRAGGING, + new InsetsState(), + DEFAULT_HAS_GLOBAL_FOCUS, + mExclusionRegion, + DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + /* isMovingToBack= */ true); + + assertThat(relayoutParams.mIsCaptionVisible).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX) + public void updateRelayoutParams_handle_inRecentsTransition_captionNotVisible() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + mMockSplitScreenController, + DEFAULT_APPLY_START_TRANSACTION_ON_DRAW, + DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP, + DEFAULT_IS_STATUSBAR_VISIBLE, + DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, + DEFAULT_IS_IN_FULL_IMMERSIVE_MODE, + DEFAULT_IS_DRAGGING, + new InsetsState(), + DEFAULT_HAS_GLOBAL_FOCUS, + mExclusionRegion, + DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + /* isRecentsTransitionRunning= */ true, + DEFAULT_IS_MOVING_TO_BACK); + + assertThat(relayoutParams.mIsCaptionVisible).isFalse(); + } + @Test public void relayout_fullscreenTask_appliesTransactionImmediately() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); @@ -1633,7 +1729,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, - DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); } private DesktopModeWindowDecoration createWindowDecoration( @@ -1676,9 +1774,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { taskInfo, mMockSurfaceControl, mMockHandler, mMainExecutor, mMockMainCoroutineDispatcher, mMockBgCoroutineScope, mBgExecutor, mMockChoreographer, mMockSyncQueue, mMockAppHeaderViewHolderFactory, - mMockRootTaskDisplayAreaOrganizer, mMockGenericLinksParser, - mMockAssistContentRequester, SurfaceControl.Builder::new, mMockTransactionSupplier, - WindowContainerTransaction::new, SurfaceControl::new, + mMockAppHandleViewHolderFactory, mMockRootTaskDisplayAreaOrganizer, + mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new, + mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory, mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory, mMockMultiInstanceHelper, mMockCaptionHandleRepository, mDesktopModeEventLogger, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt index 7341e098add5..360099777bde 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt @@ -40,11 +40,13 @@ import com.android.wm.shell.windowdecor.DragResizeInputListener.TaskResizeInputE import com.google.common.truth.Truth.assertThat import java.util.function.Consumer import java.util.function.Supplier +import org.junit.After import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.argThat import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -63,6 +65,15 @@ class DragResizeInputListenerTest : ShellTestCase() { private val testBgExecutor = TestShellExecutor() private val mockWindowSession = mock<IWindowSession>() private val mockInputEventReceiver = mock<TaskResizeInputEventReceiver>() + private val inputChannel = mock<InputChannel>() + private val sinkInputChannel = mock<InputChannel>() + private val decorationSurface = SurfaceControl.Builder().setName("decoration surface").build() + private val createdSurfaces = ArrayList<SurfaceControl>() + + @After + fun tearDown() { + decorationSurface.release() + } @Test fun testGrantInputChannelOffMainThread() { @@ -73,6 +84,35 @@ class DragResizeInputListenerTest : ShellTestCase() { } @Test + fun testGrantInputChannelAfterDecorSurfaceReleased() { + // Keep tracking the underlying surface that the decorationSurface points to. + val forVerification = SurfaceControl(decorationSurface, "forVerification") + try { + create() + decorationSurface.release() + testBgExecutor.flushAll() + + verify(mockWindowSession) + .grantInputChannel( + anyInt(), + argThat<SurfaceControl> { isValid && isSameSurface(forVerification) }, + any(), + anyOrNull(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyOrNull(), + any(), + any(), + any(), + ) + } finally { + forVerification.release() + } + } + + @Test fun testInitializationCallback_waitsForBgSetup() { val inputListener = create() @@ -143,6 +183,40 @@ class DragResizeInputListenerTest : ShellTestCase() { verify(mockWindowSession).remove(inputListener.mSinkClientToken) } + @Test + fun testClose_afterBgSetup_disposesOfInputChannels() { + val inputListener = create() + testBgExecutor.flushAll() + inputListener.close() + testMainExecutor.flushAll() + verify(inputChannel).dispose() + verify(sinkInputChannel).dispose() + } + + @Test + fun testClose_beforeBgSetup_releaseSurfaces() { + val inputListener = create() + inputListener.close() + testBgExecutor.flushAll() + testMainExecutor.flushAll() + + assertThat(createdSurfaces).hasSize(1) + assertThat(createdSurfaces[0].isValid).isFalse() + } + + @Test + fun testClose_afterBgSetup_releaseSurfaces() { + val inputListener = create() + testBgExecutor.flushAll() + inputListener.close() + testMainExecutor.flushAll() + testBgExecutor.flushAll() + + assertThat(createdSurfaces).hasSize(2) + assertThat(createdSurfaces[0].isValid).isFalse() + assertThat(createdSurfaces[1].isValid).isFalse() + } + private fun verifyNoInputChannelGrantRequests() { verify(mockWindowSession, never()) .grantInputChannel( @@ -172,12 +246,26 @@ class DragResizeInputListenerTest : ShellTestCase() { TestHandler(Looper.getMainLooper()), mock<Choreographer>(), Display.DEFAULT_DISPLAY, - mock<SurfaceControl>(), + decorationSurface, mock<DragPositioningCallback>(), - { SurfaceControl.Builder() }, - { StubTransaction() }, + { + object : SurfaceControl.Builder() { + override fun build(): SurfaceControl { + return super.build().also { createdSurfaces.add(it) } + } + } + }, + { + object : StubTransaction() { + override fun remove(sc: SurfaceControl): SurfaceControl.Transaction { + return super.remove(sc).also { sc.release() } + } + } + }, mock<DisplayController>(), mock<DesktopModeEventLogger>(), + inputChannel, + sinkInputChannel, ) private class TestInitializationCallback : Runnable { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt index f984f6db13fc..2e46f6312d03 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt @@ -98,8 +98,6 @@ class HandleMenuTest : ShellTestCase() { private lateinit var handleMenu: HandleMenu - private val menuWidthWithElevation = MENU_WIDTH + MENU_PILL_ELEVATION - @Before fun setUp() { val mockAdditionalViewHostViewContainer = AdditionalViewHostViewContainer( @@ -126,7 +124,6 @@ class HandleMenuTest : ShellTestCase() { addOverride(R.dimen.desktop_mode_handle_menu_height, MENU_HEIGHT) addOverride(R.dimen.desktop_mode_handle_menu_margin_top, MENU_TOP_MARGIN) addOverride(R.dimen.desktop_mode_handle_menu_margin_start, MENU_START_MARGIN) - addOverride(R.dimen.desktop_mode_handle_menu_pill_elevation, MENU_PILL_ELEVATION) addOverride( R.dimen.desktop_mode_handle_menu_pill_spacing_margin, MENU_PILL_SPACING_MARGIN) } @@ -141,7 +138,7 @@ class HandleMenuTest : ShellTestCase() { assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer) // Verify menu is created at coordinates that, when added to WindowManager, // show at the top-center of display. - val expected = Point(DISPLAY_BOUNDS.centerX() - menuWidthWithElevation / 2, MENU_TOP_MARGIN) + val expected = Point(DISPLAY_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN) assertEquals(expected.toPointF(), handleMenu.handleMenuPosition) } @@ -165,7 +162,7 @@ class HandleMenuTest : ShellTestCase() { // Verify menu is created at coordinates that, when added to WindowManager, // show at the top-center of split left task. val expected = Point( - SPLIT_LEFT_BOUNDS.centerX() - menuWidthWithElevation / 2, + SPLIT_LEFT_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN ) assertEquals(expected.toPointF(), handleMenu.handleMenuPosition) @@ -180,7 +177,7 @@ class HandleMenuTest : ShellTestCase() { // Verify menu is created at coordinates that, when added to WindowManager, // show at the top-center of split right task. val expected = Point( - SPLIT_RIGHT_BOUNDS.centerX() - menuWidthWithElevation / 2, + SPLIT_RIGHT_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN ) assertEquals(expected.toPointF(), handleMenu.handleMenuPosition) @@ -323,7 +320,6 @@ class HandleMenuTest : ShellTestCase() { private const val MENU_HEIGHT = 400 private const val MENU_TOP_MARGIN = 10 private const val MENU_START_MARGIN = 20 - private const val MENU_PILL_ELEVATION = 2 private const val MENU_PILL_SPACING_MARGIN = 4 private const val HANDLE_WIDTH = 80 private const val APP_NAME = "Test App" diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt index a6b077037b86..0798613ed632 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt @@ -559,6 +559,17 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { } @Test + fun testClose() = runOnUiThread { + verify(mockDisplayController, times(1)) + .addDisplayWindowListener(eq(taskPositioner)) + + taskPositioner.close() + + verify(mockDisplayController, times(1)) + .removeDisplayWindowListener(eq(taskPositioner)) + } + + @Test fun testIsResizingOrAnimatingResizeSet() = runOnUiThread { Assert.assertFalse(taskPositioner.isResizingOrAnimating) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt index fa3d3e4016e9..011c8f0ae17e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt @@ -52,7 +52,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever @@ -216,7 +216,7 @@ class ResizeVeilTest : ShellTestCase() { veil.hideVeil() - verifyZeroInteractions(mockTransaction) + verifyNoMoreInteractions(mockTransaction) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index cda343f3538b..2e95a979220c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -826,6 +826,18 @@ public class WindowDecorationTests extends ShellTestCase { } @Test + public void testClose_withTaskDragResizerSet_callResizerClose() { + final TestWindowDecoration windowDecor = createWindowDecoration( + new TestRunningTaskInfoBuilder().build()); + final TaskDragResizer taskDragResizer = mock(TaskDragResizer.class); + windowDecor.setTaskDragResizer(taskDragResizer); + + windowDecor.close(); + + verify(taskDragResizer).close(); + } + + @Test public void testRelayout_captionFrameChanged_insetsReapplied() { final Display defaultDisplay = mock(Display.class); doReturn(defaultDisplay).when(mMockDisplayController) @@ -1232,6 +1244,11 @@ public class WindowDecorationTests extends ShellTestCase { } @Override + int getCaptionViewId() { + return R.id.caption; + } + + @Override TestView inflateLayout(Context context, int layoutResId) { if (layoutResId == R.layout.caption_layout) { return mMockView; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt index c8ccac35d4c4..714d06211044 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt @@ -54,7 +54,7 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever /** @@ -125,7 +125,7 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() { loader.getName(task) - verifyZeroInteractions( + verifyNoMoreInteractions( mockPackageManager, mockIconProvider, mockHeaderIconFactory, @@ -165,7 +165,7 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() { loader.getHeaderIcon(task) - verifyZeroInteractions(mockPackageManager, mockIconProvider, mockHeaderIconFactory) + verifyNoMoreInteractions(mockPackageManager, mockIconProvider, mockHeaderIconFactory) } @Test @@ -187,7 +187,7 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() { loader.getVeilIcon(task) - verifyZeroInteractions(mockPackageManager, mockIconProvider, mockVeilIconFactory) + verifyNoMoreInteractions(mockPackageManager, mockIconProvider, mockVeilIconFactory) } @Test diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index f8eb41826c6f..109d9e83d444 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -186,3 +186,21 @@ flag { bug: "398254728" is_fixed_read_only: true } + +flag { + name: "limit_fused_gps" + namespace: "location" + description: "Limits when GPS can be used for fused location requests" + bug: "401885179" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "gnss_assistance_interface_jni" + namespace: "location" + description: "Flag for GNSS assistance interface JNI" + bug: "209078566" +} + diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index b57476f4341f..6e0821f9f89b 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -183,7 +183,17 @@ public class MediaRouter { appContext.registerReceiver(new VolumeChangeReceiver(), new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION)); - mDisplayService.registerDisplayListener(this, mHandler); + if (com.android.server.display.feature.flags.Flags + .displayListenerPerformanceImprovements() + && com.android.server.display.feature.flags.Flags + .delayImplicitRrRegistrationUntilRrAccessed()) { + mDisplayService.registerDisplayListener(this, mHandler, + DisplayManager.EVENT_TYPE_DISPLAY_ADDED + | DisplayManager.EVENT_TYPE_DISPLAY_CHANGED + | DisplayManager.EVENT_TYPE_DISPLAY_REMOVED); + } else { + mDisplayService.registerDisplayListener(this, mHandler); + } AudioRoutesInfo newAudioRoutes = null; try { diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index e39a0aa8717e..48e2f4e15238 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -242,3 +242,13 @@ flag { description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller." bug: "293743975" } + +flag { + name: "fix_output_media_item_list_index_out_of_bounds_exception" + namespace: "media_better_together" + description: "Fixes a bug of causing IndexOutOfBoundsException when building media item list." + bug: "398246089" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/media/java/android/media/quality/Android.bp b/media/java/android/media/quality/Android.bp index 080d5266ccb7..f620144e2880 100644 --- a/media/java/android/media/quality/Android.bp +++ b/media/java/android/media/quality/Android.bp @@ -15,6 +15,30 @@ filegroup { path: "aidl", } +cc_library_headers { + name: "media_quality_headers", + export_include_dirs: ["include"], +} + +cc_library_shared { + name: "libmedia_quality_include", + + export_include_dirs: ["include"], + cflags: [ + "-Wno-unused-variable", + "-Wunused-parameter", + ], + + shared_libs: [ + "libbinder", + "libutils", + ], + + srcs: [ + ":framework-media-quality-sources-aidl", + ], +} + aidl_interface { name: "media_quality_aidl_interface", unstable: true, @@ -24,7 +48,8 @@ aidl_interface { enabled: true, }, cpp: { - enabled: false, + additional_shared_libraries: ["libmedia_quality_include"], + enabled: true, }, ndk: { enabled: false, diff --git a/media/java/android/media/quality/aidl/android/media/quality/ActiveProcessingPicture.aidl b/media/java/android/media/quality/aidl/android/media/quality/ActiveProcessingPicture.aidl index 2851306f6e4d..d2cf140632ab 100644 --- a/media/java/android/media/quality/aidl/android/media/quality/ActiveProcessingPicture.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/ActiveProcessingPicture.aidl @@ -16,4 +16,4 @@ package android.media.quality; -parcelable ActiveProcessingPicture;
\ No newline at end of file +parcelable ActiveProcessingPicture cpp_header "quality/MediaQualityManager.h";
\ No newline at end of file diff --git a/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightEvent.aidl b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightEvent.aidl index 174cd461e846..d53860fdf9ad 100644 --- a/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightEvent.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightEvent.aidl @@ -16,4 +16,4 @@ package android.media.quality; -parcelable AmbientBacklightEvent; +parcelable AmbientBacklightEvent cpp_header "quality/MediaQualityManager.h"; diff --git a/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightMetadata.aidl b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightMetadata.aidl index b95a474fbf90..a935b49b5d23 100644 --- a/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightMetadata.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightMetadata.aidl @@ -16,4 +16,4 @@ package android.media.quality; -parcelable AmbientBacklightMetadata;
\ No newline at end of file +parcelable AmbientBacklightMetadata cpp_header "quality/MediaQualityManager.h";
\ No newline at end of file diff --git a/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightSettings.aidl b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightSettings.aidl index e2cdd03194cd..051aef80b948 100644 --- a/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightSettings.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightSettings.aidl @@ -16,4 +16,4 @@ package android.media.quality; -parcelable AmbientBacklightSettings; +parcelable AmbientBacklightSettings cpp_header "quality/MediaQualityManager.h"; diff --git a/media/java/android/media/quality/aidl/android/media/quality/ParameterCapability.aidl b/media/java/android/media/quality/aidl/android/media/quality/ParameterCapability.aidl index eb2ac97916f3..ea848576e026 100644 --- a/media/java/android/media/quality/aidl/android/media/quality/ParameterCapability.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/ParameterCapability.aidl @@ -16,4 +16,4 @@ package android.media.quality; -parcelable ParameterCapability; +parcelable ParameterCapability cpp_header "quality/MediaQualityManager.h"; diff --git a/media/java/android/media/quality/aidl/android/media/quality/PictureProfile.aidl b/media/java/android/media/quality/aidl/android/media/quality/PictureProfile.aidl index 41d018b12f33..b0fe3f5538f4 100644 --- a/media/java/android/media/quality/aidl/android/media/quality/PictureProfile.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/PictureProfile.aidl @@ -16,4 +16,4 @@ package android.media.quality; -parcelable PictureProfile; +parcelable PictureProfile cpp_header "quality/MediaQualityManager.h"; diff --git a/media/java/android/media/quality/aidl/android/media/quality/PictureProfileHandle.aidl b/media/java/android/media/quality/aidl/android/media/quality/PictureProfileHandle.aidl index 5d14631dbb73..0582938b6ea7 100644 --- a/media/java/android/media/quality/aidl/android/media/quality/PictureProfileHandle.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/PictureProfileHandle.aidl @@ -16,4 +16,4 @@ package android.media.quality; -parcelable PictureProfileHandle; +parcelable PictureProfileHandle cpp_header "quality/MediaQualityManager.h"; diff --git a/media/java/android/media/quality/aidl/android/media/quality/SoundProfile.aidl b/media/java/android/media/quality/aidl/android/media/quality/SoundProfile.aidl index e79fcaac97be..d93231fbf7e0 100644 --- a/media/java/android/media/quality/aidl/android/media/quality/SoundProfile.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/SoundProfile.aidl @@ -16,4 +16,4 @@ package android.media.quality; -parcelable SoundProfile; +parcelable SoundProfile cpp_header "quality/MediaQualityManager.h"; diff --git a/media/java/android/media/quality/include/quality/MediaQualityManager.h b/media/java/android/media/quality/include/quality/MediaQualityManager.h new file mode 100644 index 000000000000..8c31667077c3 --- /dev/null +++ b/media/java/android/media/quality/include/quality/MediaQualityManager.h @@ -0,0 +1,127 @@ +#ifndef ANDROID_MEDIA_QUALITY_MANAGER_H +#define ANDROID_MEDIA_QUALITY_MANAGER_H + + +namespace android { +namespace media { +namespace quality { + +// TODO: implement writeToParcel and readFromParcel + +class PictureProfileHandle : public Parcelable { + public: + PictureProfileHandle() {} + status_t writeToParcel(android::Parcel*) const override { + return 0; + } + status_t readFromParcel(const android::Parcel*) override { + return 0; + } + std::string toString() const { + return ""; + } +}; + +class SoundProfile : public Parcelable { + public: + SoundProfile() {} + status_t writeToParcel(android::Parcel*) const override { + return 0; + } + status_t readFromParcel(const android::Parcel*) override { + return 0; + } + std::string toString() const { + return ""; + } +}; + +class PictureProfile : public Parcelable { + public: + PictureProfile() {} + status_t writeToParcel(android::Parcel*) const override { + return 0; + } + status_t readFromParcel(const android::Parcel*) override { + return 0; + } + std::string toString() const { + return ""; + } +}; + +class ActiveProcessingPicture : public Parcelable { + public: + ActiveProcessingPicture() {} + status_t writeToParcel(android::Parcel*) const override { + return 0; + } + status_t readFromParcel(const android::Parcel*) override { + return 0; + } + std::string toString() const { + return ""; + } +}; + +class AmbientBacklightEvent : public Parcelable { + public: + AmbientBacklightEvent() {} + status_t writeToParcel(android::Parcel*) const override { + return 0; + } + status_t readFromParcel(const android::Parcel*) override { + return 0; + } + std::string toString() const { + return ""; + } +}; + +class AmbientBacklightMetadata : public Parcelable { + public: + AmbientBacklightMetadata() {} + status_t writeToParcel(android::Parcel*) const override { + return 0; + } + status_t readFromParcel(const android::Parcel*) override { + return 0; + } + std::string toString() const { + return ""; + } +}; + +class AmbientBacklightSettings : public Parcelable { + public: + AmbientBacklightSettings() {} + status_t writeToParcel(android::Parcel*) const override { + return 0; + } + status_t readFromParcel(const android::Parcel*) override { + return 0; + } + std::string toString() const { + return ""; + } +}; + +class ParameterCapability : public Parcelable { + public: + ParameterCapability() {} + status_t writeToParcel(android::Parcel*) const override { + return 0; + } + status_t readFromParcel(const android::Parcel*) override { + return 0; + } + std::string toString() const { + return ""; + } +}; + +} // namespace quality +} // namespace media +} // namespace android + +#endif diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java index 6089f4291f3e..f65c7efa8ca7 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java @@ -28,7 +28,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.companion.virtual.VirtualDeviceManager; @@ -56,7 +56,7 @@ public class AudioManagerUnitTest { audioManager.playSoundEffect(FX_KEY_CLICK); // We expect no interactions with VDM when running on default device. - verifyZeroInteractions(mockVdm); + verifyNoMoreInteractions(mockVdm); } @Test diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java index 65264d30f04f..006b86a63f60 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java @@ -16,9 +16,9 @@ package com.android.mediaframeworktest.unit; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java index 6229434d1d86..7c54ad2f231c 100644 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java @@ -16,7 +16,7 @@ package com.android.carrierdefaultapp; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java index b2c1e604db7e..964268e4ad14 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java @@ -65,6 +65,7 @@ import android.graphics.drawable.Icon; import android.net.MacAddress; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.os.RemoteException; import android.os.ResultReceiver; import android.text.Spanned; @@ -621,8 +622,10 @@ public class CompanionAssociationActivity extends FragmentActivity implements Slog.w(TAG, "Already selected."); return; } - // Notify the adapter to highlight the selected item. - mDeviceAdapter.setSelectedPosition(position); + // Delay highlighting the selected item by posting to the main thread. + // This helps avoid flicker in the user consent dialog after device selection. + new Handler( + Looper.getMainLooper()).post(() -> mDeviceAdapter.setSelectedPosition(position)); mSelectedDevice = requireNonNull(selectedDevice); diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java index 8e52a00fe545..a0e008c9437f 100644 --- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java +++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java @@ -19,6 +19,7 @@ package com.android.location.fused; import static android.content.Intent.ACTION_USER_SWITCHED; import static android.location.LocationManager.GPS_PROVIDER; import static android.location.LocationManager.NETWORK_PROVIDER; +import static android.location.LocationRequest.QUALITY_HIGH_ACCURACY; import static android.location.LocationRequest.QUALITY_LOW_POWER; import static android.location.provider.ProviderProperties.ACCURACY_FINE; import static android.location.provider.ProviderProperties.POWER_USAGE_LOW; @@ -34,6 +35,7 @@ import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationRequest; +import android.location.flags.Flags; import android.location.provider.LocationProviderBase; import android.location.provider.ProviderProperties; import android.location.provider.ProviderRequest; @@ -61,6 +63,9 @@ public class FusedLocationProvider extends LocationProviderBase { .build(); private static final long MAX_LOCATION_COMPARISON_NS = 11 * 1000000000L; // 11 seconds + // Maximum request interval at which we will activate GPS (because GPS sometimes consumes + // excessive power with large intervals). + private static final long MAX_GPS_INTERVAL_MS = 5 * 1000; // 5 seconds private final Object mLock = new Object(); @@ -165,8 +170,13 @@ public class FusedLocationProvider extends LocationProviderBase { mNlpPresent = mLocationManager.hasProvider(NETWORK_PROVIDER); } + boolean requestAllowsGps = + Flags.limitFusedGps() + ? mRequest.getQuality() == QUALITY_HIGH_ACCURACY + && mRequest.getIntervalMillis() <= MAX_GPS_INTERVAL_MS + : !mNlpPresent || mRequest.getQuality() < QUALITY_LOW_POWER; long gpsInterval = - mGpsPresent && (!mNlpPresent || mRequest.getQuality() < QUALITY_LOW_POWER) + mGpsPresent && requestAllowsGps ? mRequest.getIntervalMillis() : INTERVAL_DISABLED; long networkInterval = mNlpPresent ? mRequest.getIntervalMillis() : INTERVAL_DISABLED; diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING index 50331014f926..716845c5b985 100644 --- a/packages/PackageInstaller/TEST_MAPPING +++ b/packages/PackageInstaller/TEST_MAPPING @@ -23,6 +23,28 @@ ] }, { + "name": "CtsPackageInstallerCUJInstallationViaIntentForResultTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJInstallationViaSessionTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJMultiUsersTestCases", "options":[ { @@ -120,6 +142,28 @@ ] }, { + "name": "CtsPackageInstallerCUJInstallationViaIntentForResultTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJInstallationViaSessionTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJMultiUsersTestCases", "options":[ { diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt index 472ffa9289a7..6dec2f999630 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt @@ -17,6 +17,7 @@ package com.android.settingslib.datastore import android.content.SharedPreferences +import android.util.Log /** Interface of key-value store. */ interface KeyValueStore : KeyedObservable<String> { @@ -80,6 +81,27 @@ interface KeyValueStore : KeyedObservable<String> { fun setString(key: String, value: String?) = setValue(key, String::class.javaObjectType, value) } +/** Delegation of [KeyValueStore]. */ +interface KeyValueStoreDelegate : KeyValueStore, KeyedObservableDelegate<String> { + + /** [KeyValueStore] to delegate. */ + val keyValueStoreDelegate: KeyValueStore + + override val keyedObservableDelegate + get() = keyValueStoreDelegate + + override fun contains(key: String) = keyValueStoreDelegate.contains(key) + + override fun <T : Any> getDefaultValue(key: String, valueType: Class<T>) = + keyValueStoreDelegate.getDefaultValue(key, valueType) + + override fun <T : Any> getValue(key: String, valueType: Class<T>) = + keyValueStoreDelegate.getValue(key, valueType) ?: getDefaultValue(key, valueType) + + override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) = + keyValueStoreDelegate.setValue(key, valueType, value) +} + /** [SharedPreferences] based [KeyValueStore]. */ interface SharedPreferencesKeyValueStore : KeyValueStore { @@ -103,11 +125,11 @@ interface SharedPreferencesKeyValueStore : KeyValueStore { @Suppress("UNCHECKED_CAST") override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { + val edit = sharedPreferences.edit() if (value == null) { - sharedPreferences.edit().remove(key).apply() + edit.remove(key).apply() return } - val edit = sharedPreferences.edit() when (valueType) { Boolean::class.javaObjectType -> edit.putBoolean(key, value as Boolean) Float::class.javaObjectType -> edit.putFloat(key, value as Float) @@ -115,7 +137,7 @@ interface SharedPreferencesKeyValueStore : KeyValueStore { Long::class.javaObjectType -> edit.putLong(key, value as Long) String::class.javaObjectType -> edit.putString(key, value as String) Set::class.javaObjectType -> edit.putStringSet(key, value as Set<String>) - else -> {} + else -> Log.e(LOG_TAG, "Unsupported $valueType for $key: $value") } edit.apply() } diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt index 07b1c9e3385e..ff58bf7b8728 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt @@ -116,8 +116,28 @@ interface KeyedObservable<K> { } /** Delegation of [KeyedObservable]. */ -open class KeyedObservableDelegate<K>(delegate: KeyedObservable<K>) : - KeyedObservable<K> by delegate +interface KeyedObservableDelegate<K> : KeyedObservable<K> { + + /** [KeyedObservable] to delegate. */ + val keyedObservableDelegate: KeyedObservable<K> + + override fun addObserver(observer: KeyedObserver<K?>, executor: Executor): Boolean = + keyedObservableDelegate.addObserver(observer, executor) + + override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor): Boolean = + keyedObservableDelegate.addObserver(key, observer, executor) + + override fun removeObserver(observer: KeyedObserver<K?>): Boolean = + keyedObservableDelegate.removeObserver(observer) + + override fun removeObserver(key: K, observer: KeyedObserver<K>): Boolean = + keyedObservableDelegate.removeObserver(key, observer) + + override fun notifyChange(reason: Int): Unit = keyedObservableDelegate.notifyChange(reason) + + override fun notifyChange(key: K, reason: Int): Unit = + keyedObservableDelegate.notifyChange(key, reason) +} /** A thread safe implementation of [KeyedObservable]. */ open class KeyedDataObservable<K> : KeyedObservable<K> { diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt new file mode 100644 index 000000000000..fdde3d3f5669 --- /dev/null +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt @@ -0,0 +1,69 @@ +/* + * 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.devicestate + +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK + +/** + * Interface for managing [DEVICE_STATE_ROTATION_LOCK] setting. + * + * It provides methods to register/unregister listeners for setting changes, update the setting for + * specific device states, retrieve the setting value, and check if rotation is locked for specific + * or all device states. + */ +interface DeviceStateAutoRotateSettingManager { + // TODO: b/397928958 - Rename all terms from rotationLock to autoRotate in all apis. + + /** Listener for changes in device-state based auto rotate setting. */ + interface DeviceStateAutoRotateSettingListener { + /** Called whenever the setting has changed. */ + fun onSettingsChanged() + } + + /** Register listener for changes to [DEVICE_STATE_ROTATION_LOCK] setting. */ + fun registerListener(settingListener: DeviceStateAutoRotateSettingListener) + + /** Unregister listener for changes to [DEVICE_STATE_ROTATION_LOCK] setting. */ + fun unregisterListener(settingListener: DeviceStateAutoRotateSettingListener) + + /** + * Write [deviceState]'s setting value as [autoRotate], for [DEVICE_STATE_ROTATION_LOCK] setting. + */ + fun updateSetting(deviceState: Int, autoRotate: Boolean) + + /** Get [DEVICE_STATE_ROTATION_LOCK] setting value for [deviceState]. */ + fun getRotationLockSetting(deviceState: Int): Int + + /** Returns true if auto-rotate setting is OFF for [deviceState]. */ + fun isRotationLocked(deviceState: Int): Boolean + + /** Returns true if the auto-rotate setting value for all device states is OFF. */ + fun isRotationLockedForAllStates(): Boolean + + /** Returns a list of device states and their respective auto rotate setting availability. */ + fun getSettableDeviceStates(): List<SettableDeviceState> +} + +/** Represents a device state and whether it has an auto-rotation setting. */ +data class SettableDeviceState( + /** Returns the device state associated with this object. */ + val deviceState: Int, + /** Returns whether there is an auto-rotation setting for this device state. */ + val isSettable: Boolean +) + + diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt new file mode 100644 index 000000000000..0b6c6e238956 --- /dev/null +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt @@ -0,0 +1,191 @@ +/* + * 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.devicestate + +import android.content.Context +import android.database.ContentObserver +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED +import android.util.Log +import android.util.SparseIntArray +import com.android.internal.R +import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener +import com.android.window.flags.Flags +import java.util.concurrent.Executor + +/** + * Implementation of [DeviceStateAutoRotateSettingManager]. This implementation is a part of + * refactoring, it should be used when [Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR] + * is enabled. + */ +class DeviceStateAutoRotateSettingManagerImpl( + context: Context, + backgroundExecutor: Executor, + private val secureSettings: SecureSettings, + private val mainHandler: Handler, + private val posturesHelper: PosturesHelper, +) : DeviceStateAutoRotateSettingManager { + // TODO: b/397928958 rename the fields and apis from rotationLock to autoRotate. + + private val settingListeners: MutableList<DeviceStateAutoRotateSettingListener> = + mutableListOf() + private val fallbackPostureMap = SparseIntArray() + private val settableDeviceState: MutableList<SettableDeviceState> = mutableListOf() + + private val autoRotateSettingValue: String + get() = secureSettings.getStringForUser(DEVICE_STATE_ROTATION_LOCK, UserHandle.USER_CURRENT) + + init { + loadAutoRotateDeviceStates(context) + val contentObserver = + object : ContentObserver(mainHandler) { + override fun onChange(selfChange: Boolean) = notifyListeners() + } + backgroundExecutor.execute { + secureSettings.registerContentObserver( + DEVICE_STATE_ROTATION_LOCK, false, contentObserver, UserHandle.USER_CURRENT + ) + } + } + + override fun registerListener(settingListener: DeviceStateAutoRotateSettingListener) { + settingListeners.add(settingListener) + } + + override fun unregisterListener(settingListener: DeviceStateAutoRotateSettingListener) { + if (!settingListeners.remove(settingListener)) { + Log.w(TAG, "Attempting to unregister a listener hadn't been registered") + } + } + + override fun getRotationLockSetting(deviceState: Int): Int { + val devicePosture = posturesHelper.deviceStateToPosture(deviceState) + val serializedSetting = autoRotateSettingValue + val autoRotateSetting = extractSettingForDevicePosture(devicePosture, serializedSetting) + + // If the setting is ignored for this posture, check the fallback posture. + if (autoRotateSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { + val fallbackPosture = + fallbackPostureMap.get(devicePosture, DEVICE_STATE_ROTATION_LOCK_IGNORED) + return extractSettingForDevicePosture(fallbackPosture, serializedSetting) + } + + return autoRotateSetting + } + + override fun isRotationLocked(deviceState: Int) = + getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED + + override fun isRotationLockedForAllStates(): Boolean = + convertSerializedSettingToMap(autoRotateSettingValue).all { (_, value) -> + value == DEVICE_STATE_ROTATION_LOCK_LOCKED + } + + override fun getSettableDeviceStates(): List<SettableDeviceState> = settableDeviceState + + override fun updateSetting(deviceState: Int, autoRotate: Boolean) { + // TODO: b/350946537 - Create IPC to update the setting, and call it here. + throw UnsupportedOperationException("API updateSetting is not implemented yet") + } + + private fun notifyListeners() = + settingListeners.forEach { listener -> listener.onSettingsChanged() } + + private fun loadAutoRotateDeviceStates(context: Context) { + val perDeviceStateAutoRotateDefaults = + context.resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults) + for (entry in perDeviceStateAutoRotateDefaults) { + entry.parsePostureEntry()?.let { (posture, autoRotate, fallbackPosture) -> + if (autoRotate == DEVICE_STATE_ROTATION_LOCK_IGNORED && fallbackPosture != null) { + fallbackPostureMap.put(posture, fallbackPosture) + } + settableDeviceState.add( + SettableDeviceState(posture, autoRotate != DEVICE_STATE_ROTATION_LOCK_IGNORED) + ) + } + } + } + + private fun convertSerializedSettingToMap(serializedSetting: String): Map<Int, Int> { + if (serializedSetting.isEmpty()) return emptyMap() + return try { + serializedSetting + .split(SEPARATOR_REGEX) + .hasEvenSize() + .chunked(2) + .mapNotNull(::parsePostureSettingPair) + .toMap() + } catch (e: Exception) { + Log.w( + TAG, + "Invalid format in serializedSetting=$serializedSetting: ${e.message}" + ) + return emptyMap() + } + } + + private fun List<String>.hasEvenSize(): List<String> { + if (this.size % 2 != 0) { + throw IllegalStateException("Odd number of elements in the list") + } + return this + } + + private fun parsePostureSettingPair(settingPair: List<String>): Pair<Int, Int>? { + return settingPair.let { (keyStr, valueStr) -> + val key = keyStr.toIntOrNull() + val value = valueStr.toIntOrNull() + if (key != null && value != null && value in 0..2) { + key to value + } else { + Log.w(TAG, "Invalid key or value in pair: $keyStr, $valueStr") + null // Invalid pair, skip it + } + } + } + + private fun extractSettingForDevicePosture( + devicePosture: Int, + serializedSetting: String + ): Int = + convertSerializedSettingToMap(serializedSetting)[devicePosture] + ?: DEVICE_STATE_ROTATION_LOCK_IGNORED + + private fun String.parsePostureEntry(): Triple<Int, Int, Int?>? { + val values = split(SEPARATOR_REGEX) + if (values.size !in 2..3) { // It should contain 2 or 3 values. + Log.w(TAG, "Invalid number of values in entry: '$this'") + return null + } + return try { + val posture = values[0].toInt() + val rotationLockSetting = values[1].toInt() + val fallbackPosture = if (values.size == 3) values[2].toIntOrNull() else null + Triple(posture, rotationLockSetting, fallbackPosture) + } catch (e: NumberFormatException) { + Log.w(TAG, "Invalid number format in '$this': ${e.message}") + null + } + } + + companion object { + private const val TAG = "DeviceStateAutoRotate" + private const val SEPARATOR_REGEX = ":" + } +} diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingUtils.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingUtils.kt new file mode 100644 index 000000000000..4d1d29242832 --- /dev/null +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingUtils.kt @@ -0,0 +1,31 @@ +/* + * 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. + */ +@file:JvmName("DeviceStateAutoRotateSettingUtils") + +package com.android.settingslib.devicestate + +import android.content.Context +import com.android.internal.R + +/** Returns true if device-state based rotation lock settings are enabled. */ +object DeviceStateAutoRotateSettingUtils { + @JvmStatic + fun isDeviceStateRotationLockEnabled(context: Context) = + context.resources + .getStringArray(R.array.config_perDeviceStateRotationLockDefaults) + .isNotEmpty() +} + diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java index 635f6905e4f0..deeba574f2ad 100644 --- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java @@ -20,6 +20,8 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORE import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED; import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED; +import static com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener; + import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; @@ -43,7 +45,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Set; /** @@ -58,7 +59,7 @@ public final class DeviceStateRotationLockSettingsManager { private static DeviceStateRotationLockSettingsManager sSingleton; private final Handler mMainHandler = new Handler(Looper.getMainLooper()); - private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>(); + private final Set<DeviceStateAutoRotateSettingListener> mListeners = new HashSet<>(); private final SecureSettings mSecureSettings; private final PosturesHelper mPosturesHelper; private String[] mPostureRotationLockDefaults; @@ -127,20 +128,20 @@ public final class DeviceStateRotationLockSettingsManager { } /** - * Registers a {@link DeviceStateRotationLockSettingsListener} to be notified when the settings + * Registers a {@link DeviceStateAutoRotateSettingListener} to be notified when the settings * change. Can be called multiple times with different listeners. */ - public void registerListener(DeviceStateRotationLockSettingsListener runnable) { + public void registerListener(DeviceStateAutoRotateSettingListener runnable) { mListeners.add(runnable); } /** - * Unregisters a {@link DeviceStateRotationLockSettingsListener}. No-op if the given instance + * Unregisters a {@link DeviceStateAutoRotateSettingListener}. No-op if the given instance * was never registered. */ public void unregisterListener( - DeviceStateRotationLockSettingsListener deviceStateRotationLockSettingsListener) { - if (!mListeners.remove(deviceStateRotationLockSettingsListener)) { + DeviceStateAutoRotateSettingListener deviceStateAutoRotateSettingListener) { + if (!mListeners.remove(deviceStateAutoRotateSettingListener)) { Log.w(TAG, "Attempting to unregister a listener hadn't been registered"); } } @@ -379,56 +380,8 @@ public final class DeviceStateRotationLockSettingsManager { } private void notifyListeners() { - for (DeviceStateRotationLockSettingsListener r : mListeners) { + for (DeviceStateAutoRotateSettingListener r : mListeners) { r.onSettingsChanged(); } } - - /** Listener for changes in device-state based rotation lock settings */ - public interface DeviceStateRotationLockSettingsListener { - /** Called whenever the settings have changed. */ - void onSettingsChanged(); - } - - /** Represents a device state and whether it has an auto-rotation setting. */ - public static class SettableDeviceState { - private final int mDeviceState; - private final boolean mIsSettable; - - SettableDeviceState(int deviceState, boolean isSettable) { - mDeviceState = deviceState; - mIsSettable = isSettable; - } - - /** Returns the device state associated with this object. */ - public int getDeviceState() { - return mDeviceState; - } - - /** Returns whether there is an auto-rotation setting for this device state. */ - public boolean isSettable() { - return mIsSettable; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SettableDeviceState)) return false; - SettableDeviceState that = (SettableDeviceState) o; - return mDeviceState == that.mDeviceState && mIsSettable == that.mIsSettable; - } - - @Override - public int hashCode() { - return Objects.hash(mDeviceState, mIsSettable); - } - - @Override - public String toString() { - return "SettableDeviceState{" - + "mDeviceState=" + mDeviceState - + ", mIsSettable=" + mIsSettable - + '}'; - } - } } diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java index 10528739b2b0..ea40e148aed6 100644 --- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java @@ -19,7 +19,7 @@ package com.android.settingslib.devicestate; import android.database.ContentObserver; /** Minimal wrapper interface around {@link android.provider.Settings.Secure} for easier testing. */ -interface SecureSettings { +public interface SecureSettings { void putStringForUser(String name, String value, int userHandle); diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt index 13541b1ebc9a..009d265833b4 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt @@ -58,6 +58,7 @@ import com.android.settingslib.metadata.PreferenceTitleProvider import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.metadata.SensitivityLevel.Companion.HIGH_SENSITIVITY import com.android.settingslib.metadata.SensitivityLevel.Companion.UNKNOWN_SENSITIVITY +import com.android.settingslib.metadata.getPreferenceIcon import com.android.settingslib.preference.PreferenceScreenFactory import com.android.settingslib.preference.PreferenceScreenProvider import java.util.Locale diff --git a/packages/SettingsLib/IllustrationPreference/res/values/strings.xml b/packages/SettingsLib/IllustrationPreference/res/values/strings.xml index 3a8aaf8b5092..03da0dc41d47 100644 --- a/packages/SettingsLib/IllustrationPreference/res/values/strings.xml +++ b/packages/SettingsLib/IllustrationPreference/res/values/strings.xml @@ -20,8 +20,6 @@ <string name="settingslib_action_label_resume">resume</string> <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=30] --> <string name="settingslib_action_label_pause">pause</string> - <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=50] --> - <string name="settingslib_state_animation_playing">Animation playing</string> - <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=50] --> - <string name="settingslib_state_animation_paused">Animation paused</string> + <!-- Default content description attached to the illustration if there is no content description. [CHAR LIMIT=NONE] --> + <string name="settingslib_illustration_content_description">Animation</string> </resources> diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java index bf739620bc99..777607010b3a 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java @@ -474,6 +474,10 @@ public class IllustrationPreference extends Preference implements GroupSectionDi if (mIsAnimatable) { // TODO(b/397340540): list out pages having illustration without a content description. if (TextUtils.isEmpty(mContentDescription)) { + // Default content description will be attached if there's no content description. + illustrationView.setContentDescription( + getContext().getString( + R.string.settingslib_illustration_content_description)); Log.w(TAG, "Illustration should have a content description. preference key = " + getKey()); } @@ -493,8 +497,6 @@ public class IllustrationPreference extends Preference implements GroupSectionDi } private void updateAccessibilityAction(ViewGroup container) { - // Setting the state of animation - container.setStateDescription(getStateDescriptionForAnimation()); container.setAccessibilityDelegate(new View.AccessibilityDelegate() { @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { @@ -515,14 +517,6 @@ public class IllustrationPreference extends Preference implements GroupSectionDi } } - private String getStateDescriptionForAnimation() { - if (mIsAnimationPaused) { - return getContext().getString(R.string.settingslib_state_animation_paused); - } else { - return getContext().getString(R.string.settingslib_state_animation_playing); - } - } - private static void startLottieAnimationWith(LottieAnimationView illustrationView, Uri imageUri) { final InputStream inputStream = diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java index 7bd4b3f771ab..9d4c5c2735fc 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java @@ -113,8 +113,6 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen mSwitch.setOnCheckedChangeListener(this); } - setChecked(mSwitch.isChecked()); - if (attrs != null) { final TypedArray a = context.obtainStyledAttributes(attrs, androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/, @@ -130,8 +128,6 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen } a.recycle(); } - - setBackground(mSwitch.isChecked()); } @Override diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt index 7f2a61081fbb..fcca82330d10 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt @@ -137,44 +137,6 @@ interface PreferenceMetadata { /** Returns preference intent. */ fun intent(context: Context): Intent? = null - - /** - * Returns the preference title. - * - * Implement [PreferenceTitleProvider] interface if title content is generated dynamically. - */ - fun getPreferenceTitle(context: Context): CharSequence? = - when { - title != 0 -> context.getText(title) - this is PreferenceTitleProvider -> getTitle(context) - else -> null - } - - /** - * Returns the preference summary. - * - * Implement [PreferenceSummaryProvider] interface if summary content is generated dynamically - * (e.g. summary is provided per preference value). - */ - fun getPreferenceSummary(context: Context): CharSequence? = - when { - summary != 0 -> context.getText(summary) - this is PreferenceSummaryProvider -> getSummary(context) - else -> null - } - - /** - * Returns the preference icon. - * - * Implement [PreferenceIconProvider] interface if icon is provided dynamically (e.g. icon is - * provided based on flag value). - */ - fun getPreferenceIcon(context: Context): Int = - when { - icon != 0 -> icon - this is PreferenceIconProvider -> getIcon(context) - else -> 0 - } } /** Metadata of preference group. */ diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Utils.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Utils.kt new file mode 100644 index 000000000000..6d580fb47160 --- /dev/null +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Utils.kt @@ -0,0 +1,50 @@ +/* + * 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.metadata + +import android.content.Context + +/** Returns the preference screen title. */ +fun PreferenceScreenMetadata.getPreferenceScreenTitle(context: Context): CharSequence? = + when { + screenTitle != 0 -> context.getString(screenTitle) + else -> getScreenTitle(context) ?: (this as? PreferenceTitleProvider)?.getTitle(context) + } + +/** Returns the preference title. */ +fun PreferenceMetadata.getPreferenceTitle(context: Context): CharSequence? = + when { + title != 0 -> context.getText(title) + this is PreferenceTitleProvider -> getTitle(context) + else -> null + } + +/** Returns the preference summary. */ +fun PreferenceMetadata.getPreferenceSummary(context: Context): CharSequence? = + when { + summary != 0 -> context.getText(summary) + this is PreferenceSummaryProvider -> getSummary(context) + else -> null + } + +/** Returns the preference icon. */ +fun PreferenceMetadata.getPreferenceIcon(context: Context): Int = + when { + icon != 0 -> icon + this is PreferenceIconProvider -> getIcon(context) + else -> 0 + } diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt index 59141c99e587..8896af47a1c2 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt @@ -25,10 +25,16 @@ import androidx.preference.PreferenceScreen import androidx.preference.SeekBarPreference import com.android.settingslib.metadata.DiscreteIntValue import com.android.settingslib.metadata.DiscreteValue +import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS +import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY import com.android.settingslib.metadata.IntRangeValuePreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceScreenMetadata +import com.android.settingslib.metadata.getPreferenceIcon +import com.android.settingslib.metadata.getPreferenceScreenTitle +import com.android.settingslib.metadata.getPreferenceSummary +import com.android.settingslib.metadata.getPreferenceTitle /** Binding of preference widget and preference metadata. */ interface PreferenceBinding { @@ -72,9 +78,22 @@ interface PreferenceBinding { preference.icon = null } val isPreferenceScreen = preference is PreferenceScreen + val screenMetadata = this as? PreferenceScreenMetadata + // extras preference.peekExtras()?.clear() extras(context)?.let { preference.extras.putAll(it) } - preference.title = getPreferenceTitle(context) + if (!isPreferenceScreen && screenMetadata != null) { + val extras = preference.extras + // Pass the preference key to fragment, so that the fragment could find associated + // preference screen registered in PreferenceScreenRegistry + extras.putString(EXTRA_BINDING_SCREEN_KEY, preference.key) + screenMetadata.arguments?.let { extras.putBundle(EXTRA_BINDING_SCREEN_ARGS, it) } + } + preference.title = + when { + isPreferenceScreen -> screenMetadata?.getPreferenceScreenTitle(context) + else -> getPreferenceTitle(context) + } if (!isPreferenceScreen) { preference.summary = getPreferenceSummary(context) } @@ -82,12 +101,12 @@ interface PreferenceBinding { preference.isVisible = (this as? PreferenceAvailabilityProvider)?.isAvailable(context) != false preference.isPersistent = isPersistent(context) - // PreferenceRegistry will notify dependency change, so we do not need to set + // PreferenceScreenBindingHelper will notify dependency change, so we do not need to set // dependency here. This simplifies dependency management and avoid the // IllegalStateException when call Preference.setDependency preference.dependency = null if (!isPreferenceScreen) { // avoid recursive loop when build graph - preference.fragment = (this as? PreferenceScreenCreator)?.fragmentClass()?.name + preference.fragment = screenMetadata?.fragmentClass()?.name preference.intent = intent(context) } if (preference is DialogPreference) { diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt index 6287fda86a73..33b614e19bbe 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt @@ -60,7 +60,6 @@ open class DefaultPreferenceBindingFactory : PreferenceBindingFactory { ?: when (metadata) { is SwitchPreference -> SwitchPreferenceBinding.INSTANCE is PreferenceCategory -> PreferenceCategoryBinding.INSTANCE - is PreferenceScreenCreator -> PreferenceScreenBinding.INSTANCE is MainSwitchPreference -> MainSwitchPreferenceBinding.INSTANCE else -> DefaultPreferenceBinding } diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt index 44c93c77e33b..71c46fa76aed 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt @@ -19,45 +19,11 @@ package com.android.settingslib.preference import android.content.Context import androidx.preference.Preference import androidx.preference.PreferenceCategory -import androidx.preference.PreferenceScreen import androidx.preference.SwitchPreferenceCompat import androidx.preference.TwoStatePreference -import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS -import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY import com.android.settingslib.metadata.PreferenceMetadata -import com.android.settingslib.metadata.PreferenceScreenMetadata -import com.android.settingslib.metadata.PreferenceTitleProvider import com.android.settingslib.widget.MainSwitchPreference -/** Binding of preference group associated with [PreferenceCategory]. */ -interface PreferenceScreenBinding : PreferenceBinding { - - override fun bind(preference: Preference, metadata: PreferenceMetadata) { - super.bind(preference, metadata) - val context = preference.context - val screenMetadata = metadata as PreferenceScreenMetadata - val extras = preference.extras - // Pass the preference key to fragment, so that the fragment could find associated - // preference screen registered in PreferenceScreenRegistry - extras.putString(EXTRA_BINDING_SCREEN_KEY, preference.key) - screenMetadata.arguments?.let { extras.putBundle(EXTRA_BINDING_SCREEN_ARGS, it) } - if (preference is PreferenceScreen) { - val screenTitle = screenMetadata.screenTitle - preference.title = - if (screenTitle != 0) { - context.getString(screenTitle) - } else { - screenMetadata.getScreenTitle(context) - ?: (screenMetadata as? PreferenceTitleProvider)?.getTitle(context) - } - } - } - - companion object { - @JvmStatic val INSTANCE = object : PreferenceScreenBinding {} - } -} - /** Binding of preference category associated with [PreferenceCategory]. */ interface PreferenceCategoryBinding : PreferenceBinding { diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml index 686c1488fb62..112a69bb47ec 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml @@ -262,4 +262,30 @@ <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> </style> + + <style name="SettingsLibEntityHeaderContent"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_centerHorizontal">true</item> + <item name="android:orientation">vertical</item> + <item name="android:gravity">center_horizontal</item> + </style> + + <style name="SettingsLibEntityHeaderIcon"> + <item name="android:layout_width">@dimen/settingslib_expressive_space_large3</item> + <item name="android:layout_height">@dimen/settingslib_expressive_space_large3</item> + <item name="android:scaleType">fitCenter</item> + <item name="android:antialias">true</item> + </style> + + <style name="SettingsLibEntityHeaderTitle"> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_marginTop">@dimen/settingslib_expressive_space_small1</item> + <item name="android:singleLine">false</item> + <item name="android:gravity">center</item> + <item name="android:ellipsize">marquee</item> + <item name="android:textDirection">locale</item> + <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item> + </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java b/packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java index fe8e8b6f1d46..6d02c5d48715 100644 --- a/packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java +++ b/packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java @@ -16,6 +16,8 @@ package com.android.settingslib.widget; +import static android.view.HapticFeedbackConstants.CLOCK_TICK; + import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -46,6 +48,9 @@ import com.google.android.material.slider.Slider; */ public class SliderPreference extends Preference { private static final String TAG = "SliderPreference"; + public static final int HAPTIC_FEEDBACK_MODE_NONE = 0; + public static final int HAPTIC_FEEDBACK_MODE_ON_TICKS = 1; + public static final int HAPTIC_FEEDBACK_MODE_ON_ENDS = 2; private final int mTextStartId; private final int mTextEndId; @@ -71,6 +76,8 @@ public class SliderPreference extends Preference { private int mMin; private int mMax; private int mSliderIncrement; + private int mHapticFeedbackMode = HAPTIC_FEEDBACK_MODE_NONE; + private boolean mTickVisible = false; private boolean mAdjustable; private boolean mTrackingTouch; private CharSequence mSliderContentDescription; @@ -265,6 +272,7 @@ public class SliderPreference extends Preference { } if (mSliderIncrement != 0) { mSlider.setStepSize(mSliderIncrement); + mSlider.setTickVisible(mTickVisible); } else { mSliderIncrement = (int) (mSlider.getStepSize()); } @@ -442,6 +450,29 @@ public class SliderPreference extends Preference { } /** + * Sets the haptic feedback mode. HAPTIC_FEEDBACK_MODE_ON_TICKS means to perform haptic feedback + * as the {@link Slider} value is updated; HAPTIC_FEEDBACK_MODE_ON_ENDS means to perform haptic + * feedback as the {@link Slider} value is equal to the min/max value. + * + * @param hapticFeedbackMode The haptic feedback mode. + */ + public void setHapticFeedbackMode(int hapticFeedbackMode) { + mHapticFeedbackMode = hapticFeedbackMode; + } + + /** + * Sets whether the tick marks are visible. Only used when the slider is in discrete mode. + * + * @param tickVisible The visibility of tick marks. + */ + public void setTickVisible(boolean tickVisible) { + if (tickVisible != mTickVisible) { + mTickVisible = tickVisible; + notifyChanged(); + } + } + + /** * Gets whether the current {@link Slider} value is displayed to the user. * * @return Whether the current {@link Slider} value is displayed to the user @@ -519,7 +550,16 @@ public class SliderPreference extends Preference { if (sliderValue != mSliderValue) { if (callChangeListener(sliderValue)) { setValueInternal(sliderValue, false); - // TODO: mHapticFeedbackMode + switch (mHapticFeedbackMode) { + case HAPTIC_FEEDBACK_MODE_ON_TICKS: + slider.performHapticFeedback(CLOCK_TICK); + break; + case HAPTIC_FEEDBACK_MODE_ON_ENDS: + if (mSliderValue == mMax || mSliderValue == mMin) { + slider.performHapticFeedback(CLOCK_TICK); + } + break; + } } else { slider.setValue(mSliderValue); } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 03cb1ffbdef1..1297aa3ff7d5 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1510,6 +1510,9 @@ <!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] --> <string name="profile_connect_timeout_subtext">Problem connecting. Turn device off & back on</string> + <!-- Warning message when the bluetooth key is missing. [CHAR_LIMIT=NONE] --> + <string name="bluetooth_key_missing_subtext">Can’t connect</string> + <!-- Name of the 3.5mm audio device. [CHAR LIMIT=40] --> <string name="media_transfer_wired_device_name">Wired audio device</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreferenceBinding.kt index a64e8cc07b15..348941335311 100644 --- a/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt +++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreferenceBinding.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:Suppress("ktlint:standard:filename") // remove once we have more bindings package com.android.settingslib @@ -29,7 +28,8 @@ interface PrimarySwitchPreferenceBinding : PreferenceBinding { override fun bind(preference: Preference, metadata: PreferenceMetadata) { super.bind(preference, metadata) - (preference as PrimarySwitchPreference).apply { + // Could bind on PreferenceScreen + (preference as? PrimarySwitchPreference)?.apply { isChecked = preferenceDataStore!!.getBoolean(key, false) isSwitchEnabled = isEnabled } diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 9d979019be58..bf6006b1eddc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -151,6 +151,18 @@ public class RestrictedPreferenceHelper { UserHandle.myUserId()); } + /** + * Configures the user restriction that this preference will track. This is equivalent to + * specifying {@link R.styleable#RestrictedPreference_userRestriction} in XML and allows + * configuring user restriction at runtime. + */ + public void setUserRestriction(@Nullable String userRestriction) { + mAttrUserRestriction = userRestriction == null || + RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext, userRestriction, + UserHandle.myUserId()) ? null : userRestriction; + setDisabledByAdmin(checkRestrictionEnforced()); + } + public void useAdminDisabledSummary(boolean useSummary) { mDisabledSummary = useSummary; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 3646842d36ef..011b2fc15807 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1495,6 +1495,11 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> int leftBattery = -1; int rightBattery = -1; + Integer keyMissingCount = BluetoothUtils.getKeyMissingCount(mDevice); + if (keyMissingCount != null && keyMissingCount > 0) { + return mContext.getString(R.string.bluetooth_key_missing_subtext); + } + if (isProfileConnectedFail() && isConnected()) { return mContext.getString(R.string.profile_connect_timeout_subtext); } @@ -1863,10 +1868,31 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> + " mIsLeAudioProfileConnectedFail=" + mIsLeAudioProfileConnectedFail + " mIsHeadsetProfileConnectedFail=" + mIsHeadsetProfileConnectedFail + " isConnectedSapDevice()=" + isConnectedSapDevice()); - - return mIsA2dpProfileConnectedFail || mIsHearingAidProfileConnectedFail - || (!isConnectedSapDevice() && mIsHeadsetProfileConnectedFail) - || mIsLeAudioProfileConnectedFail; + if (mIsA2dpProfileConnectedFail) { + A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); + if (a2dpProfile != null && a2dpProfile.isEnabled(mDevice)) { + return true; + } + } + if (mIsHearingAidProfileConnectedFail) { + HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile(); + if (hearingAidProfile != null && hearingAidProfile.isEnabled(mDevice)) { + return true; + } + } + if (!isConnectedSapDevice() && mIsHeadsetProfileConnectedFail) { + HeadsetProfile headsetProfile = mProfileManager.getHeadsetProfile(); + if (headsetProfile != null && headsetProfile.isEnabled(mDevice)) { + return true; + } + } + if (mIsLeAudioProfileConnectedFail) { + LeAudioProfile leAudioProfile = mProfileManager.getLeAudioProfile(); + if (leAudioProfile != null && leAudioProfile.isEnabled(mDevice)) { + return true; + } + } + return false; } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 08f7806207db..01d8694256f3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -89,11 +89,14 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE"; public static final String ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED = "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_DEVICE_CONNECTED"; + public static final String ACTION_LE_AUDIO_PRIVATE_BROADCAST_RECEIVED = + "com.android.settings.action.BLUETOOTH_LE_AUDIO_PRIVATE_BROADCAST_RECEIVED"; public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE"; public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE"; public static final String EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE = "BT_DEVICE_TO_AUTO_ADD_SOURCE"; public static final String EXTRA_START_LE_AUDIO_SHARING = "START_LE_AUDIO_SHARING"; public static final String EXTRA_PAIR_AND_JOIN_SHARING = "PAIR_AND_JOIN_SHARING"; + public static final String EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA = "RECEIVE_DATA"; public static final String BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID = "bluetooth_le_broadcast_primary_device_group_id"; public static final int BROADCAST_STATE_UNKNOWN = 0; @@ -721,7 +724,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Log.d(TAG, "The BluetoothLeBroadcast is null"); return null; } - if (mBluetoothLeBroadcastMetadata == null) { + if (mBluetoothLeBroadcastMetadata == null + // mBroadcastId is updated when onBroadcastStarted, which is always before + // onBroadcastMetadataChanged, so mBroadcastId is always the latest broadcast info + || mBluetoothLeBroadcastMetadata.getBroadcastId() != mBroadcastId) { final List<BluetoothLeBroadcastMetadata> metadataList = mServiceBroadcast.getAllBroadcastMetadata(); mBluetoothLeBroadcastMetadata = @@ -729,6 +735,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { .filter(i -> i.getBroadcastId() == mBroadcastId) .findFirst() .orElse(null); + Log.d(TAG, "getLatestBluetoothLeBroadcastMetadata for broadcast id " + mBroadcastId); } return mBluetoothLeBroadcastMetadata; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveData.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveData.kt new file mode 100644 index 000000000000..a284d2010195 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveData.kt @@ -0,0 +1,90 @@ +/* + * 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.bluetooth + +import android.bluetooth.BluetoothDevice +import android.os.Parcel +import android.os.Parcelable +import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState +import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED +import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING +import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED + +/** + * Data class representing information received in a private broadcast. + * This class encapsulates details about the sink device, source ID, broadcast ID, and the + * broadcast source state. + * + * @param sink The [BluetoothDevice] acting as the sink. + * @param sourceId The ID of the audio source. + * @param broadcastId The ID of the broadcast source. + * @param programInfo The program info string of the broadcast source. + * @param state The current state of the broadcast source. + */ +data class PrivateBroadcastReceiveData( + val sink: BluetoothDevice?, + val sourceId: Int = -1, + val broadcastId: Int = -1, + val programInfo: String = "", + val state: LocalBluetoothLeBroadcastSourceState?, +) : Parcelable { + + override fun describeContents(): Int = 0 + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeParcelable(sink, flags) + parcel.writeInt(sourceId) + parcel.writeInt(broadcastId) + parcel.writeString(programInfo) + parcel.writeSerializable(state) + } + + companion object { + @JvmField + val CREATOR: Parcelable.Creator<PrivateBroadcastReceiveData> = + object : Parcelable.Creator<PrivateBroadcastReceiveData> { + override fun createFromParcel(parcel: Parcel) = + parcel.run { + PrivateBroadcastReceiveData( + sink = readParcelable( + BluetoothDevice::class.java.classLoader, + BluetoothDevice::class.java + ), + sourceId = readInt(), + broadcastId = readInt(), + programInfo = readString() ?: "", + state = readSerializable( + LocalBluetoothLeBroadcastSourceState::class.java.classLoader, + LocalBluetoothLeBroadcastSourceState::class.java + ) + ) + } + override fun newArray(size: Int): Array<PrivateBroadcastReceiveData?> { + return arrayOfNulls(size) + } + } + + fun PrivateBroadcastReceiveData.isValid(): Boolean { + return sink != null + && sourceId != -1 + && broadcastId != -1 + && (state == STREAMING + || state == PAUSED + || state == DECRYPTION_FAILED) + } + } +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt new file mode 100644 index 000000000000..78dba57028ba --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt @@ -0,0 +1,300 @@ +/* + * 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.devicestate + +import android.content.ContentResolver +import android.content.Context +import android.content.res.Resources +import android.hardware.devicestate.DeviceStateManager +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_FOLDED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_HALF_FOLDED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_UNFOLDED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import java.util.concurrent.Executor +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceStateAutoRotateSettingManagerImplTest { + @get:Rule + val rule = MockitoJUnit.rule() + + private val fakeSecureSettings = FakeSecureSettings() + private val executor: Executor = Executor { it.run() } + private val configPerDeviceStateRotationLockDefaults = arrayOf( + "$DEVICE_STATE_ROTATION_KEY_HALF_FOLDED:" + + "$DEVICE_STATE_ROTATION_LOCK_IGNORED:" + + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED", + "$DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY:" + + "$DEVICE_STATE_ROTATION_LOCK_IGNORED:" + + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED", + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED", + "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED", + ) + + @Mock + private lateinit var mockContext: Context + + @Mock + private lateinit var mockContentResolver: ContentResolver + + @Mock + private lateinit var mockPosturesHelper: PosturesHelper + + @Mock + private lateinit var mockHandler: Handler + + @Mock + private lateinit var mockDeviceStateManager: DeviceStateManager + + @Mock + private lateinit var mockResources: Resources + private lateinit var settingManager: DeviceStateAutoRotateSettingManagerImpl + + @Before + fun setUp() { + whenever(mockContext.contentResolver).thenReturn(mockContentResolver) + whenever(mockContext.resources).thenReturn(mockResources) + whenever(mockResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults)) + .thenReturn(configPerDeviceStateRotationLockDefaults) + whenever(mockHandler.post(any(Runnable::class.java))).thenAnswer { invocation -> + val runnable = invocation.arguments[0] as Runnable + runnable.run() + null + } + whenever(mockContext.getSystemService(DeviceStateManager::class.java)) + .thenReturn(mockDeviceStateManager) + whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_UNFOLDED)) + .thenReturn(DEVICE_STATE_ROTATION_KEY_UNFOLDED) + whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_FOLDED)) + .thenReturn(DEVICE_STATE_ROTATION_KEY_FOLDED) + whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_HALF_FOLDED)) + .thenReturn(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED) + whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_INVALID)) + .thenReturn(DEVICE_STATE_ROTATION_LOCK_IGNORED) + whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_REAR_DISPLAY)) + .thenReturn(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY) + + settingManager = + DeviceStateAutoRotateSettingManagerImpl( + mockContext, + executor, + fakeSecureSettings, + mockHandler, + mockPosturesHelper, + ) + } + + @Test + fun registerListener_onSettingsChanged_listenerNotified() { + val listener = mock(DeviceStateAutoRotateSettingListener::class.java) + settingManager.registerListener(listener) + + persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED) + + verify(listener).onSettingsChanged() + } + + @Test + fun registerMultipleListeners_onSettingsChanged_allListenersNotified() { + val listener1 = mock(DeviceStateAutoRotateSettingListener::class.java) + val listener2 = mock(DeviceStateAutoRotateSettingListener::class.java) + settingManager.registerListener(listener1) + settingManager.registerListener(listener2) + + persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED) + + verify(listener1).onSettingsChanged() + verify(listener2).onSettingsChanged() + } + + @Test + fun unregisterListener_onSettingsChanged_listenerNotNotified() { + val listener = mock(DeviceStateAutoRotateSettingListener::class.java) + settingManager.registerListener(listener) + settingManager.unregisterListener(listener) + + persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED) + + verify(listener, never()).onSettingsChanged() + } + + @Test + fun getAutoRotateSetting_offForUnfolded_returnsOff() { + persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED) + + val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_UNFOLDED) + + assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_LOCKED) + } + + @Test + fun getAutoRotateSetting_onForFolded_returnsOn() { + persistSettings(DEVICE_STATE_ROTATION_KEY_FOLDED, DEVICE_STATE_ROTATION_LOCK_UNLOCKED) + + val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED) + + assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_UNLOCKED) + } + + @Test + fun getAutoRotateSetting_forInvalidPostureWithNoFallback_returnsIgnored() { + val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_INVALID) + + assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED) + } + + @Test + fun getAutoRotateSetting_forInvalidPosture_returnsSettingForFallbackPosture() { + persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_UNLOCKED) + persistSettings(DEVICE_STATE_ROTATION_KEY_FOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED) + + val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_HALF_FOLDED) + + assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_UNLOCKED) + } + + @Test + fun getAutoRotateSetting_invalidFormat_returnsIgnored() { + persistSettings("invalid_format") + + val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED) + + assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED) + } + + @Test + fun getAutoRotateSetting_invalidNumberFormat_returnsIgnored() { + persistSettings("$DEVICE_STATE_ROTATION_KEY_FOLDED:4") + + val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED) + + assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED) + } + + @Test + fun getAutoRotateSetting_multipleSettings_returnsCorrectSetting() { + persistSettings( + "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED:" + + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED" + ) + + val foldedSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED) + val unfoldedSetting = settingManager.getRotationLockSetting(DEVICE_STATE_UNFOLDED) + + assertThat(foldedSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_LOCKED) + assertThat(unfoldedSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_UNLOCKED) + } + + @Test + fun isAutoRotateOff_offForUnfolded_returnsTrue() { + persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED) + + val isAutoRotateOff = settingManager.isRotationLocked(DEVICE_STATE_UNFOLDED) + + assertThat(isAutoRotateOff).isTrue() + } + + @Test + fun isRotationLockedForAllStates_allStatesLocked_returnsTrue() { + persistSettings( + "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED:" + + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED" + ) + + val isRotationLockedForAllStates = settingManager.isRotationLockedForAllStates() + + assertThat(isRotationLockedForAllStates).isTrue() + } + + @Test + fun isRotationLockedForAllStates_someStatesLocked_returnsFalse() { + persistSettings( + "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED:" + + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED" + ) + + val isRotationLockedForAllStates = settingManager.isRotationLockedForAllStates() + + assertThat(isRotationLockedForAllStates).isFalse() + } + + @Test + fun isRotationLockedForAllStates_noStatesLocked_returnsFalse() { + persistSettings( + "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED:" + + "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED" + ) + + val isRotationLockedForAllStates = settingManager.isRotationLockedForAllStates() + + assertThat(isRotationLockedForAllStates).isFalse() + } + + @Test + fun getSettableDeviceStates_returnsExpectedValuesInOriginalOrder() { + val settableDeviceStates = settingManager.getSettableDeviceStates() + + assertThat(settableDeviceStates) + .containsExactly( + SettableDeviceState(DEVICE_STATE_ROTATION_KEY_UNFOLDED, isSettable = true), + SettableDeviceState(DEVICE_STATE_ROTATION_KEY_FOLDED, isSettable = true), + SettableDeviceState(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED, isSettable = false), + SettableDeviceState(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY, isSettable = false), + SettableDeviceState(DEVICE_STATE_ROTATION_LOCK_IGNORED, isSettable = false), + ) + } + + private fun persistSettings(devicePosture: Int, autoRotateSetting: Int) { + persistSettings("$devicePosture:$autoRotateSetting") + } + + private fun persistSettings(value: String) { + fakeSecureSettings.putStringForUser( + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, value, UserHandle.USER_CURRENT + ) + } + + private companion object { + const val DEVICE_STATE_FOLDED = 0 + const val DEVICE_STATE_HALF_FOLDED = 1 + const val DEVICE_STATE_UNFOLDED = 2 + const val DEVICE_STATE_REAR_DISPLAY = 3 + const val DEVICE_STATE_INVALID = 4 + } +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java index 9f9aaf5ff83a..baebaf7dfef0 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java @@ -40,7 +40,6 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; -import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState; import com.google.common.truth.Expect; diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java index 08484bceb1fb..8d9982fe7210 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java @@ -2,9 +2,9 @@ package com.android.settingslib.graph; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyFloat; -import static org.mockito.Matchers.anyString; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java index 9ab17c49bbec..b849919cb389 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java @@ -18,10 +18,10 @@ package com.android.settingslib.users; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java index ab28d061419c..94bb61614411 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java @@ -16,11 +16,11 @@ package com.android.settingslib.users; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.nullable; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java index 995314e44767..055487b79f78 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java @@ -22,7 +22,7 @@ import static com.android.settingslib.avatarpicker.AvatarPhotoController.REQUEST import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.never; diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java index e7533bc1044b..7f4d366b76d4 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java @@ -71,7 +71,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; -import org.mockito.Matchers; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; @@ -197,7 +197,7 @@ public class WifiTrackerTest { .registerNetworkScoreCache( anyInt(), mScoreCacheCaptor.capture(), - Matchers.anyInt()); + ArgumentMatchers.anyInt()); // Capture requested keys and count down latch if present doAnswer( @@ -213,7 +213,7 @@ public class WifiTrackerTest { } return true; } - }).when(mockNetworkScoreManager).requestScores(Matchers.<NetworkKey[]>any()); + }).when(mockNetworkScoreManager).requestScores(ArgumentMatchers.<NetworkKey[]>any()); // We use a latch to detect callbacks as Tracker initialization state often invokes // callbacks @@ -464,9 +464,9 @@ public class WifiTrackerTest { startTracking(tracker); verify(mockNetworkScoreManager) .registerNetworkScoreCache( - Matchers.anyInt(), + ArgumentMatchers.anyInt(), mScoreCacheCaptor.capture(), - Matchers.anyInt()); + ArgumentMatchers.anyInt()); WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index ed53d8d04988..f57ee0c0930e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -182,6 +182,8 @@ public class CachedBluetoothDeviceTest { updateProfileStatus(connectingProfile, BluetoothProfile.STATE_CONNECTING); // Set connection policy when(connectingProfile.getConnectionPolicy(mDevice)).thenReturn(connectionPolicy); + when(connectingProfile.isEnabled(mDevice)) + .thenReturn(connectionPolicy > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); // Act & Assert: // Get the expected connection summary. @@ -191,6 +193,9 @@ public class CachedBluetoothDeviceTest { @Test public void onProfileStateChanged_testConnectingToDisconnected_policyAllowed_problem() { + when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); + when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); + String connectTimeoutString = mContext.getString(R.string.profile_connect_timeout_subtext); testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile, @@ -205,6 +210,9 @@ public class CachedBluetoothDeviceTest { @Test public void onProfileStateChanged_testConnectingToDisconnected_policyForbidden_noProblem() { + when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); + when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); + testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null); testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile, @@ -217,6 +225,9 @@ public class CachedBluetoothDeviceTest { @Test public void onProfileStateChanged_testConnectingToDisconnected_policyUnknown_noProblem() { + when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); + when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); + testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null); testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile, @@ -1830,6 +1841,10 @@ public class CachedBluetoothDeviceTest { @Test public void getConnectionSummary_profileConnectedFail_showErrorMessage() { final A2dpProfile profile = mock(A2dpProfile.class); + when(mProfileManager.getA2dpProfile()).thenReturn(profile); + when(profile.getConnectionPolicy(mDevice)) + .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); + when(profile.isEnabled(mDevice)).thenReturn(true); mCachedDevice.onProfileStateChanged(profile, BluetoothProfile.STATE_CONNECTED); mCachedDevice.setProfileConnectedStatus(BluetoothProfile.A2DP, true); @@ -1842,6 +1857,10 @@ public class CachedBluetoothDeviceTest { @Test public void getTvConnectionSummary_profileConnectedFail_showErrorMessage() { final A2dpProfile profile = mock(A2dpProfile.class); + when(mProfileManager.getA2dpProfile()).thenReturn(profile); + when(profile.getConnectionPolicy(mDevice)) + .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); + when(profile.isEnabled(mDevice)).thenReturn(true); mCachedDevice.onProfileStateChanged(profile, BluetoothProfile.STATE_CONNECTED); mCachedDevice.setProfileConnectedStatus(BluetoothProfile.A2DP, true); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveDataTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveDataTest.kt new file mode 100644 index 000000000000..5fd67a16a305 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveDataTest.kt @@ -0,0 +1,124 @@ +/* + * 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.bluetooth + +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.os.Parcel +import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState +import com.android.settingslib.bluetooth.PrivateBroadcastReceiveData.Companion.isValid +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class PrivateBroadcastReceiveDataTest { + + @Test + fun parcelable() { + val original = PrivateBroadcastReceiveData( + sink = sink, + sourceId = 1, + broadcastId = 2, + programInfo = "Test Program", + state = LocalBluetoothLeBroadcastSourceState.STREAMING + ) + + val parcel = Parcel.obtain() + original.writeToParcel(parcel, 0) + parcel.setDataPosition(0) + + val recreated = PrivateBroadcastReceiveData.CREATOR.createFromParcel(parcel) + + assertEquals(original, recreated) + } + + @Test + fun isValid_validData() { + val data = PrivateBroadcastReceiveData( + sink = sink, + sourceId = 1, + broadcastId = 2, + state = LocalBluetoothLeBroadcastSourceState.STREAMING + ) + assertTrue(data.isValid()) + } + + @Test + fun isValid_nullSink() { + val data = PrivateBroadcastReceiveData( + sink = null, + sourceId = 1, + broadcastId = 2, + state = LocalBluetoothLeBroadcastSourceState.STREAMING + ) + assertFalse(data.isValid()) + } + + @Test + fun isValid_invalidSourceId() { + val data = PrivateBroadcastReceiveData( + sink = sink, + sourceId = -1, + broadcastId = 2, + state = LocalBluetoothLeBroadcastSourceState.STREAMING + ) + assertFalse(data.isValid()) + } + + @Test + fun isValid_invalidBroadcastId() { + val data = PrivateBroadcastReceiveData( + sink = sink, + sourceId = 1, + broadcastId = -1, + state = LocalBluetoothLeBroadcastSourceState.STREAMING + ) + assertFalse(data.isValid()) + } + + @Test + fun isValid_nullState() { + val data = PrivateBroadcastReceiveData( + sink = sink, + sourceId = 1, + broadcastId = 2, + state = null + ) + assertFalse(data.isValid()) + } + + @Test + fun isValid_correctStates() { + assertTrue(PrivateBroadcastReceiveData(sink, 1, 1, state = LocalBluetoothLeBroadcastSourceState.STREAMING).isValid()) + assertTrue(PrivateBroadcastReceiveData(sink, 1, 1, state = LocalBluetoothLeBroadcastSourceState.PAUSED).isValid()) + assertTrue(PrivateBroadcastReceiveData(sink, 1, 1, state = LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED).isValid()) + } + + private companion object { + const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1" + + val sink: BluetoothDevice = + BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice( + TEST_DEVICE_ADDRESS, + BluetoothDevice.ADDRESS_TYPE_RANDOM + ) + } +} diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index d929b0de391a..94aa955f0282 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -341,4 +341,7 @@ <!-- The default ringer mode. See `AudioManager` for list of valid values. --> <integer name="def_ringer_mode">2</integer> + + <!-- Caps minsum contrast from -1.0 (Material API) to 0.0 (Android Support)--> + <bool name="config_increaseMinContrast">true</bool> </resources> diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java index fc61b1e875f3..d3291b4bac17 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java @@ -118,6 +118,8 @@ public class GlobalSettings { Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED, Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, Settings.Global.Wearable.AUTO_BEDTIME_MODE, + Settings.Global.Wearable.GESTURE_PRIMARY_ACTION_USER_PREFERENCE, + Settings.Global.Wearable.GESTURE_DISMISS_ACTION_USER_PREFERENCE, Settings.Global.FORCE_ENABLE_PSS_PROFILING, Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED, Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE, diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index a2291123e192..829d4cb6c772 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -221,6 +221,8 @@ public class SecureSettings { Settings.Secure.EMERGENCY_GESTURE_ENABLED, Settings.Secure.EMERGENCY_GESTURE_SOUND_ENABLED, Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED, + Settings.Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, + Settings.Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED, Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, @@ -298,5 +300,9 @@ public class SecureSettings { Settings.Secure.DUAL_SHADE, Settings.Secure.BROWSER_CONTENT_FILTERS_ENABLED, Settings.Secure.SEARCH_CONTENT_FILTERS_ENABLED, + Settings.Secure.SPELL_CHECKER_ENABLED, + Settings.Secure.SELECTED_SPELL_CHECKER, + // SELECTED_SPELL_CHECKER_SUBTYPE needs to be restored after SELECTED_SPELL_CHECKER + Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index cf0447f9fb3a..98f5face5e96 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -124,7 +124,8 @@ public class SystemSettings { Settings.System.NOTIFICATION_COOLDOWN_ENABLED, Settings.System.NOTIFICATION_COOLDOWN_ALL, Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, - Settings.System.PREFERRED_REGION + Settings.System.PREFERRED_REGION, + Settings.System.CV_ENABLED )); if (Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) { settings.add(Settings.System.PEAK_REFRESH_RATE); diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 4c6a1ba7db0a..cd6521ff0dc5 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -474,5 +474,7 @@ public class GlobalSettingsValidators { String.valueOf( Global.Wearable.STATUS_TRAY_CONFIGURATION_SYSTEM_HIDDEN) })); + VALIDATORS.put(Global.Wearable.GESTURE_PRIMARY_ACTION_USER_PREFERENCE, BOOLEAN_VALIDATOR); + VALIDATORS.put(Global.Wearable.GESTURE_DISMISS_ACTION_USER_PREFERENCE, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index a4325344709a..d0f84627f8d8 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -358,6 +358,8 @@ public class SecureSettingsValidators { VALIDATORS.put( Secure.EMERGENCY_GESTURE_UI_LAST_STARTED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR); VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put( Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR); VALIDATORS.put(Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR); @@ -470,5 +472,8 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.DUAL_SHADE, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.BROWSER_CONTENT_FILTERS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.SEARCH_CONTENT_FILTERS_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.SPELL_CHECKER_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.SELECTED_SPELL_CHECKER, NULLABLE_COMPONENT_NAME_VALIDATOR); + VALIDATORS.put(Secure.SELECTED_SPELL_CHECKER_SUBTYPE, ANY_INTEGER_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 4f649ed49be3..3a584401ed72 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -271,5 +271,7 @@ public class SystemSettingsValidators { VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ALL, BOOLEAN_VALIDATOR); VALIDATORS.put(System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.PREFERRED_REGION, ANY_STRING_VALIDATOR); + VALIDATORS.put(System.CV_ENABLED, + new InclusiveIntegerRangeValidator(0, 1)); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/OWNERS b/packages/SettingsProvider/src/com/android/providers/settings/OWNERS index b0086c180cbd..78c87b389dfc 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/OWNERS +++ b/packages/SettingsProvider/src/com/android/providers/settings/OWNERS @@ -1,2 +1,2 @@ -per-file WritableNamespacePrefixes.java = mpgroover@google.com,tedbauer@google.com -per-file WritableNamespaces.java = mpgroover@google.com,tedbauer@google.com +per-file WritableNamespacePrefixes.java = mpgroover@google.com +per-file WritableNamespaces.java = mpgroover@google.com diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index fc402d45c3ec..37ada933259e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -37,10 +37,12 @@ import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.app.backup.BackupRestoreEventLogger; import android.app.backup.FullBackupDataOutput; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; import android.database.Cursor; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; @@ -941,6 +943,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { Set<String> blockedSettings = getBlockedSettings(blockedSettingsArrayId); int restoredSettingsCount = 0; + boolean selectedSpellCheckerRestored = false; for (String key : allowlist.mSettingsAllowlist) { boolean isBlockedBySystem = blockedSettings != null && blockedSettings.contains(key); if (isBlockedBySystem || isBlockedByDynamicList(dynamicBlockList, contentUri, key)) { @@ -1068,6 +1071,25 @@ public class SettingsBackupAgent extends BackupAgentHelper { } continue; } + } else if (Settings.Secure.SELECTED_SPELL_CHECKER.equals(key)) { + ServiceInfo si = getServiceInfoOrNull(value); + if (si == null || si.applicationInfo == null) { + Log.i(TAG, "Skipping restore for setting selected_spell_checker " + + "as it is not installed"); + continue; + } else if (!si.applicationInfo.isSystemApp() + && !si.applicationInfo.isUpdatedSystemApp()) { + Log.i(TAG, "Skipping restore for setting selected_spell_checker " + + "as it is not a system app"); + continue; + } + selectedSpellCheckerRestored = true; + } else if (Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE.equals(key)) { + if (!selectedSpellCheckerRestored) { + Log.i(TAG, "Skipping restore for setting selected_spell_checker_subtype " + + "as selected_spell_checker was not restored"); + continue; + } } if (Settings.System.FONT_SCALE.equals(key)) { @@ -1085,6 +1107,21 @@ public class SettingsBackupAgent extends BackupAgentHelper { Log.d(TAG, "Restored font scale from: " + toRestore + " to " + value); } + if (Settings.Secure.CONTRAST_LEVEL.equals(key)) { + boolean increaseMinContrast = getBaseContext().getResources() + .getBoolean(R.bool.config_increaseMinContrast); + + float valueFloat; + try { + valueFloat = Float.parseFloat(value); + } catch (NumberFormatException e) { + valueFloat = 0.0f; + } + + float newValue = Math.max(valueFloat, increaseMinContrast ? 0.0f : -1.0f); + value = String.valueOf(newValue); + } + settingsHelper.restoreValue(this, cr, contentValues, destination, key, value, mRestoredFromSdkInt); @@ -1868,6 +1905,18 @@ public class SettingsBackupAgent extends BackupAgentHelper { return result; } + @Nullable + private ServiceInfo getServiceInfoOrNull(@Nullable String flattenedServiceName) { + if (flattenedServiceName == null) return null; + ComponentName componentName = ComponentName.unflattenFromString(flattenedServiceName); + if (componentName == null) return null; + try { + return getBaseContext().getPackageManager().getServiceInfo(componentName, 0); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + /** * Store the allowlist of settings to be backed up and validators for them. */ diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 57facdaa388c..584b21adbe77 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -2111,6 +2111,12 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED, SecureSettingsProto.ADAPTIVE_CONNECTIVITY_ENABLED); + dumpSetting(s, p, + Settings.Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, + SecureSettingsProto.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED); + dumpSetting(s, p, + Settings.Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED, + SecureSettingsProto.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED); final long controlsToken = p.start(SecureSettingsProto.CONTROLS); dumpSetting(s, p, @@ -3151,6 +3157,12 @@ class SettingsProtoDumpUtil { SystemSettingsProto.Volume.MASTER_BALANCE); p.end(volumeToken); + final long systemDisplayToken = p.start(SystemSettingsProto.DISPLAY); + dumpSetting(s, p, + Settings.System.CV_ENABLED, + SystemSettingsProto.Display.CV_ENABLED); + p.end(systemDisplayToken); + dumpSetting(s, p, Settings.System.WHEN_TO_MAKE_WIFI_CALLS, SystemSettingsProto.WHEN_TO_MAKE_WIFI_CALLS); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 70c042cb8eba..3148f22ab511 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -749,15 +749,12 @@ public class SettingsBackupTest { Settings.Secure.SECURE_FRP_MODE, Settings.Secure.SEARCH_WEB_RESULTS_OVERRIDE_LIMIT, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, - Settings.Secure.SELECTED_SPELL_CHECKER, // Intentionally removed in Q - Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, // Intentionally removed in Q Settings.Secure.SETTINGS_CLASSNAME, Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, // candidate? Settings.Secure.SHOW_ROTATION_SUGGESTIONS, Settings.Secure.SKIP_FIRST_USE_HINTS, // candidate? Settings.Secure.SLEEP_TIMEOUT, Settings.Secure.SMS_DEFAULT_APPLICATION, - Settings.Secure.SPELL_CHECKER_ENABLED, // Intentionally removed in Q Settings.Secure.TRUST_AGENTS_INITIALIZED, Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java index 48c360b635ea..bc727d33ad4a 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java @@ -16,15 +16,15 @@ package com.android.providers.settings; -import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_NEW_CONFIG; -import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG; import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SIM_SPECIFIC_SETTINGS_2; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_NEW_CONFIG; import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_SETTINGS_BACKUP_DATA; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertArrayEquals; @@ -35,12 +35,10 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; -import android.annotation.Nullable; import android.app.backup.BackupAnnotations.BackupDestination; import android.app.backup.BackupAnnotations.OperationType; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; -import android.app.backup.BackupRestoreEventLogger; import android.app.backup.BackupRestoreEventLogger.DataTypeResult; import android.content.ContentResolver; import android.content.ContentValues; @@ -69,13 +67,11 @@ import androidx.test.runner.AndroidJUnit4; import com.android.window.flags.Flags; -import java.util.List; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -331,6 +327,47 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { } @Test + public void testOnRestore_minContrastLevelIsRestoredToZero() { + mAgentUnderTest = new TestFriendlySettingsBackupAgent() { + @Override + protected Set<String> getBlockedSettings(int blockedSettingsArrayId) { + return new HashSet<>(); + } + }; + mAgentUnderTest.attach(mContext); + + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + + String contrastLevelValue = "-1.0"; + Map<String, String> settingsToRestore = Map.of(Settings.Secure.CONTRAST_LEVEL, + contrastLevelValue); + + byte[] backupData = generateBackupData(settingsToRestore); + mAgentUnderTest.restoreSettings( + backupData, + /* pos */ 0, + backupData.length, + Settings.Secure.CONTENT_URI, + null, + null, + null, + /* blockedSettingsArrayId */ 0, + Collections.emptySet(), + Collections.emptySet(), + KEY_SECURE); + + // Check that the contrast level has been restored. + assertTrue(settingsHelper.mWrittenValues.containsKey(Settings.Secure.CONTRAST_LEVEL)); + + String restoredContrastLevel = settingsHelper.mWrittenValues.get( + Settings.Secure.CONTRAST_LEVEL); + + float restoredFloat = Float.parseFloat(restoredContrastLevel); + assertEquals(0.0f, restoredFloat, 0.001f); + } + + @Test @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) public void onCreate_metricsFlagIsDisabled_areAgentMetricsEnabledIsFalse() { mAgentUnderTest.onCreate(); diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index fb0678fedb56..5bba99f84d43 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -255,11 +255,11 @@ public class BugreportProgressService extends Service { /** Always keep remote bugreport files created in the last day. */ private static final long REMOTE_MIN_KEEP_AGE = DateUtils.DAY_IN_MILLIS; - /** Minimum delay for sending last update notification */ - private static final int DELAY_NOTIFICATION_MS = 250; - private final Object mLock = new Object(); +/** Minimum delay between percentage points before sending an update notification */ + private static final int MIN_NOTIFICATION_GAP = 10; + /** Managed bugreport info (keyed by id) */ @GuardedBy("mLock") private final SparseArray<BugreportInfo> mBugreportInfos = new SparseArray<>(); @@ -1460,17 +1460,6 @@ public class BugreportProgressService extends Service { * Sends a notification indicating the bugreport has finished so use can share it. */ private void sendBugreportNotification(BugreportInfo info, boolean takingScreenshot) { - - final long lastUpdate = System.currentTimeMillis() - info.lastUpdate.longValue(); - if (lastUpdate < DELAY_NOTIFICATION_MS) { - Log.d(TAG, "Delaying final notification for " - + (DELAY_NOTIFICATION_MS - lastUpdate) + " ms "); - mMainThreadHandler.postDelayed(() -> { - sendBugreportNotification(info, takingScreenshot); - }, DELAY_NOTIFICATION_MS - lastUpdate); - return; - } - // Since adding the details can take a while, do it before notifying user. addDetailsToZipFile(info); @@ -1523,7 +1512,7 @@ public class BugreportProgressService extends Service { builder.setSubText(info.getName()); } - Log.v(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title); + Log.d(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title); NotificationManager.from(mContext).notify(info.id, builder.build()); } @@ -2753,6 +2742,11 @@ public class BugreportProgressService extends Service { if (progress > CAPPED_PROGRESS) { progress = CAPPED_PROGRESS; } + + if ((progress - info.lastProgress.intValue()) < MIN_NOTIFICATION_GAP) { + return; + } + if (DEBUG) { if (progress != info.progress.intValue()) { Log.v(TAG, "Updating progress for name " + info.getName() + "(id: " + info.id diff --git a/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java b/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java index 433eca23080e..8031b103d72c 100644 --- a/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java +++ b/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java @@ -20,7 +20,7 @@ import static com.android.shell.BugreportProgressService.findSendToAccount; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import android.accounts.Account; diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 5b48566d92f9..129949fd38b2 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -534,6 +534,7 @@ android_library { "androidx.compose.animation_animation-graphics", "androidx.lifecycle_lifecycle-viewmodel-compose", "kairos", + "displaylib", "aconfig_settings_flags_lib", ], libs: [ @@ -728,6 +729,7 @@ android_library { "Traceur-res", "aconfig_settings_flags_lib", "kairos", + "displaylib", ], } @@ -770,6 +772,7 @@ android_library { "androidx.compose.runtime_runtime", "kairos", "kosmos", + "displaylib", "testables", "androidx.test.rules", "platform-compat-test-rules", diff --git a/packages/SystemUI/TEST_OWNERS b/packages/SystemUI/TEST_OWNERS index eadc86e386cb..21faf036c8f6 100644 --- a/packages/SystemUI/TEST_OWNERS +++ b/packages/SystemUI/TEST_OWNERS @@ -3,3 +3,6 @@ # for restructuring and test maintenance only saff@google.com + +# Work around per-file set noparent includes +per-file *=saff@google.com diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java index 71726199aeb6..1543dbe7bb29 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java @@ -46,7 +46,7 @@ import android.hardware.display.DisplayManager; import android.media.AudioManager; import android.os.PowerManager; import android.os.UserManager; -import android.platform.uiautomator_helpers.WaitUtils; +import android.platform.uiautomatorhelpers.WaitUtils; import android.provider.Settings; import android.util.Log; import android.view.Display; diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 3cb30258fcb1..c6bc1c70ad18 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -155,3 +155,13 @@ 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/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index ab18612355f0..4693377654f8 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -213,18 +213,6 @@ flag { } flag { - name: "notification_undo_guts_on_config_changed" - namespace: "systemui" - description: "Fixes a bug where a theme or font change while notification guts were open" - " (e.g. the snooze options or notification info) would show an empty notification by" - " closing the guts and undoing changes." - bug: "379267630" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "pss_app_selector_recents_split_screen" namespace: "systemui" description: "Allows recent apps selected for partial screenshare to be launched in split screen mode" diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java index 694fc8eb6ad7..ca94482b9c5a 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.TRANSIT_OLD_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; @@ -261,7 +262,8 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner SurfaceControl.Transaction startTransaction ) { checkArgument(isOpeningMode(launcherChange.getMode())); - if (!isClosingType(info.getType())) { + if (!isClosingType(info.getType()) + && !ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX.isTrue()) { return; } for (int i = info.getChanges().size() - 1; i >= 0; --i) { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index 4a39cff388a9..4f01d7fcdb51 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -82,6 +82,10 @@ class TypefaceVariantCacheImpl(var baseTypeface: Typeface, override val animatio } } +interface TextAnimatorListener : TextInterpolatorListener { + fun onInvalidate() {} +} + /** * This class provides text animation between two styles. * @@ -110,13 +114,19 @@ class TypefaceVariantCacheImpl(var baseTypeface: Typeface, override val animatio class TextAnimator( layout: Layout, private val typefaceCache: TypefaceVariantCache, - private val invalidateCallback: () -> Unit = {}, + private val listener: TextAnimatorListener? = null, ) { - @VisibleForTesting var textInterpolator = TextInterpolator(layout, typefaceCache) + var textInterpolator = TextInterpolator(layout, typefaceCache, listener) @VisibleForTesting var createAnimator: () -> ValueAnimator = { ValueAnimator.ofFloat(1f) } var animator: ValueAnimator? = null + val progress: Float + get() = textInterpolator.progress + + val linearProgress: Float + get() = textInterpolator.linearProgress + val fontVariationUtils = FontVariationUtils() sealed class PositionedGlyph { @@ -288,8 +298,9 @@ class TextAnimator( animator = buildAnimator(animation).apply { start() } } else { textInterpolator.progress = 1f + textInterpolator.linearProgress = 1f textInterpolator.rebase() - invalidateCallback() + listener?.onInvalidate() } } @@ -302,7 +313,7 @@ class TextAnimator( addUpdateListener { textInterpolator.progress = it.animatedValue as Float textInterpolator.linearProgress = it.currentPlayTime / it.duration.toFloat() - invalidateCallback() + listener?.onInvalidate() } addListener( diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt index 457f45388062..22c5258edb58 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt @@ -27,8 +27,19 @@ import android.util.MathUtils import com.android.internal.graphics.ColorUtils import java.lang.Math.max +interface TextInterpolatorListener { + fun onPaintModified() {} + + fun onRebased() {} +} + /** Provide text style linear interpolation for plain text. */ -class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache) { +class TextInterpolator( + layout: Layout, + var typefaceCache: TypefaceVariantCache, + private val listener: TextInterpolatorListener? = null, +) { + /** * Returns base paint used for interpolation. * @@ -136,6 +147,7 @@ class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache) */ fun onTargetPaintModified() { updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false) + listener?.onPaintModified() } /** @@ -146,6 +158,7 @@ class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache) */ fun onBasePaintModified() { updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true) + listener?.onPaintModified() } /** @@ -204,6 +217,7 @@ class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache) */ fun rebase() { if (progress == 0f) { + listener?.onRebased() return } else if (progress == 1f) { basePaint.set(targetPaint) @@ -233,6 +247,8 @@ class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache) } progress = 0f + linearProgress = 0f + listener?.onRebased() } /** diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt index a352b1eebb81..82e5f5bb6dc8 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt @@ -21,7 +21,6 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroupOverlay import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -62,6 +61,7 @@ import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.graphics.layer.drawLayer import androidx.compose.ui.graphics.rememberGraphicsLayer @@ -82,6 +82,7 @@ import androidx.lifecycle.setViewTreeLifecycleOwner import androidx.lifecycle.setViewTreeViewModelStoreOwner import androidx.savedstate.findViewTreeSavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner +import com.android.compose.modifiers.animatedBackground import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.FullScreenComposeViewInOverlay import com.android.systemui.animation.ComposableControllerFactory @@ -291,7 +292,7 @@ fun Expandable( .updateExpandableSize() .then(minInteractiveSizeModifier) .then(clickModifier(controller, onClick, interactionSource)) - .background(color, shape) + .animatedBackground(color, shape = shape) .border(controller) .onGloballyPositioned { controller.boundsInComposeViewRoot = it.boundsInRoot() } ) { @@ -307,19 +308,27 @@ private fun WrappedContent( contentColor: Color, content: @Composable (Expandable) -> Unit, ) { - CompositionLocalProvider(LocalContentColor provides contentColor) { - // We make sure that the content itself (wrapped by the background) is at least 40.dp, which - // is the same as the M3 buttons. This applies even if onClick is null, to make it easier to - // write expandables that are sometimes clickable and sometimes not. There shouldn't be any - // Expandable smaller than 40dp because if the expandable is not clickable directly, then - // something in its content should be (and with a size >= 40dp). - val minSize = 40.dp - Box( - Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize), - contentAlignment = Alignment.Center, - ) { - content(expandable) + val minSizeContent = + @Composable { + // We make sure that the content itself (wrapped by the background) is at least 40.dp, + // which is the same as the M3 buttons. This applies even if onClick is null, to make it + // easier to write expandables that are sometimes clickable and sometimes not. There + // shouldn't be any Expandable smaller than 40dp because if the expandable is not + // clickable directly, then something in its content should be (and with a size >= + // 40dp). + val minSize = 40.dp + Box( + Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize), + contentAlignment = Alignment.Center, + ) { + content(expandable) + } } + + if (contentColor.isSpecified) { + CompositionLocalProvider(LocalContentColor provides contentColor, content = minSizeContent) + } else { + minSizeContent() } } @@ -345,7 +354,7 @@ private fun Modifier.expandable( .thenIf(drawContent) { Modifier.border(controller) .then(clickModifier(controller, onClick, interactionSource)) - .background(controller.color, controller.shape) + .animatedBackground(controller.color, shape = controller.shape) } .onPlaced { controller.boundsInComposeViewRoot = it.boundsInRoot() } .drawWithContent { @@ -422,7 +431,7 @@ private class DrawExpandableInOverlayNode( // Background. this@draw.drawBackground( state, - controller.color, + controller.color(), controller.borderStroke, size = Size(state.width.toFloat(), state.height.toFloat()), ) @@ -469,7 +478,7 @@ private fun clickModifier( /** Draw [content] in [overlay] while respecting its screen position given by [animatorState]. */ @Composable private fun AnimatedContentInOverlay( - color: Color, + color: () -> Color, sizeInOriginalLayout: Size, overlay: ViewGroupOverlay, controller: ExpandableControllerImpl, @@ -523,7 +532,7 @@ private fun AnimatedContentInOverlay( return@drawWithContent } - drawBackground(animatorState, color, controller.borderStroke) + drawBackground(animatorState, color(), controller.borderStroke) drawContent() }, // We center the content in the expanding container. diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt index 72da175e26cf..d7d5a48e2b79 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt @@ -26,7 +26,6 @@ import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Stable -import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -80,6 +79,24 @@ fun rememberExpandableController( borderStroke: BorderStroke? = null, transitionControllerFactory: ComposableControllerFactory? = null, ): ExpandableController { + return rememberExpandableController( + color = { color }, + shape = shape, + contentColor = contentColor, + borderStroke = borderStroke, + transitionControllerFactory = transitionControllerFactory, + ) +} + +/** Create an [ExpandableController] to control an [Expandable]. */ +@Composable +fun rememberExpandableController( + color: () -> Color, + shape: Shape, + contentColor: Color = Color.Unspecified, + borderStroke: BorderStroke? = null, + transitionControllerFactory: ComposableControllerFactory? = null, +): ExpandableController { val composeViewRoot = LocalView.current val density = LocalDensity.current val layoutDirection = LocalLayoutDirection.current @@ -125,7 +142,7 @@ fun rememberExpandableController( } internal class ExpandableControllerImpl( - internal val color: Color, + internal val color: () -> Color, internal val contentColor: Color, internal val shape: Shape, internal val borderStroke: BorderStroke?, diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt index 959f28f2d99e..2ea9c487c27c 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt @@ -95,13 +95,31 @@ interface NestedDraggable { * nested scrollable. * * This is called whenever a nested scrollable does not consume some scroll amount. If this - * returns `true`, then [onDragStarted] will be called and this draggable will have priority and + * returns `true`, then [onDragStarted] will be called, this draggable will have priority and * consume all future events during preScroll until the nested scroll is finished. */ - fun shouldConsumeNestedScroll(sign: Float): Boolean + fun shouldConsumeNestedPostScroll(sign: Float): Boolean = true + + /** + * Whether this draggable should consume any scroll amount with the given [sign] *before* it can + * be consumed by a nested scrollable. + * + * This is called before a nested scrollable is able to consume that scroll amount. If this + * returns `true`, then [onDragStarted] will be called, this draggable will have priority and + * consume all future scroll events during preScroll until the nested scroll is finished. + */ + fun shouldConsumeNestedPreScroll(sign: Float): Boolean = false interface Controller { /** + * Whether drags that were started from nested scrolls should be automatically + * [stopped][onDragStopped] as soon as they don't consume the entire `delta` passed to + * [onDrag]. + */ + val autoStopNestedDrags: Boolean + get() = false + + /** * Drag by [delta] pixels. * * @return the consumed [delta]. Any non-consumed delta will be dispatched to the next @@ -540,6 +558,14 @@ private class NestedDraggableNode( } override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + val sign = available.toFloat().sign + maybeCreateNewController( + sign = sign, + condition = { + source == NestedScrollSource.UserInput && + draggable.shouldConsumeNestedPreScroll(sign) + }, + ) val controller = nestedScrollController ?: return Offset.Zero return scrollWithOverscroll(controller, available) } @@ -560,33 +586,46 @@ private class NestedDraggableNode( } val sign = offset.sign + maybeCreateNewController( + sign, + condition = { draggable.shouldConsumeNestedPostScroll(sign) }, + ) + val controller = nestedScrollController ?: return Offset.Zero + return scrollWithOverscroll(controller, available) + } + + private fun maybeCreateNewController(sign: Float, condition: () -> Boolean) { if ( - nestedDragsEnabled && - nestedScrollController == null && - // TODO(b/388231324): Remove this. - !lastEventWasScrollWheel && - draggable.shouldConsumeNestedScroll(sign) && - lastFirstDown != null + !nestedDragsEnabled || + nestedScrollController != null || + lastEventWasScrollWheel || + lastFirstDown == null || + !condition() ) { - val startedPosition = checkNotNull(lastFirstDown) - - // TODO(b/382665591): Ensure that there is at least one pointer down. - val pointersDownCount = pointersDown.size.coerceAtLeast(1) - val pointerType = pointersDown.entries.firstOrNull()?.value - nestedScrollController = - NestedScrollController( - overscrollEffect, - draggable.onDragStarted(startedPosition, sign, pointersDownCount, pointerType), - ) + return } - val controller = nestedScrollController ?: return Offset.Zero - return scrollWithOverscroll(controller, available) + // TODO(b/382665591): Ensure that there is at least one pointer down. + val pointersDownCount = pointersDown.size.coerceAtLeast(1) + val pointerType = pointersDown.entries.firstOrNull()?.value + val startedPosition = checkNotNull(lastFirstDown) + nestedScrollController = + NestedScrollController( + overscrollEffect, + draggable.onDragStarted(startedPosition, sign, pointersDownCount, pointerType), + ) } private fun scrollWithOverscroll(controller: NestedScrollController, offset: Offset): Offset { - return scrollWithOverscroll(offset) { - controller.controller.onDrag(it.toFloat()).toOffset() + return scrollWithOverscroll(offset) { delta -> + val available = delta.toFloat() + val consumed = controller.controller.onDrag(available) + if (controller.controller.autoStopNestedDrags && consumed != available) { + controller.ensureOnDragStoppedIsCalled() + this.nestedScrollController = null + } + + consumed.toOffset() } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/AnimatedBackground.kt b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/AnimatedBackground.kt new file mode 100644 index 000000000000..5b1f0a7c6eb6 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/AnimatedBackground.kt @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.modifiers + +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.ObserverModifierNode +import androidx.compose.ui.node.invalidateDraw +import androidx.compose.ui.node.observeReads +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.platform.debugInspectorInfo +import androidx.compose.ui.unit.LayoutDirection + +/** + * Draws a background in a given [shape] and with a [color] or [alpha] that can be animated. + * + * @param color color to paint background with + * @param alpha alpha of the background + * @param shape desired shape of the background + */ +fun Modifier.animatedBackground( + color: () -> Color, + alpha: () -> Float = DefaultAlpha, + shape: Shape = RectangleShape, +) = + this.then( + BackgroundElement( + color = color, + alpha = alpha, + shape = shape, + inspectorInfo = + debugInspectorInfo { + name = "background" + value = color + properties["color"] = color + properties["alpha"] = alpha + properties["shape"] = shape + }, + ) + ) + +private val DefaultAlpha = { 1f } + +private class BackgroundElement( + private val color: () -> Color, + private val alpha: () -> Float, + private val shape: Shape, + private val inspectorInfo: InspectorInfo.() -> Unit, +) : ModifierNodeElement<BackgroundNode>() { + override fun create(): BackgroundNode { + return BackgroundNode(color, alpha, shape) + } + + override fun update(node: BackgroundNode) { + node.color = color + node.alpha = alpha + node.shape = shape + } + + override fun InspectorInfo.inspectableProperties() { + inspectorInfo() + } + + override fun hashCode(): Int { + var result = color.hashCode() + result = 31 * result + alpha.hashCode() + result = 31 * result + shape.hashCode() + return result + } + + override fun equals(other: Any?): Boolean { + val otherModifier = other as? BackgroundElement ?: return false + return color == otherModifier.color && + alpha == otherModifier.alpha && + shape == otherModifier.shape + } +} + +private class BackgroundNode(var color: () -> Color, var alpha: () -> Float, var shape: Shape) : + DrawModifierNode, Modifier.Node(), ObserverModifierNode { + + // Naively cache outline calculation if input parameters are the same, we manually observe + // reads inside shape#createOutline separately + private var lastSize: Size = Size.Unspecified + private var lastLayoutDirection: LayoutDirection? = null + private var lastOutline: Outline? = null + private var lastShape: Shape? = null + private var tmpOutline: Outline? = null + + override fun ContentDrawScope.draw() { + if (shape === RectangleShape) { + // shortcut to avoid Outline calculation and allocation + drawRect() + } else { + drawOutline() + } + drawContent() + } + + override fun onObservedReadsChanged() { + // Reset cached properties + lastSize = Size.Unspecified + lastLayoutDirection = null + lastOutline = null + lastShape = null + // Invalidate draw so we build the cache again - this is needed because observeReads within + // the draw scope obscures the state reads from the draw scope's observer + invalidateDraw() + } + + private fun ContentDrawScope.drawRect() { + drawRect(color = color(), alpha = alpha()) + } + + private fun ContentDrawScope.drawOutline() { + val outline = getOutline() + drawOutline(outline, color = color(), alpha = alpha()) + } + + private fun ContentDrawScope.getOutline(): Outline { + val outline: Outline? + if (size == lastSize && layoutDirection == lastLayoutDirection && lastShape == shape) { + outline = lastOutline!! + } else { + // Manually observe reads so we can directly invalidate the outline when it changes + // Use tmpOutline to avoid creating an object reference to local var outline + observeReads { tmpOutline = shape.createOutline(size, layoutDirection, this) } + outline = tmpOutline + tmpOutline = null + } + lastOutline = outline + lastSize = size + lastLayoutDirection = layoutDirection + lastShape = shape + return outline!! + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/FadingBackground.kt b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/FadingBackground.kt deleted file mode 100644 index 794b7a4a3d30..000000000000 --- a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/FadingBackground.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.compose.modifiers - -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.DrawModifier -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Outline -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.drawOutline -import androidx.compose.ui.graphics.drawscope.ContentDrawScope -import androidx.compose.ui.platform.InspectorInfo -import androidx.compose.ui.platform.InspectorValueInfo -import androidx.compose.ui.platform.debugInspectorInfo -import androidx.compose.ui.unit.LayoutDirection - -/** - * Draws a fading [shape] with a solid [color] and [alpha] behind the content. - * - * @param color color to paint background with - * @param alpha alpha of the background - * @param shape desired shape of the background - */ -fun Modifier.fadingBackground(color: Color, alpha: () -> Float, shape: Shape = RectangleShape) = - this.then( - FadingBackground( - brush = SolidColor(color), - alpha = alpha, - shape = shape, - inspectorInfo = - debugInspectorInfo { - name = "background" - value = color - properties["color"] = color - properties["alpha"] = alpha - properties["shape"] = shape - }, - ) - ) - -private class FadingBackground -constructor( - private val brush: Brush, - private val shape: Shape, - private val alpha: () -> Float, - inspectorInfo: InspectorInfo.() -> Unit, -) : DrawModifier, InspectorValueInfo(inspectorInfo) { - // naive cache outline calculation if size is the same - private var lastSize: Size? = null - private var lastLayoutDirection: LayoutDirection? = null - private var lastOutline: Outline? = null - - override fun ContentDrawScope.draw() { - if (shape === RectangleShape) { - // shortcut to avoid Outline calculation and allocation - drawRect() - } else { - drawOutline() - } - drawContent() - } - - private fun ContentDrawScope.drawRect() { - drawRect(brush, alpha = alpha()) - } - - private fun ContentDrawScope.drawOutline() { - val outline = - if (size == lastSize && layoutDirection == lastLayoutDirection) { - lastOutline!! - } else { - shape.createOutline(size, layoutDirection, this) - } - drawOutline(outline, brush = brush, alpha = alpha()) - lastOutline = outline - lastSize = size - lastLayoutDirection = layoutDirection - } - - override fun hashCode(): Int { - var result = brush.hashCode() - result = 31 * result + alpha.hashCode() - result = 31 * result + shape.hashCode() - return result - } - - override fun equals(other: Any?): Boolean { - val otherModifier = other as? FadingBackground ?: return false - return brush == otherModifier.brush && - alpha == otherModifier.alpha && - shape == otherModifier.shape - } - - override fun toString(): String = "FadingBackground(brush=$brush, alpha = $alpha, shape=$shape)" -} diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt index d8e46ad34c37..b247993de4e4 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt @@ -971,6 +971,68 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw assertThat(availableToEffectPostFling).isWithin(1f).of(100f) } + @Test + fun consumeNestedPreScroll() { + var consumeNestedPreScroll by mutableStateOf(false) + val draggable = TestDraggable(shouldConsumeNestedPreScroll = { consumeNestedPreScroll }) + + val touchSlop = + rule.setContentWithTouchSlop { + Box( + Modifier.fillMaxSize() + .nestedDraggable(draggable, orientation) + // Always consume everything so that the only way to start the drag is to + // intercept preScroll events. + .scrollable(rememberScrollableState { it }, orientation) + ) + } + + rule.onRoot().performTouchInput { + down(center) + moveBy((touchSlop + 1f).toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isFalse() + + consumeNestedPreScroll = true + rule.onRoot().performTouchInput { moveBy(1f.toOffset()) } + + assertThat(draggable.onDragStartedCalled).isTrue() + } + + @Test + fun autoStopNestedDrags() { + var consumeScrolls by mutableStateOf(true) + val draggable = + TestDraggable(autoStopNestedDrags = true, onDrag = { if (consumeScrolls) it else 0f }) + + val touchSlop = + rule.setContentWithTouchSlop { + Box( + Modifier.fillMaxSize() + .nestedDraggable(draggable, orientation) + .scrollable(rememberScrollableState { 0f }, orientation) + ) + } + + rule.onRoot().performTouchInput { + down(center) + moveBy((touchSlop + 1f).toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragStoppedCalled).isFalse() + + rule.onRoot().performTouchInput { moveBy(50f.toOffset()) } + + assertThat(draggable.onDragStoppedCalled).isFalse() + + consumeScrolls = false + rule.onRoot().performTouchInput { moveBy(1f.toOffset()) } + + assertThat(draggable.onDragStoppedCalled).isTrue() + } + private fun ComposeContentTestRule.setContentWithTouchSlop( content: @Composable () -> Unit ): Float { @@ -996,7 +1058,9 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw { velocity, _ -> velocity }, - private val shouldConsumeNestedScroll: (Float) -> Boolean = { true }, + private val shouldConsumeNestedPostScroll: (Float) -> Boolean = { true }, + private val shouldConsumeNestedPreScroll: (Float) -> Boolean = { false }, + private val autoStopNestedDrags: Boolean = false, ) : NestedDraggable { var shouldStartDrag = true var onDragStartedCalled = false @@ -1026,6 +1090,8 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw onDragStarted.invoke(position, sign) return object : NestedDraggable.Controller { + override val autoStopNestedDrags: Boolean = this@TestDraggable.autoStopNestedDrags + override fun onDrag(delta: Float): Float { onDragCalled = true onDragDelta += delta @@ -1042,8 +1108,12 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw } } - override fun shouldConsumeNestedScroll(sign: Float): Boolean { - return shouldConsumeNestedScroll.invoke(sign) + override fun shouldConsumeNestedPostScroll(sign: Float): Boolean { + return shouldConsumeNestedPostScroll.invoke(sign) + } + + override fun shouldConsumeNestedPreScroll(sign: Float): Boolean { + return shouldConsumeNestedPreScroll.invoke(sign) } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 216f0a74e1c7..7782705d4c61 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -73,7 +73,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.compose.animation.Expandable import com.android.compose.animation.scene.ContentScope -import com.android.compose.modifiers.fadingBackground +import com.android.compose.modifiers.animatedBackground import com.android.compose.theme.colorAttr import com.android.systemui.Flags.notificationShadeBlur import com.android.systemui.animation.Expandable @@ -172,8 +172,8 @@ fun FooterActions( val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius) val backgroundModifier = remember(backgroundColor, backgroundAlphaValue, backgroundTopRadius) { - Modifier.fadingBackground( - backgroundColor, + Modifier.animatedBackground( + { backgroundColor }, backgroundAlphaValue, RoundedCornerShape(topStart = backgroundTopRadius, topEnd = backgroundTopRadius), ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 7015f79e4a9f..0daef465bf1f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -101,7 +101,7 @@ fun SceneContainer( rememberActivated(traceName = "sceneJankMonitor") { sceneJankMonitorFactory.create() } val hapticFeedback = LocalHapticFeedback.current - val shadeExpansionMotion = OverlayShade.rememberShadeExpansionMotion() + val shadeExpansionMotion = OverlayShade.rememberShadeExpansionMotion(isFullWidthShade()) val sceneTransitions = remember(hapticFeedback, shadeExpansionMotion) { transitionsBuilder.build( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt index 9b45ef693ce6..2f5a0306c84f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt @@ -6,7 +6,7 @@ import com.android.compose.animation.scene.TransitionKey import com.android.compose.animation.scene.reveal.ContainerRevealHaptics import com.android.compose.animation.scene.transitions import com.android.internal.jank.Cuj -import com.android.mechanics.behavior.EdgeContainerExpansionSpec +import com.android.mechanics.behavior.VerticalExpandContainerSpec import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes @@ -50,7 +50,7 @@ import com.android.systemui.shade.ui.composable.Shade */ class SceneContainerTransitions : SceneContainerTransitionsBuilder { override fun build( - shadeExpansionMotion: EdgeContainerExpansionSpec, + shadeExpansionMotion: VerticalExpandContainerSpec, revealHaptics: ContainerRevealHaptics, ): SceneTransitions { return transitions { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitionsBuilder.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitionsBuilder.kt index eb5548d45247..4c9c23ad9f9f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitionsBuilder.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitionsBuilder.kt @@ -19,7 +19,7 @@ package com.android.systemui.scene.ui.composable import com.android.compose.animation.scene.SceneTransitions import com.android.compose.animation.scene.reveal.ContainerRevealHaptics import com.android.compose.animation.scene.transitions -import com.android.mechanics.behavior.EdgeContainerExpansionSpec +import com.android.mechanics.behavior.VerticalExpandContainerSpec /** * Builder of the comprehensive definition of all transitions between scenes and overlays in the @@ -29,7 +29,7 @@ interface SceneContainerTransitionsBuilder { /** Build the [SceneContainer] transitions spec. */ fun build( - shadeExpansionMotion: EdgeContainerExpansionSpec, + shadeExpansionMotion: VerticalExpandContainerSpec, revealHaptics: ContainerRevealHaptics, ): SceneTransitions } @@ -42,7 +42,7 @@ class ConstantSceneContainerTransitionsBuilder( private val transitions: SceneTransitions = transitions { /* No transitions */ } ) : SceneContainerTransitionsBuilder { override fun build( - shadeExpansionMotion: EdgeContainerExpansionSpec, + shadeExpansionMotion: VerticalExpandContainerSpec, revealHaptics: ContainerRevealHaptics, ): SceneTransitions = transitions } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt index 9b4b91eb23c1..85aad9b087f1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt @@ -20,7 +20,7 @@ import androidx.compose.animation.core.tween import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.reveal.ContainerRevealHaptics import com.android.compose.animation.scene.reveal.verticalContainerReveal -import com.android.mechanics.behavior.EdgeContainerExpansionSpec +import com.android.mechanics.behavior.VerticalExpandContainerSpec import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys import com.android.systemui.notifications.ui.composable.NotificationsShade import com.android.systemui.scene.shared.model.Overlays @@ -29,7 +29,7 @@ import kotlin.time.Duration.Companion.milliseconds fun TransitionBuilder.toNotificationsShadeTransition( durationScale: Double = 1.0, - shadeExpansionMotion: EdgeContainerExpansionSpec, + shadeExpansionMotion: VerticalExpandContainerSpec, revealHaptics: ContainerRevealHaptics, ) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt index 47dd85f17cee..8f0447d05036 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt @@ -20,14 +20,14 @@ import androidx.compose.animation.core.tween import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.reveal.ContainerRevealHaptics import com.android.compose.animation.scene.reveal.verticalContainerReveal -import com.android.mechanics.behavior.EdgeContainerExpansionSpec +import com.android.mechanics.behavior.VerticalExpandContainerSpec import com.android.systemui.qs.ui.composable.QuickSettingsShade import com.android.systemui.shade.ui.composable.OverlayShade import kotlin.time.Duration.Companion.milliseconds fun TransitionBuilder.toQuickSettingsShadeTransition( durationScale: Double = 1.0, - shadeExpansionMotion: EdgeContainerExpansionSpec, + shadeExpansionMotion: VerticalExpandContainerSpec, revealHaptics: ContainerRevealHaptics, ) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index 3446081fb123..068218a0053a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -53,8 +53,8 @@ import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.windowsizeclass.LocalWindowSizeClass -import com.android.mechanics.behavior.EdgeContainerExpansionSpec -import com.android.mechanics.behavior.edgeContainerExpansionBackground +import com.android.mechanics.behavior.VerticalExpandContainerSpec +import com.android.mechanics.behavior.verticalExpandContainerBackground import com.android.systemui.res.R import com.android.systemui.shade.ui.composable.OverlayShade.rememberShadeExpansionMotion @@ -114,9 +114,9 @@ private fun ContentScope.Panel( modifier = modifier .disableSwipesWhenScrolling() - .edgeContainerExpansionBackground( - OverlayShade.Colors.PanelBackground, - rememberShadeExpansionMotion(), + .verticalExpandContainerBackground( + backgroundColor = OverlayShade.Colors.PanelBackground, + spec = rememberShadeExpansionMotion(isFullWidthShade()), ) ) { Column { @@ -202,8 +202,10 @@ object OverlayShade { } @Composable - fun rememberShadeExpansionMotion(): EdgeContainerExpansionSpec { + fun rememberShadeExpansionMotion(isFullWidth: Boolean): VerticalExpandContainerSpec { val radius = Dimensions.PanelCornerRadius - return remember(radius) { EdgeContainerExpansionSpec(radius = radius) } + return remember(radius, isFullWidth) { + VerticalExpandContainerSpec(isFloating = !isFullWidth, radius = radius) + } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 1360611ed814..024ca22069ae 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -68,7 +68,7 @@ internal class DraggableHandler( return layoutImpl.swipeDetector.detectSwipe(change) } - override fun shouldConsumeNestedScroll(sign: Float): Boolean { + override fun shouldConsumeNestedPostScroll(sign: Float): Boolean { return this.enabled() } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt index 72f9bd5da742..734de34dcd2f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt @@ -29,7 +29,7 @@ import com.android.compose.animation.scene.transformation.CustomPropertyTransfor import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.PropertyTransformationScope import com.android.mechanics.MotionValue -import com.android.mechanics.behavior.EdgeContainerExpansionSpec +import com.android.mechanics.behavior.VerticalExpandContainerSpec import kotlinx.coroutines.CoroutineScope interface ContainerRevealHaptics { @@ -53,7 +53,7 @@ interface ContainerRevealHaptics { @OptIn(ExperimentalMaterial3ExpressiveApi::class) fun TransitionBuilder.verticalContainerReveal( container: ElementKey, - motionSpec: EdgeContainerExpansionSpec, + motionSpec: VerticalExpandContainerSpec, haptics: ContainerRevealHaptics, ) { // Make the swipe distance be exactly the target height of the container. diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp index 2ab27af37700..d63450b27390 100644 --- a/packages/SystemUI/compose/scene/tests/Android.bp +++ b/packages/SystemUI/compose/scene/tests/Android.bp @@ -42,6 +42,7 @@ android_test { "PlatformMotionTestingCompose", "androidx.test.runner", "androidx.test.ext.junit", + "platform-parametric-runner-lib", "androidx.compose.runtime_runtime", "androidx.compose.ui_ui-test-junit4", diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json new file mode 100644 index 000000000000..5dbb01338090 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json @@ -0,0 +1,624 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432, + 448, + 464, + 480, + 496, + 512, + 528, + 544, + 560, + 576, + 592, + 608, + 624, + 640, + 656, + 672, + 688, + 704, + 720, + 736, + 752, + 768, + 784, + 800, + 816, + 832, + 848, + 864, + 880, + 896, + 912, + 928, + 944 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.6 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 8.4, + "y": 5.2 + }, + { + "x": 11.2, + "y": 5.2 + }, + { + "x": 13.6, + "y": 5.2 + }, + { + "x": 15.6, + "y": 5.2 + }, + { + "x": 16.8, + "y": 5.2 + }, + { + "x": 17.6, + "y": 5.2 + }, + { + "x": 18.4, + "y": 5.2 + }, + { + "x": 18.8, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 293.2 + }, + { + "width": 150, + "height": 293.2 + }, + { + "width": 150, + "height": 286 + }, + { + "width": 150, + "height": 279.6 + }, + { + "width": 150, + "height": 273.2 + }, + { + "width": 150, + "height": 266.8 + }, + { + "width": 150, + "height": 260.4 + }, + { + "width": 150, + "height": 254 + }, + { + "width": 150, + "height": 247.6 + }, + { + "width": 150, + "height": 241.2 + }, + { + "width": 150, + "height": 241.2 + }, + { + "width": 150, + "height": 234.4 + }, + { + "width": 150, + "height": 228 + }, + { + "width": 150, + "height": 221.6 + }, + { + "width": 150, + "height": 215.2 + }, + { + "width": 150, + "height": 208.8 + }, + { + "width": 150, + "height": 202 + }, + { + "width": 150, + "height": 195.6 + }, + { + "width": 150, + "height": 189.2 + }, + { + "width": 150, + "height": 189.2 + }, + { + "width": 150, + "height": 182.8 + }, + { + "width": 150, + "height": 176.4 + }, + { + "width": 150, + "height": 170 + }, + { + "width": 150, + "height": 163.6 + }, + { + "width": 150, + "height": 157.2 + }, + { + "width": 150, + "height": 150.8 + }, + { + "width": 150, + "height": 144.4 + }, + { + "width": 150, + "height": 137.6 + }, + { + "width": 150, + "height": 137.6 + }, + { + "width": 150, + "height": 131.2 + }, + { + "width": 150, + "height": 124.8 + }, + { + "width": 150, + "height": 118.4 + }, + { + "width": 150, + "height": 112 + }, + { + "width": 150, + "height": 112 + }, + { + "width": 150, + "height": 99.2 + }, + { + "width": 150, + "height": 81.2 + }, + { + "width": 144, + "height": 62.8 + }, + { + "width": 138, + "height": 46.4 + }, + { + "width": 133.2, + "height": 32 + }, + { + "width": 129.6, + "height": 20.4 + }, + { + "width": 127.2, + "height": 12 + }, + { + "width": 125.2, + "height": 6.4 + }, + { + "width": 124, + "height": 2.8 + }, + { + "width": 123.2, + "height": 0.4 + }, + { + "width": 122.4, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0.99781144, + 0.87040234, + 0.6695792, + 0.48078007, + 0.33033127, + 0.22004372, + 0.1432175, + 0.09153092, + 0.057634592, + 0.035840213, + 0.022048414, + 0.013435662, + 0.008117795, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json new file mode 100644 index 000000000000..1543d186ea03 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json @@ -0,0 +1,644 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432, + 448, + 464, + 480, + 496, + 512, + 528, + 544, + 560, + 576, + 592, + 608, + 624, + 640, + 656, + 672, + 688, + 704, + 720, + 736, + 752, + 768, + 784, + 800, + 816, + 832, + 848, + 864, + 880, + 896, + 912, + 928, + 944, + 960, + 976 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.6, + "y": 6.8 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 6.8, + "y": 5.2 + }, + { + "x": 10.4, + "y": 5.2 + }, + { + "x": 13.2, + "y": 5.2 + }, + { + "x": 15.2, + "y": 5.2 + }, + { + "x": 16.8, + "y": 5.2 + }, + { + "x": 17.6, + "y": 5.2 + }, + { + "x": 18.4, + "y": 5.2 + }, + { + "x": 18.8, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 297.6 + }, + { + "width": 150, + "height": 294 + }, + { + "width": 150, + "height": 287.6 + }, + { + "width": 150, + "height": 282.8 + }, + { + "width": 150, + "height": 278 + }, + { + "width": 150, + "height": 273.2 + }, + { + "width": 150, + "height": 268.4 + }, + { + "width": 150, + "height": 263.6 + }, + { + "width": 150, + "height": 258.8 + }, + { + "width": 150, + "height": 258.8 + }, + { + "width": 150, + "height": 253.6 + }, + { + "width": 150, + "height": 248.8 + }, + { + "width": 150, + "height": 244 + }, + { + "width": 150, + "height": 239.2 + }, + { + "width": 150, + "height": 234.4 + }, + { + "width": 150, + "height": 229.6 + }, + { + "width": 150, + "height": 224.8 + }, + { + "width": 150, + "height": 220 + }, + { + "width": 150, + "height": 220 + }, + { + "width": 150, + "height": 214.8 + }, + { + "width": 150, + "height": 210 + }, + { + "width": 150, + "height": 205.2 + }, + { + "width": 150, + "height": 200.4 + }, + { + "width": 150, + "height": 195.6 + }, + { + "width": 150, + "height": 190.8 + }, + { + "width": 150, + "height": 186 + }, + { + "width": 150, + "height": 181.2 + }, + { + "width": 150, + "height": 181.2 + }, + { + "width": 150, + "height": 176.4 + }, + { + "width": 150, + "height": 171.6 + }, + { + "width": 150, + "height": 166.8 + }, + { + "width": 150, + "height": 161.6 + }, + { + "width": 150, + "height": 161.6 + }, + { + "width": 150, + "height": 147.2 + }, + { + "width": 150, + "height": 122 + }, + { + "width": 150, + "height": 95.2 + }, + { + "width": 146.8, + "height": 70.8 + }, + { + "width": 139.6, + "height": 50.8 + }, + { + "width": 134, + "height": 34 + }, + { + "width": 130, + "height": 19.2 + }, + { + "width": 127.2, + "height": 9.2 + }, + { + "width": 125.2, + "height": 2.8 + }, + { + "width": 123.6, + "height": 0 + }, + { + "width": 122.8, + "height": 0 + }, + { + "width": 122.4, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0.99979615, + 0.8860379, + 0.6869267, + 0.4955439, + 0.34154767, + 0.22803628, + 0.14868057, + 0.09515619, + 0.059987247, + 0.037340224, + 0.02299112, + 0.01402092, + 0.008477271, + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragOpen.json index b6e423afc6c4..13f75d2adfb4 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragOpen.json @@ -68,200 +68,200 @@ "type": "not_found" }, { - "x": 62.8, - "y": 50 + "x": 18, + "y": 5.2 }, { - "x": 62.8, - "y": 50 + "x": 18, + "y": 5.2 }, { - "x": 61.6, - "y": 50 + "x": 16.8, + "y": 5.2 }, { - "x": 60.8, - "y": 50 + "x": 16, + "y": 5.2 }, { - "x": 59.6, - "y": 50 + "x": 14.8, + "y": 5.2 }, { - "x": 58.4, - "y": 50 + "x": 13.6, + "y": 5.2 }, { - "x": 57.2, - "y": 50 + "x": 12.4, + "y": 5.2 }, { - "x": 56, - "y": 50 + "x": 11.2, + "y": 5.2 }, { - "x": 55.2, - "y": 50 + "x": 10.4, + "y": 5.2 }, { - "x": 54, - "y": 50 + "x": 9.2, + "y": 5.2 }, { - "x": 54, - "y": 50 + "x": 9.2, + "y": 5.2 }, { - "x": 52.8, - "y": 50 + "x": 8, + "y": 5.2 }, { - "x": 51.6, - "y": 50 + "x": 6.8, + "y": 5.2 }, { - "x": 50.4, - "y": 50 + "x": 5.6, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 } ] }, @@ -279,200 +279,200 @@ "type": "not_found" }, { - "width": 162.4, + "width": 124.4, "height": 1.6 }, { - "width": 162.4, + "width": 124.4, "height": 1.6 }, { - "width": 164.8, + "width": 126.8, "height": 3.2 }, { - "width": 166.8, + "width": 128.8, "height": 4.8 }, { - "width": 169.2, + "width": 131.2, "height": 6.4 }, { - "width": 171.6, + "width": 133.6, "height": 8 }, { - "width": 173.6, + "width": 135.6, "height": 9.6 }, { - "width": 176, + "width": 138, "height": 11.2 }, { - "width": 178, + "width": 140, "height": 12.8 }, { - "width": 180.4, + "width": 142.4, "height": 14.4 }, { - "width": 180.4, + "width": 142.4, "height": 14.4 }, { - "width": 182.8, + "width": 144.8, "height": 16.4 }, { - "width": 185.2, + "width": 147.2, "height": 18 }, { - "width": 187.2, + "width": 149.2, "height": 19.6 }, { - "width": 188, + "width": 150, "height": 25.6 }, { - "width": 188, + "width": 150, "height": 36.4 }, { - "width": 188, + "width": 150, "height": 45.6 }, { - "width": 188, + "width": 150, "height": 59.2 }, { - "width": 188, + "width": 150, "height": 72.8 }, { - "width": 188, + "width": 150, "height": 79.6 }, { - "width": 188, + "width": 150, "height": 92.8 }, { - "width": 188, + "width": 150, "height": 104.4 }, { - "width": 188, + "width": 150, "height": 115.2 }, { - "width": 188, + "width": 150, "height": 125.2 }, { - "width": 188, + "width": 150, "height": 134.8 }, { - "width": 188, + "width": 150, "height": 143.2 }, { - "width": 188, + "width": 150, "height": 151.2 }, { - "width": 188, + "width": 150, "height": 158.8 }, { - "width": 188, + "width": 150, "height": 160 }, { - "width": 188, + "width": 150, "height": 167.2 }, { - "width": 188, + "width": 150, "height": 174.4 }, { - "width": 188, + "width": 150, "height": 180.8 }, { - "width": 188, + "width": 150, "height": 187.6 }, { - "width": 188, + "width": 150, "height": 188 }, { - "width": 188, - "height": 207.2 + "width": 150, + "height": 200.4 }, { - "width": 188, - "height": 240 + "width": 150, + "height": 218.4 }, { - "width": 188, - "height": 275.2 + "width": 150, + "height": 236.8 }, { - "width": 188, - "height": 306.8 + "width": 150, + "height": 253.2 }, { - "width": 188, - "height": 333.2 + "width": 150, + "height": 266.8 }, { - "width": 188, - "height": 353.6 + "width": 150, + "height": 277.2 }, { - "width": 188, - "height": 368.8 + "width": 150, + "height": 284.8 }, { - "width": 188, - "height": 380 + "width": 150, + "height": 290 }, { - "width": 188, - "height": 387.6 + "width": 150, + "height": 294 }, { - "width": 188, - "height": 392.4 + "width": 150, + "height": 296.4 }, { - "width": 188, - "height": 395.6 + "width": 150, + "height": 298 }, { - "width": 188, - "height": 398 + "width": 150, + "height": 298.8 }, { - "width": 188, - "height": 398.8 + "width": 150, + "height": 299.6 }, { - "width": 188, - "height": 399.6 + "width": 150, + "height": 299.6 }, { - "width": 188, - "height": 400 + "width": 150, + "height": 300 } ] }, @@ -494,12 +494,12 @@ 0, 0, 0.0067873597, - 0.0612576, - 0.19080025, + 0.06125766, + 0.19080031, 0.39327443, 0.5711931, - 0.70855826, - 0.8074064, + 0.7085583, + 0.8074065, 0.8754226, 0.9207788, 0.95032376, diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json new file mode 100644 index 000000000000..115483cf4013 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json @@ -0,0 +1,434 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432, + 448, + 464, + 480, + 496, + 512, + 528, + 544, + 560, + 576, + 592, + 608, + 624, + 640 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.6, + "y": 6.4 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 6.4, + "y": 5.2 + }, + { + "x": 10.4, + "y": 5.2 + }, + { + "x": 13.6, + "y": 5.2 + }, + { + "x": 15.6, + "y": 5.2 + }, + { + "x": 17.2, + "y": 5.2 + }, + { + "x": 18, + "y": 5.2 + }, + { + "x": 18.8, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 290 + }, + { + "width": 150, + "height": 278.8 + }, + { + "width": 150, + "height": 266 + }, + { + "width": 150, + "height": 252 + }, + { + "width": 150, + "height": 252 + }, + { + "width": 150, + "height": 223.6 + }, + { + "width": 150, + "height": 182.8 + }, + { + "width": 150, + "height": 141.2 + }, + { + "width": 150, + "height": 104 + }, + { + "width": 147.6, + "height": 74 + }, + { + "width": 139.6, + "height": 50.8 + }, + { + "width": 133.6, + "height": 32 + }, + { + "width": 129.2, + "height": 15.6 + }, + { + "width": 126.4, + "height": 5.2 + }, + { + "width": 124.4, + "height": 0 + }, + { + "width": 123.2, + "height": 0 + }, + { + "width": 122.4, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0.99479187, + 0.8575029, + 0.65572864, + 0.4691311, + 0.3215357, + 0.21380007, + 0.13896108, + 0.0887118, + 0.05580789, + 0.03467691, + 0.021318138, + 0.0129826665, + 0.007839739, + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingOpen.json index 6dc5a0e79e81..f202fcd5f59c 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingOpen.json @@ -64,104 +64,104 @@ "type": "not_found" }, { - "x": 62.4, - "y": 50 + "x": 17.6, + "y": 5.2 }, { - "x": 61.2, - "y": 50 + "x": 16.4, + "y": 5.2 }, { - "x": 59.2, - "y": 50 + "x": 14.4, + "y": 5.2 }, { - "x": 57.2, - "y": 50 + "x": 12.4, + "y": 5.2 }, { - "x": 54.8, - "y": 50 + "x": 10, + "y": 5.2 }, { - "x": 52.4, - "y": 50 + "x": 7.6, + "y": 5.2 }, { - "x": 52.4, - "y": 50 + "x": 7.6, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 } ] }, @@ -194,104 +194,104 @@ "type": "not_found" }, { - "width": 163.2, + "width": 125.2, "height": 2 }, { - "width": 166, + "width": 128, "height": 4.4 }, { - "width": 170, + "width": 132, "height": 6.8 }, { - "width": 174, + "width": 136, "height": 10 }, { - "width": 178.4, + "width": 140.4, "height": 13.2 }, { - "width": 183.6, + "width": 145.6, "height": 16.8 }, { - "width": 183.6, + "width": 145.6, "height": 16.8 }, { - "width": 188, - "height": 44.4 + "width": 150, + "height": 36.8 }, { - "width": 188, - "height": 103.6 + "width": 150, + "height": 81.2 }, { - "width": 188, - "height": 166 + "width": 150, + "height": 126.8 }, { - "width": 188, - "height": 222.4 + "width": 150, + "height": 168 }, { - "width": 188, - "height": 270 + "width": 150, + "height": 202.8 }, { - "width": 188, - "height": 307.2 + "width": 150, + "height": 230 }, { - "width": 188, - "height": 335.6 + "width": 150, + "height": 250.8 }, { - "width": 188, - "height": 356.4 + "width": 150, + "height": 266.4 }, { - "width": 188, - "height": 371.2 + "width": 150, + "height": 277.6 }, { - "width": 188, - "height": 381.6 + "width": 150, + "height": 285.2 }, { - "width": 188, - "height": 388.8 + "width": 150, + "height": 290.4 }, { - "width": 188, - "height": 393.2 + "width": 150, + "height": 294 }, { - "width": 188, - "height": 396 + "width": 150, + "height": 296.4 }, { - "width": 188, - "height": 398 + "width": 150, + "height": 298 }, { - "width": 188, - "height": 398.8 + "width": 150, + "height": 298.8 }, { - "width": 188, - "height": 399.2 + "width": 150, + "height": 299.2 }, { - "width": 188, - "height": 399.6 + "width": 150, + "height": 299.6 }, { - "width": 188, - "height": 399.6 + "width": 150, + "height": 299.6 } ] }, diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_magneticDetachAndReattach.json index 1cd971aa2898..4c57bda8fd5a 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_magneticDetachAndReattach.json @@ -89,268 +89,268 @@ "type": "not_found" }, { - "x": 62.8, - "y": 50 + "x": 18, + "y": 5.2 }, { - "x": 62, - "y": 50 + "x": 17.2, + "y": 5.2 }, { - "x": 61.2, - "y": 50 + "x": 16.4, + "y": 5.2 }, { - "x": 60.4, - "y": 50 + "x": 15.6, + "y": 5.2 }, { - "x": 59.6, - "y": 50 + "x": 14.8, + "y": 5.2 }, { - "x": 58.8, - "y": 50 + "x": 14, + "y": 5.2 }, { - "x": 58, - "y": 50 + "x": 13.2, + "y": 5.2 }, { - "x": 57.2, - "y": 50 + "x": 12.4, + "y": 5.2 }, { - "x": 56.4, - "y": 50 + "x": 11.6, + "y": 5.2 }, { - "x": 55.6, - "y": 50 + "x": 10.8, + "y": 5.2 }, { - "x": 55.2, - "y": 50 + "x": 10.4, + "y": 5.2 }, { - "x": 54.4, - "y": 50 + "x": 9.6, + "y": 5.2 }, { - "x": 53.6, - "y": 50 + "x": 8.8, + "y": 5.2 }, { - "x": 53.2, - "y": 50 + "x": 8.4, + "y": 5.2 }, { - "x": 52.8, - "y": 50 + "x": 8, + "y": 5.2 }, { - "x": 52, - "y": 50 + "x": 7.2, + "y": 5.2 }, { - "x": 51.6, - "y": 50 + "x": 6.8, + "y": 5.2 }, { - "x": 51.2, - "y": 50 + "x": 6.4, + "y": 5.2 }, { - "x": 50.8, - "y": 50 + "x": 6, + "y": 5.2 }, { - "x": 50.4, - "y": 50 + "x": 5.6, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50, - "y": 50 + "x": 5.2, + "y": 5.2 }, { - "x": 50.4, - "y": 50 + "x": 5.6, + "y": 5.2 }, { - "x": 50.8, - "y": 50 + "x": 6, + "y": 5.2 }, { - "x": 51.2, - "y": 50 + "x": 6.4, + "y": 5.2 }, { - "x": 51.6, - "y": 50 + "x": 6.8, + "y": 5.2 }, { - "x": 52, - "y": 50 + "x": 7.2, + "y": 5.2 }, { - "x": 52.8, - "y": 50 + "x": 8, + "y": 5.2 }, { - "x": 53.2, - "y": 50 + "x": 8.4, + "y": 5.2 }, { - "x": 53.6, - "y": 50 + "x": 8.8, + "y": 5.2 }, { - "x": 54.4, - "y": 50 + "x": 9.6, + "y": 5.2 }, { - "x": 55.2, - "y": 50 + "x": 10.4, + "y": 5.2 }, { - "x": 55.6, - "y": 50 + "x": 10.8, + "y": 5.2 }, { - "x": 56.4, - "y": 50 + "x": 11.6, + "y": 5.2 }, { - "x": 57.2, - "y": 50 + "x": 12.4, + "y": 5.2 }, { - "x": 58, - "y": 50 + "x": 13.2, + "y": 5.2 }, { - "x": 58.8, - "y": 50 + "x": 14, + "y": 5.2 }, { - "x": 59.6, - "y": 50 + "x": 14.8, + "y": 5.2 }, { - "x": 60.4, - "y": 50 + "x": 15.6, + "y": 5.2 }, { - "x": 61.2, - "y": 50 + "x": 16.4, + "y": 5.2 }, { - "x": 62, - "y": 50 + "x": 17.2, + "y": 5.2 }, { - "x": 62.8, - "y": 50 + "x": 18, + "y": 5.2 }, { - "x": 63.6, - "y": 50 + "x": 18.8, + "y": 5.2 }, { - "x": 64, - "y": 50 + "x": 19.2, + "y": 5.2 }, { - "x": 64, - "y": 50 + "x": 19.2, + "y": 5.2 }, { - "x": 64, - "y": 50 + "x": 19.2, + "y": 5.2 }, { - "x": 64, - "y": 50 + "x": 19.2, + "y": 5.2 }, { - "x": 64, - "y": 50 + "x": 19.2, + "y": 5.2 }, { - "x": 64, - "y": 50 + "x": 19.2, + "y": 5.2 }, { - "x": 64, - "y": 50 + "x": 19.2, + "y": 5.2 }, { - "x": 64, - "y": 50 + "x": 19.2, + "y": 5.2 }, { - "x": 64, - "y": 50 + "x": 19.2, + "y": 5.2 } ] }, @@ -371,267 +371,267 @@ "type": "not_found" }, { - "width": 162.4, + "width": 124.4, "height": 1.6 }, { - "width": 164, + "width": 126, "height": 2.8 }, { - "width": 166, + "width": 128, "height": 4 }, { - "width": 167.6, + "width": 129.6, "height": 5.2 }, { - "width": 169.2, + "width": 131.2, "height": 6.4 }, { - "width": 170.8, + "width": 132.8, "height": 7.6 }, { - "width": 172.4, + "width": 134.4, "height": 8.8 }, { - "width": 174, + "width": 136, "height": 10 }, { - "width": 175.2, + "width": 137.2, "height": 10.8 }, { - "width": 176.8, + "width": 138.8, "height": 12 }, { - "width": 178, + "width": 140, "height": 12.8 }, { - "width": 179.2, + "width": 141.2, "height": 13.6 }, { - "width": 180.8, + "width": 142.8, "height": 14.8 }, { - "width": 182, + "width": 144, "height": 15.6 }, { - "width": 182.8, + "width": 144.8, "height": 16.4 }, { - "width": 184, + "width": 146, "height": 17.2 }, { - "width": 184.8, + "width": 146.8, "height": 17.6 }, { - "width": 186, + "width": 148, "height": 18.4 }, { - "width": 186.8, + "width": 148.8, "height": 19.2 }, { - "width": 187.6, + "width": 149.6, "height": 19.6 }, { - "width": 188, + "width": 150, "height": 21.2 }, { - "width": 188, + "width": 150, "height": 24.8 }, { - "width": 188, + "width": 150, "height": 30 }, { - "width": 188, + "width": 150, "height": 38 }, { - "width": 188, + "width": 150, "height": 46 }, { - "width": 188, + "width": 150, "height": 54 }, { - "width": 188, + "width": 150, "height": 61.2 }, { - "width": 188, + "width": 150, "height": 66.8 }, { - "width": 188, + "width": 150, "height": 71.6 }, { - "width": 188, + "width": 150, "height": 75.6 }, { - "width": 188, + "width": 150, "height": 78 }, { - "width": 188, + "width": 150, "height": 79.6 }, { - "width": 188, + "width": 150, "height": 80.8 }, { - "width": 188, + "width": 150, "height": 80.8 }, { - "width": 188, + "width": 150, "height": 80.4 }, { - "width": 188, + "width": 150, "height": 79.6 }, { - "width": 187.6, + "width": 149.6, "height": 78 }, { - "width": 186.8, + "width": 148.8, "height": 76.4 }, { - "width": 186, + "width": 148, "height": 74 }, { - "width": 184.8, + "width": 146.8, "height": 71.6 }, { - "width": 184, + "width": 146, "height": 69.2 }, { - "width": 182.8, + "width": 144.8, "height": 66 }, { - "width": 182, + "width": 144, "height": 62.8 }, { - "width": 180.8, + "width": 142.8, "height": 59.2 }, { - "width": 179.2, + "width": 141.2, "height": 55.6 }, { - "width": 178, + "width": 140, "height": 52 }, { - "width": 176.8, + "width": 138.8, "height": 48 }, { - "width": 175.2, + "width": 137.2, "height": 44 }, { - "width": 174, + "width": 136, "height": 40 }, { - "width": 172.4, + "width": 134.4, "height": 37.6 }, { - "width": 170.8, + "width": 132.8, "height": 38 }, { - "width": 169.2, + "width": 131.2, "height": 30.4 }, { - "width": 167.6, + "width": 129.6, "height": 25.2 }, { - "width": 166, + "width": 128, "height": 20.4 }, { - "width": 164, + "width": 126, "height": 16 }, { - "width": 162.4, + "width": 124.4, "height": 12.4 }, { - "width": 160.8, + "width": 122.8, "height": 9.2 }, { - "width": 160, + "width": 122, "height": 6.8 }, { - "width": 160, + "width": 122, "height": 5.2 }, { - "width": 160, + "width": 122, "height": 3.6 }, { - "width": 160, + "width": 122, "height": 2.4 }, { - "width": 160, + "width": 122, "height": 1.6 }, { - "width": 160, + "width": 122, "height": 0.8 }, { - "width": 160, + "width": 122, "height": 0.4 }, { - "width": 160, + "width": 122, "height": 0.4 }, { - "width": 160, + "width": 122, "height": 0 } ] @@ -658,10 +658,10 @@ 0, 0.012518823, 0.0741024, - 0.2254293, + 0.22542936, 0.42628878, - 0.5976641, - 0.7280312, + 0.5976642, + 0.7280313, 0.82100236, 0.8845844, 0.9267946, @@ -706,17 +706,17 @@ 1, 0.9944124, 0.9417388, - 0.8184184, - 0.6157812, - 0.4361611, - 0.2968906, - 0.19641554, - 0.12716137, - 0.080921985, - 0.050773025, - 0.03147719, - 0.019312752, - 0.011740655, + 0.81841844, + 0.61578125, + 0.43616113, + 0.29689062, + 0.19641556, + 0.12716138, + 0.080922, + 0.050773032, + 0.031477194, + 0.019312754, + 0.011740657, 0 ] } diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealCloseTransition.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealCloseTransition.json new file mode 100644 index 000000000000..26c80e331f81 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealCloseTransition.json @@ -0,0 +1,314 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432, + 448 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 7.2, + "y": 5.2 + }, + { + "x": 11.2, + "y": 5.2 + }, + { + "x": 14, + "y": 5.2 + }, + { + "x": 16, + "y": 5.2 + }, + { + "x": 17.6, + "y": 5.2 + }, + { + "x": 18.4, + "y": 5.2 + }, + { + "x": 18.8, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 19.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 278.8 + }, + { + "width": 150, + "height": 234.8 + }, + { + "width": 150, + "height": 185.2 + }, + { + "width": 150, + "height": 138.8 + }, + { + "width": 150, + "height": 100.4 + }, + { + "width": 146.4, + "height": 69.6 + }, + { + "width": 138.4, + "height": 46.8 + }, + { + "width": 132.4, + "height": 28 + }, + { + "width": 128.4, + "height": 13.2 + }, + { + "width": 125.6, + "height": 4 + }, + { + "width": 124, + "height": 0 + }, + { + "width": 122.8, + "height": 0 + }, + { + "width": 122.4, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + }, + { + "width": 122, + "height": 0 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0.9762947, + 0.8118515, + 0.60931784, + 0.43090785, + 0.29299664, + 0.19368339, + 0.12531388, + 0.079705715, + 0.049988627, + 0.030979574, + 0.019001365, + 0.011548042, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealOpenTransition.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealOpenTransition.json new file mode 100644 index 000000000000..7a02d369c7f2 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealOpenTransition.json @@ -0,0 +1,224 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "type": "not_found" + }, + { + "x": 19.2, + "y": 5.2 + }, + { + "x": 15.6, + "y": 5.2 + }, + { + "x": 8, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "type": "not_found" + }, + { + "width": 122, + "height": 0 + }, + { + "width": 129.2, + "height": 5.2 + }, + { + "width": 144.4, + "height": 16 + }, + { + "width": 150, + "height": 62 + }, + { + "width": 150, + "height": 118.4 + }, + { + "width": 150, + "height": 166 + }, + { + "width": 150, + "height": 204 + }, + { + "width": 150, + "height": 233.2 + }, + { + "width": 150, + "height": 254.4 + }, + { + "width": 150, + "height": 270 + }, + { + "width": 150, + "height": 280.8 + }, + { + "width": 150, + "height": 288 + }, + { + "width": 150, + "height": 292.8 + }, + { + "width": 150, + "height": 296 + }, + { + "width": 150, + "height": 298 + }, + { + "width": 150, + "height": 298.8 + }, + { + "width": 150, + "height": 299.2 + }, + { + "width": 150, + "height": 299.6 + }, + { + "width": 150, + "height": 299.6 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + { + "type": "not_found" + }, + 0, + 0, + 0.0951103, + 0.2911651, + 0.48551244, + 0.6439433, + 0.76157355, + 0.8441935, + 0.9001033, + 0.9369305, + 0.96069145, + 0.97577035, + 0.98520935, + 0.9910494, + 1, + 1, + 1, + 1, + 1 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json new file mode 100644 index 000000000000..f44d4cd7c14e --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json @@ -0,0 +1,584 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432, + 448, + 464, + 480, + 496, + 512, + 528, + 544, + 560, + 576, + 592, + 608, + 624, + 640, + 656, + 672, + 688, + 704, + 720, + 736, + 752, + 768, + 784, + 800, + 816, + 832, + 848, + 864, + 880 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.6 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 293.2 + }, + { + "width": 150, + "height": 293.2 + }, + { + "width": 150, + "height": 286 + }, + { + "width": 150, + "height": 279.6 + }, + { + "width": 150, + "height": 273.2 + }, + { + "width": 150, + "height": 266.8 + }, + { + "width": 150, + "height": 260.4 + }, + { + "width": 150, + "height": 254 + }, + { + "width": 150, + "height": 247.6 + }, + { + "width": 150, + "height": 241.2 + }, + { + "width": 150, + "height": 241.2 + }, + { + "width": 150, + "height": 234.4 + }, + { + "width": 150, + "height": 228 + }, + { + "width": 150, + "height": 221.6 + }, + { + "width": 150, + "height": 215.2 + }, + { + "width": 150, + "height": 208.8 + }, + { + "width": 150, + "height": 202 + }, + { + "width": 150, + "height": 195.6 + }, + { + "width": 150, + "height": 189.2 + }, + { + "width": 150, + "height": 189.2 + }, + { + "width": 150, + "height": 182.8 + }, + { + "width": 150, + "height": 176.4 + }, + { + "width": 150, + "height": 170 + }, + { + "width": 150, + "height": 163.6 + }, + { + "width": 150, + "height": 157.2 + }, + { + "width": 150, + "height": 150.8 + }, + { + "width": 150, + "height": 144.4 + }, + { + "width": 150, + "height": 137.6 + }, + { + "width": 150, + "height": 137.6 + }, + { + "width": 150, + "height": 131.2 + }, + { + "width": 150, + "height": 124.8 + }, + { + "width": 150, + "height": 118.4 + }, + { + "width": 150, + "height": 112 + }, + { + "width": 150, + "height": 112 + }, + { + "width": 150, + "height": 99.2 + }, + { + "width": 150, + "height": 84.4 + }, + { + "width": 150, + "height": 70.8 + }, + { + "width": 150, + "height": 58 + }, + { + "width": 150, + "height": 46.4 + }, + { + "width": 150, + "height": 36.4 + }, + { + "width": 150, + "height": 28 + }, + { + "width": 150, + "height": 20.8 + }, + { + "width": 150, + "height": 15.6 + }, + { + "width": 150, + "height": 11.2 + }, + { + "width": 150, + "height": 8 + }, + { + "width": 150, + "height": 5.6 + }, + { + "width": 150, + "height": 3.6 + }, + { + "width": 150, + "height": 2.4 + }, + { + "width": 150, + "height": 1.2 + }, + { + "width": 150, + "height": 0.8 + }, + { + "width": 150, + "height": 0.4 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0.99781144, + 0.87040234, + 0.6695792, + 0.48078007, + 0.33033127, + 0.22004372, + 0.1432175, + 0.09153092, + 0.057634592, + 0.035840213, + 0.022048414, + 0.013435662, + 0.008117795, + 0 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json new file mode 100644 index 000000000000..9b68c71a7a34 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json @@ -0,0 +1,624 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432, + 448, + 464, + 480, + 496, + 512, + 528, + 544, + 560, + 576, + 592, + 608, + 624, + 640, + 656, + 672, + 688, + 704, + 720, + 736, + 752, + 768, + 784, + 800, + 816, + 832, + 848, + 864, + 880, + 896, + 912, + 928, + 944 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.6, + "y": 6.8 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 297.6 + }, + { + "width": 150, + "height": 294 + }, + { + "width": 150, + "height": 287.6 + }, + { + "width": 150, + "height": 282.8 + }, + { + "width": 150, + "height": 278 + }, + { + "width": 150, + "height": 273.2 + }, + { + "width": 150, + "height": 268.4 + }, + { + "width": 150, + "height": 263.6 + }, + { + "width": 150, + "height": 258.8 + }, + { + "width": 150, + "height": 258.8 + }, + { + "width": 150, + "height": 253.6 + }, + { + "width": 150, + "height": 248.8 + }, + { + "width": 150, + "height": 244 + }, + { + "width": 150, + "height": 239.2 + }, + { + "width": 150, + "height": 234.4 + }, + { + "width": 150, + "height": 229.6 + }, + { + "width": 150, + "height": 224.8 + }, + { + "width": 150, + "height": 220 + }, + { + "width": 150, + "height": 220 + }, + { + "width": 150, + "height": 214.8 + }, + { + "width": 150, + "height": 210 + }, + { + "width": 150, + "height": 205.2 + }, + { + "width": 150, + "height": 200.4 + }, + { + "width": 150, + "height": 195.6 + }, + { + "width": 150, + "height": 190.8 + }, + { + "width": 150, + "height": 186 + }, + { + "width": 150, + "height": 181.2 + }, + { + "width": 150, + "height": 181.2 + }, + { + "width": 150, + "height": 176.4 + }, + { + "width": 150, + "height": 171.6 + }, + { + "width": 150, + "height": 166.8 + }, + { + "width": 150, + "height": 161.6 + }, + { + "width": 150, + "height": 161.6 + }, + { + "width": 150, + "height": 147.2 + }, + { + "width": 150, + "height": 122 + }, + { + "width": 150, + "height": 95.2 + }, + { + "width": 150, + "height": 70.8 + }, + { + "width": 150, + "height": 51.6 + }, + { + "width": 150, + "height": 36.8 + }, + { + "width": 150, + "height": 25.6 + }, + { + "width": 150, + "height": 17.2 + }, + { + "width": 150, + "height": 11.2 + }, + { + "width": 150, + "height": 6.8 + }, + { + "width": 150, + "height": 4 + }, + { + "width": 150, + "height": 2 + }, + { + "width": 150, + "height": 0.8 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0.99979615, + 0.8860379, + 0.6869267, + 0.4955439, + 0.34154767, + 0.22803628, + 0.14868057, + 0.09515619, + 0.059987247, + 0.037340224, + 0.02299112, + 0.01402092, + 0.008477271, + 0, + 0, + 0, + 0 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragOpen.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragOpen.json new file mode 100644 index 000000000000..1bf6a62d02af --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragOpen.json @@ -0,0 +1,544 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432, + 448, + 464, + 480, + 496, + 512, + 528, + 544, + 560, + 576, + 592, + 608, + 624, + 640, + 656, + 672, + 688, + 704, + 720, + 736, + 752, + 768, + 784, + 800, + 816 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "width": 150, + "height": 1.6 + }, + { + "width": 150, + "height": 1.6 + }, + { + "width": 150, + "height": 3.2 + }, + { + "width": 150, + "height": 4.8 + }, + { + "width": 150, + "height": 6.4 + }, + { + "width": 150, + "height": 8 + }, + { + "width": 150, + "height": 9.6 + }, + { + "width": 150, + "height": 11.2 + }, + { + "width": 150, + "height": 12.8 + }, + { + "width": 150, + "height": 14.4 + }, + { + "width": 150, + "height": 14.4 + }, + { + "width": 150, + "height": 16.4 + }, + { + "width": 150, + "height": 18 + }, + { + "width": 150, + "height": 19.6 + }, + { + "width": 150, + "height": 20.8 + }, + { + "width": 150, + "height": 22.8 + }, + { + "width": 150, + "height": 24.4 + }, + { + "width": 150, + "height": 26 + }, + { + "width": 150, + "height": 27.6 + }, + { + "width": 150, + "height": 27.6 + }, + { + "width": 150, + "height": 29.2 + }, + { + "width": 150, + "height": 30.8 + }, + { + "width": 150, + "height": 32.4 + }, + { + "width": 150, + "height": 34 + }, + { + "width": 150, + "height": 40.4 + }, + { + "width": 150, + "height": 52.4 + }, + { + "width": 150, + "height": 64.8 + }, + { + "width": 150, + "height": 83.2 + }, + { + "width": 150, + "height": 96 + }, + { + "width": 150, + "height": 114.8 + }, + { + "width": 150, + "height": 132 + }, + { + "width": 150, + "height": 148 + }, + { + "width": 150, + "height": 162 + }, + { + "width": 150, + "height": 168.4 + }, + { + "width": 150, + "height": 186 + }, + { + "width": 150, + "height": 208 + }, + { + "width": 150, + "height": 229.6 + }, + { + "width": 150, + "height": 248.4 + }, + { + "width": 150, + "height": 263.2 + }, + { + "width": 150, + "height": 274.8 + }, + { + "width": 150, + "height": 283.2 + }, + { + "width": 150, + "height": 289.2 + }, + { + "width": 150, + "height": 293.6 + }, + { + "width": 150, + "height": 296 + }, + { + "width": 150, + "height": 298 + }, + { + "width": 150, + "height": 298.8 + }, + { + "width": 150, + "height": 299.6 + }, + { + "width": 150, + "height": 299.6 + }, + { + "width": 150, + "height": 300 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + 0, + 0, + 0, + 0, + 0.0067873597, + 0.06125766, + 0.19080031, + 0.39327443, + 0.5711931, + 0.7085583, + 0.8074065, + 0.8754226, + 0.9207788, + 0.95032376, + 0.9692185, + 0.98112255, + 0.9885286, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json new file mode 100644 index 000000000000..86805bd6ff29 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json @@ -0,0 +1,424 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432, + 448, + 464, + 480, + 496, + 512, + 528, + 544, + 560, + 576, + 592, + 608, + 624 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.6, + "y": 6.4 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 290 + }, + { + "width": 150, + "height": 278.8 + }, + { + "width": 150, + "height": 266 + }, + { + "width": 150, + "height": 252 + }, + { + "width": 150, + "height": 252 + }, + { + "width": 150, + "height": 223.6 + }, + { + "width": 150, + "height": 182.8 + }, + { + "width": 150, + "height": 141.2 + }, + { + "width": 150, + "height": 104 + }, + { + "width": 150, + "height": 72 + }, + { + "width": 150, + "height": 46 + }, + { + "width": 150, + "height": 28 + }, + { + "width": 150, + "height": 15.6 + }, + { + "width": 150, + "height": 7.2 + }, + { + "width": 150, + "height": 2 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0.99479187, + 0.8575029, + 0.65572864, + 0.4691311, + 0.3215357, + 0.21380007, + 0.13896108, + 0.0887118, + 0.05580789, + 0.03467691, + 0.021318138, + 0.0129826665, + 0.007839739, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingOpen.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingOpen.json new file mode 100644 index 000000000000..98519db9e848 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingOpen.json @@ -0,0 +1,374 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432, + 448, + 464, + 480, + 496, + 512, + 528, + 544 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "width": 150, + "height": 2 + }, + { + "width": 150, + "height": 4.4 + }, + { + "width": 150, + "height": 6.8 + }, + { + "width": 150, + "height": 10 + }, + { + "width": 150, + "height": 13.2 + }, + { + "width": 150, + "height": 16.8 + }, + { + "width": 150, + "height": 16.8 + }, + { + "width": 150, + "height": 23.6 + }, + { + "width": 150, + "height": 32.8 + }, + { + "width": 150, + "height": 76.8 + }, + { + "width": 150, + "height": 123.6 + }, + { + "width": 150, + "height": 164.8 + }, + { + "width": 150, + "height": 198.4 + }, + { + "width": 150, + "height": 225.6 + }, + { + "width": 150, + "height": 246.4 + }, + { + "width": 150, + "height": 262 + }, + { + "width": 150, + "height": 273.2 + }, + { + "width": 150, + "height": 281.6 + }, + { + "width": 150, + "height": 287.6 + }, + { + "width": 150, + "height": 292 + }, + { + "width": 150, + "height": 294.8 + }, + { + "width": 150, + "height": 296.4 + }, + { + "width": 150, + "height": 297.6 + }, + { + "width": 150, + "height": 298.4 + }, + { + "width": 150, + "height": 299.2 + }, + { + "width": 150, + "height": 299.6 + }, + { + "width": 150, + "height": 299.6 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + 0, + 0, + 0.008216977, + 0.06259775, + 0.19032806, + 0.39281356, + 0.57081985, + 0.7082821, + 0.80721295, + 0.8752918, + 0.9206928, + 0.95026827, + 0.9691833, + 0.98110056, + 0.988515, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_magneticDetachAndReattach.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_magneticDetachAndReattach.json new file mode 100644 index 000000000000..850cee9130d0 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_magneticDetachAndReattach.json @@ -0,0 +1,744 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432, + 448, + 464, + 480, + 496, + 512, + 528, + 544, + 560, + 576, + 592, + 608, + 624, + 640, + 656, + 672, + 688, + 704, + 720, + 736, + 752, + 768, + 784, + 800, + 816, + 832, + 848, + 864, + 880, + 896, + 912, + 928, + 944, + 960, + 976, + 992, + 1008, + 1024, + 1040, + 1056, + 1072, + 1088, + 1104, + 1120, + 1136 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "width": 150, + "height": 2.8 + }, + { + "width": 150, + "height": 4.8 + }, + { + "width": 150, + "height": 6.8 + }, + { + "width": 150, + "height": 8.4 + }, + { + "width": 150, + "height": 10.4 + }, + { + "width": 150, + "height": 12.4 + }, + { + "width": 150, + "height": 14 + }, + { + "width": 150, + "height": 16 + }, + { + "width": 150, + "height": 17.6 + }, + { + "width": 150, + "height": 19.2 + }, + { + "width": 150, + "height": 20.8 + }, + { + "width": 150, + "height": 22.4 + }, + { + "width": 150, + "height": 24 + }, + { + "width": 150, + "height": 25.6 + }, + { + "width": 150, + "height": 26.8 + }, + { + "width": 150, + "height": 28 + }, + { + "width": 150, + "height": 29.2 + }, + { + "width": 150, + "height": 30.4 + }, + { + "width": 150, + "height": 31.6 + }, + { + "width": 150, + "height": 32.4 + }, + { + "width": 150, + "height": 33.2 + }, + { + "width": 150, + "height": 34 + }, + { + "width": 150, + "height": 36.8 + }, + { + "width": 150, + "height": 42.4 + }, + { + "width": 150, + "height": 50.8 + }, + { + "width": 150, + "height": 64 + }, + { + "width": 150, + "height": 78 + }, + { + "width": 150, + "height": 91.2 + }, + { + "width": 150, + "height": 102.4 + }, + { + "width": 150, + "height": 112.4 + }, + { + "width": 150, + "height": 120 + }, + { + "width": 150, + "height": 126 + }, + { + "width": 150, + "height": 130 + }, + { + "width": 150, + "height": 132.8 + }, + { + "width": 150, + "height": 134 + }, + { + "width": 150, + "height": 134 + }, + { + "width": 150, + "height": 133.2 + }, + { + "width": 150, + "height": 131.2 + }, + { + "width": 150, + "height": 128.8 + }, + { + "width": 150, + "height": 125.2 + }, + { + "width": 150, + "height": 121.6 + }, + { + "width": 150, + "height": 117.6 + }, + { + "width": 150, + "height": 112.8 + }, + { + "width": 150, + "height": 108 + }, + { + "width": 150, + "height": 102.4 + }, + { + "width": 150, + "height": 96.4 + }, + { + "width": 150, + "height": 91.2 + }, + { + "width": 150, + "height": 88 + }, + { + "width": 150, + "height": 81.6 + }, + { + "width": 150, + "height": 70.8 + }, + { + "width": 150, + "height": 59.2 + }, + { + "width": 150, + "height": 48 + }, + { + "width": 150, + "height": 38.4 + }, + { + "width": 150, + "height": 30 + }, + { + "width": 150, + "height": 22.8 + }, + { + "width": 150, + "height": 17.2 + }, + { + "width": 150, + "height": 12.4 + }, + { + "width": 150, + "height": 9.2 + }, + { + "width": 150, + "height": 6.4 + }, + { + "width": 150, + "height": 4.4 + }, + { + "width": 150, + "height": 2.8 + }, + { + "width": 150, + "height": 1.6 + }, + { + "width": 150, + "height": 1.2 + }, + { + "width": 150, + "height": 0.4 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + 0, + 0, + 0.0066464543, + 0.059778452, + 0.1875459, + 0.39009166, + 0.5686131, + 0.70664865, + 0.8060679, + 0.87451804, + 0.92018366, + 0.94994, + 0.9689752, + 0.9809703, + 0.98843443, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0.98828065, + 0.9288363, + 0.7806658, + 0.57941735, + 0.40687433, + 0.27529213, + 0.18131107, + 0.11697123, + 0.074225225, + 0.046460062, + 0.028744182, + 0.017604083, + 0.010684598 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealCloseTransition.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealCloseTransition.json new file mode 100644 index 000000000000..afa005ac421e --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealCloseTransition.json @@ -0,0 +1,304 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 300 + }, + { + "width": 150, + "height": 278.8 + }, + { + "width": 150, + "height": 234.8 + }, + { + "width": 150, + "height": 185.2 + }, + { + "width": 150, + "height": 138.8 + }, + { + "width": 150, + "height": 100.4 + }, + { + "width": 150, + "height": 66.8 + }, + { + "width": 150, + "height": 41.6 + }, + { + "width": 150, + "height": 23.6 + }, + { + "width": 150, + "height": 12 + }, + { + "width": 150, + "height": 4.4 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 0 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0.9762947, + 0.8118515, + 0.60931784, + 0.43090785, + 0.29299664, + 0.19368339, + 0.12531388, + 0.079705715, + 0.049988627, + 0.030979574, + 0.019001365, + 0.011548042, + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealOpenTransition.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealOpenTransition.json new file mode 100644 index 000000000000..317d4804fa52 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealOpenTransition.json @@ -0,0 +1,254 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "type": "not_found" + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + }, + { + "x": 5.2, + "y": 5.2 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "type": "not_found" + }, + { + "width": 150, + "height": 0 + }, + { + "width": 150, + "height": 5.2 + }, + { + "width": 150, + "height": 16 + }, + { + "width": 150, + "height": 28.4 + }, + { + "width": 150, + "height": 63.6 + }, + { + "width": 150, + "height": 116.4 + }, + { + "width": 150, + "height": 161.2 + }, + { + "width": 150, + "height": 197.2 + }, + { + "width": 150, + "height": 225.2 + }, + { + "width": 150, + "height": 246.8 + }, + { + "width": 150, + "height": 262.4 + }, + { + "width": 150, + "height": 274 + }, + { + "width": 150, + "height": 282.4 + }, + { + "width": 150, + "height": 288.4 + }, + { + "width": 150, + "height": 292.4 + }, + { + "width": 150, + "height": 294.8 + }, + { + "width": 150, + "height": 296.4 + }, + { + "width": 150, + "height": 297.6 + }, + { + "width": 150, + "height": 298.4 + }, + { + "width": 150, + "height": 299.2 + }, + { + "width": 150, + "height": 299.6 + }, + { + "width": 150, + "height": 299.6 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + { + "type": "not_found" + }, + 0, + 0, + 0.0951103, + 0.2911651, + 0.48551244, + 0.6439433, + 0.76157355, + 0.8441935, + 0.9001033, + 0.9369305, + 0.96069145, + 0.97577035, + 0.98520935, + 0.9910494, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json deleted file mode 100644 index 57f67665242c..000000000000 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json +++ /dev/null @@ -1,654 +0,0 @@ -{ - "frame_ids": [ - 0, - 16, - 32, - 48, - 64, - 80, - 96, - 112, - 128, - 144, - 160, - 176, - 192, - 208, - 224, - 240, - 256, - 272, - 288, - 304, - 320, - 336, - 352, - 368, - 384, - 400, - 416, - 432, - 448, - 464, - 480, - 496, - 512, - 528, - 544, - 560, - 576, - 592, - 608, - 624, - 640, - 656, - 672, - 688, - 704, - 720, - 736, - 752, - 768, - 784, - 800, - 816, - 832, - 848, - 864, - 880, - 896, - 912, - 928, - 944, - 960, - 976, - 992 - ], - "features": [ - { - "name": "RevealElement_position", - "type": "dpOffset", - "data_points": [ - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50.4 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 52.4, - "y": 50 - }, - { - "x": 56, - "y": 50 - }, - { - "x": 58.8, - "y": 50 - }, - { - "x": 60.8, - "y": 50 - }, - { - "x": 62, - "y": 50 - }, - { - "x": 62.8, - "y": 50 - }, - { - "x": 63.6, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - } - ] - }, - { - "name": "RevealElement_size", - "type": "dpSize", - "data_points": [ - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 393.2 - }, - { - "width": 188, - "height": 393.2 - }, - { - "width": 188, - "height": 386 - }, - { - "width": 188, - "height": 379.6 - }, - { - "width": 188, - "height": 372.8 - }, - { - "width": 188, - "height": 366.8 - }, - { - "width": 188, - "height": 360.4 - }, - { - "width": 188, - "height": 354 - }, - { - "width": 188, - "height": 347.6 - }, - { - "width": 188, - "height": 341.2 - }, - { - "width": 188, - "height": 341.2 - }, - { - "width": 188, - "height": 334 - }, - { - "width": 188, - "height": 328 - }, - { - "width": 188, - "height": 321.6 - }, - { - "width": 188, - "height": 315.2 - }, - { - "width": 188, - "height": 308.8 - }, - { - "width": 188, - "height": 302.4 - }, - { - "width": 188, - "height": 296 - }, - { - "width": 188, - "height": 289.6 - }, - { - "width": 188, - "height": 289.6 - }, - { - "width": 188, - "height": 282.8 - }, - { - "width": 188, - "height": 276.4 - }, - { - "width": 188, - "height": 270 - }, - { - "width": 188, - "height": 263.6 - }, - { - "width": 188, - "height": 257.2 - }, - { - "width": 188, - "height": 250.8 - }, - { - "width": 188, - "height": 244.4 - }, - { - "width": 188, - "height": 238 - }, - { - "width": 188, - "height": 238 - }, - { - "width": 188, - "height": 231.2 - }, - { - "width": 188, - "height": 224.8 - }, - { - "width": 188, - "height": 218.4 - }, - { - "width": 188, - "height": 212 - }, - { - "width": 188, - "height": 212 - }, - { - "width": 188, - "height": 192.4 - }, - { - "width": 188, - "height": 159.6 - }, - { - "width": 188, - "height": 124.4 - }, - { - "width": 188, - "height": 92.8 - }, - { - "width": 183.2, - "height": 66.4 - }, - { - "width": 176, - "height": 46 - }, - { - "width": 170.4, - "height": 28.8 - }, - { - "width": 166.8, - "height": 15.2 - }, - { - "width": 164, - "height": 6.4 - }, - { - "width": 162.4, - "height": 0.8 - }, - { - "width": 161.2, - "height": 0 - }, - { - "width": 160.4, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - } - ] - }, - { - "name": "RevealElement_alpha", - "type": "float", - "data_points": [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 0.9808927, - 0.8211168, - 0.61845565, - 0.43834114, - 0.29850912, - 0.19755232, - 0.12793064, - 0.08142871, - 0.051099956, - 0.031684637, - 0.019442618, - 0.011821032, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ] - } - ] -}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json deleted file mode 100644 index 01bc852cf7f4..000000000000 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json +++ /dev/null @@ -1,644 +0,0 @@ -{ - "frame_ids": [ - 0, - 16, - 32, - 48, - 64, - 80, - 96, - 112, - 128, - 144, - 160, - 176, - 192, - 208, - 224, - 240, - 256, - 272, - 288, - 304, - 320, - 336, - 352, - 368, - 384, - 400, - 416, - 432, - 448, - 464, - 480, - 496, - 512, - 528, - 544, - 560, - 576, - 592, - 608, - 624, - 640, - 656, - 672, - 688, - 704, - 720, - 736, - 752, - 768, - 784, - 800, - 816, - 832, - 848, - 864, - 880, - 896, - 912, - 928, - 944, - 960, - 976 - ], - "features": [ - { - "name": "RevealElement_position", - "type": "dpOffset", - "data_points": [ - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50.8, - "y": 52 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 52.4, - "y": 50 - }, - { - "x": 55.6, - "y": 50 - }, - { - "x": 58.4, - "y": 50 - }, - { - "x": 60.4, - "y": 50 - }, - { - "x": 61.6, - "y": 50 - }, - { - "x": 62.8, - "y": 50 - }, - { - "x": 63.2, - "y": 50 - }, - { - "x": 63.6, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - } - ] - }, - { - "name": "RevealElement_size", - "type": "dpSize", - "data_points": [ - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 390 - }, - { - "width": 188, - "height": 390 - }, - { - "width": 188, - "height": 379.2 - }, - { - "width": 188, - "height": 371.2 - }, - { - "width": 188, - "height": 363.2 - }, - { - "width": 188, - "height": 355.2 - }, - { - "width": 188, - "height": 347.2 - }, - { - "width": 188, - "height": 339.2 - }, - { - "width": 188, - "height": 331.2 - }, - { - "width": 188, - "height": 323.2 - }, - { - "width": 188, - "height": 323.2 - }, - { - "width": 188, - "height": 314.8 - }, - { - "width": 188, - "height": 306.8 - }, - { - "width": 188, - "height": 298.8 - }, - { - "width": 188, - "height": 290.8 - }, - { - "width": 188, - "height": 282.8 - }, - { - "width": 188, - "height": 274.8 - }, - { - "width": 188, - "height": 266.8 - }, - { - "width": 188, - "height": 258.8 - }, - { - "width": 188, - "height": 258.8 - }, - { - "width": 188, - "height": 250.4 - }, - { - "width": 188, - "height": 242.4 - }, - { - "width": 188, - "height": 234.4 - }, - { - "width": 188, - "height": 226.4 - }, - { - "width": 188, - "height": 218.4 - }, - { - "width": 188, - "height": 210.4 - }, - { - "width": 188, - "height": 202.4 - }, - { - "width": 188, - "height": 194.4 - }, - { - "width": 188, - "height": 194.4 - }, - { - "width": 188, - "height": 185.6 - }, - { - "width": 188, - "height": 178 - }, - { - "width": 188, - "height": 170 - }, - { - "width": 188, - "height": 161.6 - }, - { - "width": 188, - "height": 161.6 - }, - { - "width": 188, - "height": 144.8 - }, - { - "width": 188, - "height": 118.8 - }, - { - "width": 188, - "height": 92 - }, - { - "width": 183.6, - "height": 68 - }, - { - "width": 176.8, - "height": 48.4 - }, - { - "width": 171.6, - "height": 32 - }, - { - "width": 167.6, - "height": 18 - }, - { - "width": 164.8, - "height": 8.8 - }, - { - "width": 162.8, - "height": 2.8 - }, - { - "width": 161.6, - "height": 0 - }, - { - "width": 160.8, - "height": 0 - }, - { - "width": 160.4, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - } - ] - }, - { - "name": "RevealElement_alpha", - "type": "float", - "data_points": [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 0.9967737, - 0.86538374, - 0.66414475, - 0.47619528, - 0.32686388, - 0.21757984, - 0.14153665, - 0.09041709, - 0.05691254, - 0.035380244, - 0.02175957, - 0.01325649, - 0.008007765, - 0, - 0, - 0, - 0, - 0, - 0 - ] - } - ] -}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json deleted file mode 100644 index a82db346ed58..000000000000 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json +++ /dev/null @@ -1,444 +0,0 @@ -{ - "frame_ids": [ - 0, - 16, - 32, - 48, - 64, - 80, - 96, - 112, - 128, - 144, - 160, - 176, - 192, - 208, - 224, - 240, - 256, - 272, - 288, - 304, - 320, - 336, - 352, - 368, - 384, - 400, - 416, - 432, - 448, - 464, - 480, - 496, - 512, - 528, - 544, - 560, - 576, - 592, - 608, - 624, - 640, - 656 - ], - "features": [ - { - "name": "RevealElement_position", - "type": "dpOffset", - "data_points": [ - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50.4, - "y": 50.8 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 51.2, - "y": 50 - }, - { - "x": 55.6, - "y": 50 - }, - { - "x": 58.8, - "y": 50 - }, - { - "x": 60.8, - "y": 50 - }, - { - "x": 62, - "y": 50 - }, - { - "x": 63.2, - "y": 50 - }, - { - "x": 63.6, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - } - ] - }, - { - "name": "RevealElement_size", - "type": "dpSize", - "data_points": [ - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 389.6 - }, - { - "width": 188, - "height": 378.8 - }, - { - "width": 188, - "height": 366 - }, - { - "width": 188, - "height": 352 - }, - { - "width": 188, - "height": 352 - }, - { - "width": 188, - "height": 316.8 - }, - { - "width": 188, - "height": 261.2 - }, - { - "width": 188, - "height": 202.8 - }, - { - "width": 188, - "height": 150.8 - }, - { - "width": 188, - "height": 107.6 - }, - { - "width": 186, - "height": 74.4 - }, - { - "width": 177.2, - "height": 49.6 - }, - { - "width": 170.8, - "height": 29.6 - }, - { - "width": 166.8, - "height": 12.8 - }, - { - "width": 164, - "height": 2.4 - }, - { - "width": 162, - "height": 0 - }, - { - "width": 160.8, - "height": 0 - }, - { - "width": 160.4, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - } - ] - }, - { - "name": "RevealElement_alpha", - "type": "float", - "data_points": [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 0.9833227, - 0.8263634, - 0.623688, - 0.44261706, - 0.3016883, - 0.1997872, - 0.12944388, - 0.08242595, - 0.051743627, - 0.032093227, - 0.019698441, - 0.0119793415, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ] - } - ] -}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json deleted file mode 100644 index 1030455e873f..000000000000 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "frame_ids": [ - 0, - 16, - 32, - 48, - 64, - 80, - 96, - 112, - 128, - 144, - 160, - 176, - 192, - 208, - 224, - 240, - 256, - 272, - 288, - 304, - 320, - 336, - 352, - 368, - 384, - 400, - 416, - 432, - 448, - 464 - ], - "features": [ - { - "name": "RevealElement_position", - "type": "dpOffset", - "data_points": [ - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 53.2, - "y": 50 - }, - { - "x": 57.2, - "y": 50 - }, - { - "x": 59.6, - "y": 50 - }, - { - "x": 61.6, - "y": 50 - }, - { - "x": 62.8, - "y": 50 - }, - { - "x": 63.6, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 - } - ] - }, - { - "name": "RevealElement_size", - "type": "dpSize", - "data_points": [ - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 400 - }, - { - "width": 188, - "height": 372 - }, - { - "width": 188, - "height": 312.8 - }, - { - "width": 188, - "height": 246.8 - }, - { - "width": 188, - "height": 185.2 - }, - { - "width": 188, - "height": 133.6 - }, - { - "width": 188, - "height": 93.2 - }, - { - "width": 181.6, - "height": 62.8 - }, - { - "width": 174, - "height": 40.8 - }, - { - "width": 168.8, - "height": 22.4 - }, - { - "width": 165.2, - "height": 10 - }, - { - "width": 162.8, - "height": 2.4 - }, - { - "width": 161.2, - "height": 0 - }, - { - "width": 160.4, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - }, - { - "width": 160, - "height": 0 - } - ] - }, - { - "name": "RevealElement_alpha", - "type": "float", - "data_points": [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 0.91758585, - 0.72435355, - 0.52812576, - 0.3665868, - 0.24600428, - 0.16102076, - 0.103373945, - 0.06533456, - 0.04075712, - 0.025142312, - 0.015358448, - 0.0092999935, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ] - } - ] -}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealOpenTransition.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealOpenTransition.json deleted file mode 100644 index 622c29eebfb4..000000000000 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealOpenTransition.json +++ /dev/null @@ -1,244 +0,0 @@ -{ - "frame_ids": [ - 0, - 16, - 32, - 48, - 64, - 80, - 96, - 112, - 128, - 144, - 160, - 176, - 192, - 208, - 224, - 240, - 256, - 272, - 288, - 304, - 320, - 336 - ], - "features": [ - { - "name": "RevealElement_position", - "type": "dpOffset", - "data_points": [ - { - "type": "not_found" - }, - { - "x": 64, - "y": 50 - }, - { - "x": 59.2, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - }, - { - "x": 50, - "y": 50 - } - ] - }, - { - "name": "RevealElement_size", - "type": "dpSize", - "data_points": [ - { - "type": "not_found" - }, - { - "width": 160, - "height": 0 - }, - { - "width": 169.6, - "height": 6.8 - }, - { - "width": 188, - "height": 26.8 - }, - { - "width": 188, - "height": 95.6 - }, - { - "width": 188, - "height": 163.2 - }, - { - "width": 188, - "height": 222 - }, - { - "width": 188, - "height": 269.6 - }, - { - "width": 188, - "height": 307.2 - }, - { - "width": 188, - "height": 335.2 - }, - { - "width": 188, - "height": 356 - }, - { - "width": 188, - "height": 370.4 - }, - { - "width": 188, - "height": 380.8 - }, - { - "width": 188, - "height": 387.6 - }, - { - "width": 188, - "height": 392.4 - }, - { - "width": 188, - "height": 395.2 - }, - { - "width": 188, - "height": 397.2 - }, - { - "width": 188, - "height": 398 - }, - { - "width": 188, - "height": 398.8 - }, - { - "width": 188, - "height": 399.2 - }, - { - "width": 188, - "height": 399.2 - }, - { - "width": 188, - "height": 399.6 - } - ] - }, - { - "name": "RevealElement_alpha", - "type": "float", - "data_points": [ - { - "type": "not_found" - }, - 0, - 0.05698657, - 0.24197984, - 0.44158113, - 0.6097554, - 0.73685503, - 0.8271309, - 0.8886989, - 0.9294886, - 0.9559254, - 0.97276413, - 0.98333716, - 0.98989624, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 - ] - } - ] -}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/reveal/ContentRevealTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/reveal/ContentRevealTest.kt index f4e2328f16ab..1bc83e0401bf 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/reveal/ContentRevealTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/reveal/ContentRevealTest.kt @@ -37,7 +37,6 @@ import androidx.compose.ui.test.swipeUp import androidx.compose.ui.test.swipeWithVelocity import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.FeatureCaptures.elementAlpha @@ -47,8 +46,8 @@ import com.android.compose.animation.scene.SceneTransitionLayoutForTesting import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.featureOfElement import com.android.compose.animation.scene.transitions -import com.android.mechanics.behavior.EdgeContainerExpansionSpec -import com.android.mechanics.behavior.edgeContainerExpansionBackground +import com.android.mechanics.behavior.VerticalExpandContainerSpec +import com.android.mechanics.behavior.verticalExpandContainerBackground import kotlin.math.sin import kotlinx.coroutines.CoroutineScope import org.junit.Rule @@ -63,26 +62,48 @@ import platform.test.motion.compose.createFixedConfigurationComposeMotionTestRul import platform.test.motion.compose.recordMotion import platform.test.motion.compose.runTest import platform.test.motion.testing.createGoldenPathManager +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters +import platform.test.screenshot.PathConfig +import platform.test.screenshot.PathElementNoContext -@RunWith(AndroidJUnit4::class) @MotionTest -class ContentRevealTest { +@RunWith(ParameterizedAndroidJunit4::class) +class ContentRevealTest(private val isFloating: Boolean) { + + private val pathConfig = + PathConfig( + PathElementNoContext("floating", isDir = false) { + if (isFloating) "floating" else "edge" + } + ) private val goldenPaths = - createGoldenPathManager("frameworks/base/packages/SystemUI/compose/scene/tests/goldens") + createGoldenPathManager( + "frameworks/base/packages/SystemUI/compose/scene/tests/goldens", + pathConfig, + ) @get:Rule val motionRule = createFixedConfigurationComposeMotionTestRule(goldenPaths) private val fakeHaptics = FakeHaptics() + private val motionSpec = VerticalExpandContainerSpec(isFloating) + @Test fun verticalReveal_triggeredRevealOpenTransition() { - assertVerticalContainerRevealMotion(TriggeredRevealMotion(SceneClosed, SceneOpen)) + assertVerticalContainerRevealMotion( + TriggeredRevealMotion(SceneClosed, SceneOpen), + "verticalReveal_triggeredRevealOpenTransition", + ) } @Test fun verticalReveal_triggeredRevealCloseTransition() { - assertVerticalContainerRevealMotion(TriggeredRevealMotion(SceneOpen, SceneClosed)) + assertVerticalContainerRevealMotion( + TriggeredRevealMotion(SceneOpen, SceneClosed), + "verticalReveal_triggeredRevealCloseTransition", + ) } @Test @@ -90,15 +111,18 @@ class ContentRevealTest { assertVerticalContainerRevealMotion( GestureRevealMotion(SceneClosed) { val gestureDurationMillis = 1000L + // detach position for the floating container is larger + val gestureHeight = if (isFloating) 160.dp.toPx() else 100.dp.toPx() swipe( curve = { val progress = it / gestureDurationMillis.toFloat() - val y = sin(progress * Math.PI).toFloat() * 100.dp.toPx() + val y = sin(progress * Math.PI).toFloat() * gestureHeight Offset(centerX, y) }, gestureDurationMillis, ) - } + }, + "verticalReveal_gesture_magneticDetachAndReattach", ) } @@ -107,7 +131,8 @@ class ContentRevealTest { assertVerticalContainerRevealMotion( GestureRevealMotion(SceneClosed) { swipeDown(endY = 200.dp.toPx(), durationMillis = 500) - } + }, + "verticalReveal_gesture_dragOpen", ) } @@ -117,7 +142,8 @@ class ContentRevealTest { GestureRevealMotion(SceneClosed) { val end = Offset(centerX, 80.dp.toPx()) swipeWithVelocity(start = topCenter, end = end, endVelocity = FlingVelocity.toPx()) - } + }, + "verticalReveal_gesture_flingOpen", ) } @@ -126,7 +152,8 @@ class ContentRevealTest { assertVerticalContainerRevealMotion( GestureRevealMotion(SceneOpen) { swipeUp(200.dp.toPx(), 0.dp.toPx(), durationMillis = 500) - } + }, + "verticalReveal_gesture_dragFullyClose", ) } @@ -134,8 +161,9 @@ class ContentRevealTest { fun verticalReveal_gesture_dragHalfClose() { assertVerticalContainerRevealMotion( GestureRevealMotion(SceneOpen) { - swipeUp(350.dp.toPx(), 100.dp.toPx(), durationMillis = 500) - } + swipeUp(250.dp.toPx(), 100.dp.toPx(), durationMillis = 500) + }, + "verticalReveal_gesture_dragHalfClose", ) } @@ -146,7 +174,8 @@ class ContentRevealTest { val start = Offset(centerX, 260.dp.toPx()) val end = Offset(centerX, 200.dp.toPx()) swipeWithVelocity(start, end, FlingVelocity.toPx()) - } + }, + "verticalReveal_gesture_flingClose", ) } @@ -164,11 +193,14 @@ class ContentRevealTest { val gestureControl: TouchInjectionScope.() -> Unit, ) : RevealMotion - private fun assertVerticalContainerRevealMotion(testInstructions: RevealMotion) = + private fun assertVerticalContainerRevealMotion( + testInstructions: RevealMotion, + goldenName: String, + ) = motionRule.runTest { val transitions = transitions { from(SceneClosed, to = SceneOpen) { - verticalContainerReveal(RevealElement, MotionSpec, fakeHaptics) + verticalContainerReveal(RevealElement, motionSpec, fakeHaptics) } } @@ -221,9 +253,9 @@ class ContentRevealTest { SceneTransitionLayoutForTesting( state, modifier = - Modifier.padding(50.dp) + Modifier.padding(5.dp) .background(Color.Yellow) - .size(ContainerSize.width, ContainerSize.height + 200.dp) + .size(ContainerSize.width, ContainerSize.height + 100.dp) .testTag("stl"), ) { scene( @@ -241,7 +273,7 @@ class ContentRevealTest { recordingSpec, ) - assertThat(motion).timeSeriesMatchesGolden() + assertThat(motion).timeSeriesMatchesGolden(goldenName) } @Composable @@ -256,7 +288,7 @@ class ContentRevealTest { modifier = Modifier.element(RevealElement) .size(ContainerSize) - .edgeContainerExpansionBackground(Color.DarkGray, MotionSpec) + .verticalExpandContainerBackground(Color.DarkGray, motionSpec) ) } } @@ -266,7 +298,9 @@ class ContentRevealTest { } companion object { - val ContainerSize = DpSize(200.dp, 400.dp) + @get:Parameters @JvmStatic val parameterValues = listOf(true, false) + + val ContainerSize = DpSize(150.dp, 300.dp) val FlingVelocity = 1000.dp // dp/sec @@ -274,6 +308,5 @@ class ContentRevealTest { val SceneOpen = SceneKey("SceneB") val RevealElement = ElementKey("RevealElement") - val MotionSpec = EdgeContainerExpansionSpec() } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 6e29e6932629..2f819d183bcc 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -34,6 +34,7 @@ import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.systemui.animation.GlyphCallback import com.android.systemui.animation.TextAnimator +import com.android.systemui.animation.TextAnimatorListener import com.android.systemui.animation.TypefaceVariantCacheImpl import com.android.systemui.customization.R import com.android.systemui.log.core.LogLevel @@ -100,7 +101,13 @@ constructor( @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb -> val cache = TypefaceVariantCacheImpl(layout.paint.typeface, NUM_CLOCK_FONT_ANIMATION_STEPS) - TextAnimator(layout, cache, invalidateCb) + TextAnimator( + layout, + cache, + object : TextAnimatorListener { + override fun onInvalidate() = invalidateCb() + }, + ) } // Used by screenshot tests to provide stability 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 37acbe261f76..5f71b19fbc3f 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 @@ -22,10 +22,10 @@ import com.android.app.animation.Interpolators import com.android.systemui.log.core.Logger import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockAnimations +import com.android.systemui.plugins.clocks.ClockAxisStyle import com.android.systemui.plugins.clocks.ClockEvents import com.android.systemui.plugins.clocks.ClockFaceConfig import com.android.systemui.plugins.clocks.ClockFaceEvents -import com.android.systemui.plugins.clocks.ClockFontAxisSetting import com.android.systemui.plugins.clocks.ThemeConfig import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData @@ -111,7 +111,7 @@ class ComposedDigitalLayerController(private val clockCtx: ClockContext) : override fun onZenDataChanged(data: ZenData) {} - override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) { + override fun onFontAxesChanged(axes: ClockAxisStyle) { view.updateAxes(axes) } 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 bc4bdf4243cb..3cfa78d17fe7 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 @@ -26,6 +26,7 @@ import com.android.systemui.customization.R import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockAnimations +import com.android.systemui.plugins.clocks.ClockAxisStyle import com.android.systemui.plugins.clocks.ClockConfig import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockEventListener @@ -33,7 +34,6 @@ import com.android.systemui.plugins.clocks.ClockEvents 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.ClockFontAxisSetting import com.android.systemui.plugins.clocks.ClockMessageBuffers import com.android.systemui.plugins.clocks.ClockSettings import com.android.systemui.plugins.clocks.DefaultClockFaceLayout @@ -232,7 +232,7 @@ class DefaultClockController( override fun onZenDataChanged(data: ZenData) {} - override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {} + override fun onFontAxesChanged(axes: ClockAxisStyle) {} } open inner class DefaultClockAnimations( diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index c3935e68ca04..d778bc04ab8e 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -20,6 +20,8 @@ import android.os.Vibrator import android.view.LayoutInflater import com.android.systemui.customization.R import com.android.systemui.log.core.MessageBuffer +import com.android.systemui.plugins.clocks.AxisPresetConfig +import com.android.systemui.plugins.clocks.ClockAxisStyle import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockFontAxis.Companion.merge import com.android.systemui.plugins.clocks.ClockLogger @@ -28,7 +30,7 @@ import com.android.systemui.plugins.clocks.ClockMetadata import com.android.systemui.plugins.clocks.ClockPickerConfig import com.android.systemui.plugins.clocks.ClockProvider import com.android.systemui.plugins.clocks.ClockSettings -import com.android.systemui.shared.clocks.FlexClockController.Companion.AXIS_PRESETS +import com.android.systemui.shared.clocks.FlexClockController.Companion.buildPresetGroup import com.android.systemui.shared.clocks.FlexClockController.Companion.getDefaultAxes private val TAG = DefaultClockProvider::class.simpleName @@ -80,7 +82,7 @@ class DefaultClockProvider( return if (isClockReactiveVariantsEnabled) { val buffers = messageBuffers ?: ClockMessageBuffers(ClockLogger.DEFAULT_MESSAGE_BUFFER) val fontAxes = getDefaultAxes(settings).merge(settings.axes) - val clockSettings = settings.copy(axes = fontAxes.map { it.toSetting() }) + val clockSettings = settings.copy(axes = ClockAxisStyle(fontAxes)) val typefaceCache = TypefaceCache(buffers.infraMessageBuffer, NUM_CLOCK_FONT_ANIMATION_STEPS) { FLEX_TYPEFACE @@ -106,17 +108,35 @@ class DefaultClockProvider( throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG") } - return ClockPickerConfig( - settings.clockId ?: DEFAULT_CLOCK_ID, - resources.getString(R.string.clock_default_name), - resources.getString(R.string.clock_default_description), - resources.getDrawable(R.drawable.clock_default_thumbnail, null), - isReactiveToTone = true, - axes = - if (!isClockReactiveVariantsEnabled) emptyList() - else getDefaultAxes(settings).merge(settings.axes), - axisPresets = if (!isClockReactiveVariantsEnabled) emptyList() else AXIS_PRESETS, - ) + if (!isClockReactiveVariantsEnabled) { + return ClockPickerConfig( + settings.clockId ?: DEFAULT_CLOCK_ID, + resources.getString(R.string.clock_default_name), + resources.getString(R.string.clock_default_description), + resources.getDrawable(R.drawable.clock_default_thumbnail, null), + isReactiveToTone = true, + axes = emptyList(), + presetConfig = null, + ) + } else { + val fontAxes = getDefaultAxes(settings).merge(settings.axes) + return ClockPickerConfig( + settings.clockId ?: DEFAULT_CLOCK_ID, + resources.getString(R.string.clock_default_name), + resources.getString(R.string.clock_default_description), + resources.getDrawable(R.drawable.clock_default_thumbnail, null), + isReactiveToTone = true, + axes = fontAxes, + presetConfig = + AxisPresetConfig( + listOf( + buildPresetGroup(resources, isRound = true), + buildPresetGroup(resources, isRound = false), + ) + ) + .let { cfg -> cfg.copy(current = cfg.findStyle(ClockAxisStyle(fontAxes))) }, + ) + } } companion object { 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 5acd4468fe92..96c3ac75587e 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 @@ -16,20 +16,24 @@ package com.android.systemui.shared.clocks +import android.content.res.Resources import com.android.systemui.animation.GSFAxes import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.AlarmData +import com.android.systemui.plugins.clocks.AxisPresetConfig import com.android.systemui.plugins.clocks.AxisType +import com.android.systemui.plugins.clocks.ClockAxisStyle import com.android.systemui.plugins.clocks.ClockConfig import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockEventListener import com.android.systemui.plugins.clocks.ClockEvents import com.android.systemui.plugins.clocks.ClockFontAxis import com.android.systemui.plugins.clocks.ClockFontAxis.Companion.merge -import com.android.systemui.plugins.clocks.ClockFontAxisSetting import com.android.systemui.plugins.clocks.ClockSettings import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData +import com.android.systemui.shared.clocks.FontUtils.put +import com.android.systemui.shared.clocks.FontUtils.toClockAxis import com.android.systemui.shared.clocks.view.FlexClockView import java.io.PrintWriter import java.util.Locale @@ -96,8 +100,8 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController largeClock.events.onZenDataChanged(data) } - override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) { - val fontAxes = getDefaultAxes(clockCtx.settings).merge(axes).map { it.toSetting() } + override fun onFontAxesChanged(axes: ClockAxisStyle) { + val fontAxes = ClockAxisStyle(getDefaultAxes(clockCtx.settings).merge(axes)) smallClock.events.onFontAxesChanged(fontAxes) largeClock.events.onFontAxesChanged(fontAxes) } @@ -162,66 +166,46 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController ), ) - private val LEGACY_FLEX_SETTINGS = - listOf( - GSFAxes.WEIGHT.toClockAxisSetting(600f), - GSFAxes.WIDTH.toClockAxisSetting(100f), - GSFAxes.ROUND.toClockAxisSetting(100f), - GSFAxes.SLANT.toClockAxisSetting(0f), - ) + private val LEGACY_FLEX_SETTINGS = ClockAxisStyle { + put(GSFAxes.WEIGHT, 600f) + put(GSFAxes.WIDTH, 100f) + put(GSFAxes.ROUND, 100f) + put(GSFAxes.SLANT, 0f) + } - val AXIS_PRESETS = - listOf( - FONT_AXES.map { it.toSetting() }, - LEGACY_FLEX_SETTINGS, - listOf( // Porcelain - GSFAxes.WEIGHT.toClockAxisSetting(500f), - GSFAxes.WIDTH.toClockAxisSetting(100f), - GSFAxes.ROUND.toClockAxisSetting(0f), - GSFAxes.SLANT.toClockAxisSetting(0f), - ), - listOf( // Midnight - GSFAxes.WEIGHT.toClockAxisSetting(300f), - GSFAxes.WIDTH.toClockAxisSetting(100f), - GSFAxes.ROUND.toClockAxisSetting(100f), - GSFAxes.SLANT.toClockAxisSetting(-10f), - ), - listOf( // Sterling - GSFAxes.WEIGHT.toClockAxisSetting(1000f), - GSFAxes.WIDTH.toClockAxisSetting(100f), - GSFAxes.ROUND.toClockAxisSetting(0f), - GSFAxes.SLANT.toClockAxisSetting(0f), - ), - listOf( // Smoky Green - GSFAxes.WEIGHT.toClockAxisSetting(150f), - GSFAxes.WIDTH.toClockAxisSetting(50f), - GSFAxes.ROUND.toClockAxisSetting(0f), - GSFAxes.SLANT.toClockAxisSetting(0f), - ), - listOf( // Iris - GSFAxes.WEIGHT.toClockAxisSetting(500f), - GSFAxes.WIDTH.toClockAxisSetting(100f), - GSFAxes.ROUND.toClockAxisSetting(100f), - GSFAxes.SLANT.toClockAxisSetting(0f), - ), - listOf( // Margarita - GSFAxes.WEIGHT.toClockAxisSetting(300f), - GSFAxes.WIDTH.toClockAxisSetting(30f), - GSFAxes.ROUND.toClockAxisSetting(100f), - GSFAxes.SLANT.toClockAxisSetting(-10f), - ), - listOf( // Raspberry - GSFAxes.WEIGHT.toClockAxisSetting(700f), - GSFAxes.WIDTH.toClockAxisSetting(140f), - GSFAxes.ROUND.toClockAxisSetting(100f), - GSFAxes.SLANT.toClockAxisSetting(-7f), - ), - listOf( // Ultra Blue - GSFAxes.WEIGHT.toClockAxisSetting(850f), - GSFAxes.WIDTH.toClockAxisSetting(130f), - GSFAxes.ROUND.toClockAxisSetting(0f), - GSFAxes.SLANT.toClockAxisSetting(0f), - ), + private val PRESET_COUNT = 8 + private val PRESET_WIDTH_INIT = 30f + private val PRESET_WIDTH_STEP = 12.5f + private val PRESET_WEIGHT_INIT = 800f + private val PRESET_WEIGHT_STEP = -100f + private val BASE_PRESETS: List<ClockAxisStyle> = run { + val presets = mutableListOf<ClockAxisStyle>() + var weight = PRESET_WEIGHT_INIT + var width = PRESET_WIDTH_INIT + for (i in 1..PRESET_COUNT) { + presets.add( + ClockAxisStyle { + put(GSFAxes.WEIGHT, weight) + put(GSFAxes.WIDTH, width) + put(GSFAxes.ROUND, 0f) + put(GSFAxes.SLANT, 0f) + } + ) + + weight += PRESET_WEIGHT_STEP + width += PRESET_WIDTH_STEP + } + + return@run presets + } + + fun buildPresetGroup(resources: Resources, isRound: Boolean): AxisPresetConfig.Group { + val round = if (isRound) GSFAxes.ROUND.maxValue else GSFAxes.ROUND.minValue + return AxisPresetConfig.Group( + presets = BASE_PRESETS.map { it.copy { put(GSFAxes.ROUND, round) } }, + // TODO(b/395647577): Placeholder Icon; Replace or remove + icon = resources.getDrawable(R.drawable.clock_default_thumbnail, null), ) + } } } 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 578a489c68c6..171a68f72e20 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 @@ -25,16 +25,18 @@ import com.android.systemui.animation.GSFAxes import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockAnimations +import com.android.systemui.plugins.clocks.ClockAxisStyle import com.android.systemui.plugins.clocks.ClockEvents 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.ClockFontAxisSetting 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.FontUtils.get +import com.android.systemui.shared.clocks.FontUtils.set import com.android.systemui.shared.clocks.ViewUtils.computeLayoutDiff import com.android.systemui.shared.clocks.view.FlexClockView import com.android.systemui.shared.clocks.view.HorizontalAlignment @@ -129,17 +131,10 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: layerController.faceEvents.onThemeChanged(theme) } - override fun onFontAxesChanged(settings: List<ClockFontAxisSetting>) { - var axes = settings - if (!isLargeClock) { - axes = - axes.map { axis -> - if (axis.key == GSFAxes.WIDTH.tag && axis.value > SMALL_CLOCK_MAX_WDTH) { - axis.copy(value = SMALL_CLOCK_MAX_WDTH) - } else { - axis - } - } + 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) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt index 212b1e29d1b8..722d76beedbb 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt @@ -18,26 +18,36 @@ package com.android.systemui.shared.clocks import com.android.systemui.animation.AxisDefinition import com.android.systemui.plugins.clocks.AxisType +import com.android.systemui.plugins.clocks.ClockAxisStyle import com.android.systemui.plugins.clocks.ClockFontAxis -import com.android.systemui.plugins.clocks.ClockFontAxisSetting -fun AxisDefinition.toClockAxis( - type: AxisType, - currentValue: Float? = null, - name: String, - description: String, -): ClockFontAxis { - return ClockFontAxis( - key = this.tag, - type = type, - maxValue = this.maxValue, - minValue = this.minValue, - currentValue = currentValue ?: this.defaultValue, - name = name, - description = description, - ) -} +object FontUtils { + fun AxisDefinition.toClockAxis( + type: AxisType, + currentValue: Float? = null, + name: String, + description: String, + ): ClockFontAxis { + return ClockFontAxis( + key = this.tag, + type = type, + maxValue = this.maxValue, + minValue = this.minValue, + currentValue = currentValue ?: this.defaultValue, + name = name, + description = description, + ) + } + + fun ClockAxisStyle.put(def: AxisDefinition, value: Float? = null) { + this.put(def.tag, value ?: def.defaultValue) + } + + operator fun ClockAxisStyle.set(def: AxisDefinition, value: Float) { + this[def.tag] = value + } -fun AxisDefinition.toClockAxisSetting(value: Float? = null): ClockFontAxisSetting { - return ClockFontAxisSetting(this.tag, value ?: this.defaultValue) + operator fun ClockAxisStyle.get(def: AxisDefinition): Float { + return this[def.tag] ?: def.defaultValue + } } 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 1d963af3ad22..7be9a936cbd3 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 @@ -26,10 +26,10 @@ import com.android.systemui.customization.R import com.android.systemui.log.core.Logger import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockAnimations +import com.android.systemui.plugins.clocks.ClockAxisStyle import com.android.systemui.plugins.clocks.ClockEvents import com.android.systemui.plugins.clocks.ClockFaceConfig import com.android.systemui.plugins.clocks.ClockFaceEvents -import com.android.systemui.plugins.clocks.ClockFontAxisSetting import com.android.systemui.plugins.clocks.ThemeConfig import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData @@ -172,7 +172,7 @@ open class SimpleDigitalHandLayerController( override fun onZenDataChanged(data: ZenData) {} - override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) { + override fun onFontAxesChanged(axes: ClockAxisStyle) { view.updateAxes(axes) } } 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 ba32ab083063..4531aed0e83d 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 @@ -26,7 +26,7 @@ import androidx.annotation.VisibleForTesting import androidx.core.view.children import com.android.app.animation.Interpolators import com.android.systemui.customization.R -import com.android.systemui.plugins.clocks.ClockFontAxisSetting +import com.android.systemui.plugins.clocks.ClockAxisStyle import com.android.systemui.plugins.clocks.ClockLogger import com.android.systemui.plugins.clocks.VPoint import com.android.systemui.plugins.clocks.VPointF @@ -272,7 +272,7 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) { invalidate() } - fun updateAxes(axes: List<ClockFontAxisSetting>) { + fun updateAxes(axes: ClockAxisStyle) { childViews.forEach { view -> view.updateAxes(axes) } 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 2af25fe339a2..377a24c2899b 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 @@ -24,7 +24,6 @@ import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.Rect import android.os.VibrationEffect -import android.text.Layout import android.text.TextPaint import android.util.AttributeSet import android.util.Log @@ -37,12 +36,12 @@ import android.view.animation.PathInterpolator import android.widget.TextView import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.animation.AxisDefinition import com.android.systemui.animation.GSFAxes import com.android.systemui.animation.TextAnimator +import com.android.systemui.animation.TextAnimatorListener import com.android.systemui.customization.R -import com.android.systemui.plugins.clocks.ClockFontAxisSetting -import com.android.systemui.plugins.clocks.ClockFontAxisSetting.Companion.replace -import com.android.systemui.plugins.clocks.ClockFontAxisSetting.Companion.toFVar +import com.android.systemui.plugins.clocks.ClockAxisStyle import com.android.systemui.plugins.clocks.ClockLogger import com.android.systemui.plugins.clocks.VPoint import com.android.systemui.plugins.clocks.VPointF @@ -56,9 +55,9 @@ import com.android.systemui.shared.clocks.DigitTranslateAnimator import com.android.systemui.shared.clocks.DimensionParser import com.android.systemui.shared.clocks.FLEX_CLOCK_ID import com.android.systemui.shared.clocks.FontTextStyle +import com.android.systemui.shared.clocks.FontUtils.set import com.android.systemui.shared.clocks.ViewUtils.measuredSize import com.android.systemui.shared.clocks.ViewUtils.size -import com.android.systemui.shared.clocks.toClockAxisSetting import java.lang.Thread import kotlin.math.max import kotlin.math.min @@ -123,9 +122,9 @@ open class SimpleDigitalClockTextView( private val isLegacyFlex = clockCtx.settings.clockId == FLEX_CLOCK_ID private val fixedAodAxes = when { - !isLegacyFlex -> listOf(AOD_WEIGHT_AXIS, WIDTH_AXIS) - isLargeClock -> listOf(FLEX_AOD_LARGE_WEIGHT_AXIS, FLEX_AOD_WIDTH_AXIS) - else -> listOf(FLEX_AOD_SMALL_WEIGHT_AXIS, FLEX_AOD_WIDTH_AXIS) + !isLegacyFlex -> fromAxes(AOD_WEIGHT_AXIS, WIDTH_AXIS) + isLargeClock -> fromAxes(FLEX_AOD_LARGE_WEIGHT_AXIS, FLEX_AOD_WIDTH_AXIS) + else -> fromAxes(FLEX_AOD_SMALL_WEIGHT_AXIS, FLEX_AOD_WIDTH_AXIS) } private var lsFontVariation: String @@ -135,11 +134,11 @@ open class SimpleDigitalClockTextView( init { val roundAxis = if (!isLegacyFlex) ROUND_AXIS else FLEX_ROUND_AXIS val lsFontAxes = - if (!isLegacyFlex) listOf(LS_WEIGHT_AXIS, WIDTH_AXIS, ROUND_AXIS, SLANT_AXIS) - else listOf(FLEX_LS_WEIGHT_AXIS, FLEX_LS_WIDTH_AXIS, FLEX_ROUND_AXIS, SLANT_AXIS) + if (!isLegacyFlex) fromAxes(LS_WEIGHT_AXIS, WIDTH_AXIS, ROUND_AXIS, SLANT_AXIS) + else fromAxes(FLEX_LS_WEIGHT_AXIS, FLEX_LS_WIDTH_AXIS, FLEX_ROUND_AXIS, SLANT_AXIS) lsFontVariation = lsFontAxes.toFVar() - aodFontVariation = (fixedAodAxes + listOf(roundAxis, SLANT_AXIS)).toFVar() + aodFontVariation = fixedAodAxes.copyWith(fromAxes(roundAxis, SLANT_AXIS)).toFVar() fidgetFontVariation = buildFidgetVariation(lsFontAxes).toFVar() } @@ -175,11 +174,6 @@ open class SimpleDigitalClockTextView( private val typefaceCache = clockCtx.typefaceCache.getVariantCache("") - @VisibleForTesting - var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb -> - TextAnimator(layout, typefaceCache, invalidateCb) - } - var verticalAlignment: VerticalAlignment = VerticalAlignment.BASELINE var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.CENTER @@ -206,9 +200,9 @@ open class SimpleDigitalClockTextView( invalidate() } - fun updateAxes(lsAxes: List<ClockFontAxisSetting>) { + fun updateAxes(lsAxes: ClockAxisStyle) { lsFontVariation = lsAxes.toFVar() - aodFontVariation = lsAxes.replace(fixedAodAxes).toFVar() + aodFontVariation = lsAxes.copyWith(fixedAodAxes).toFVar() fidgetFontVariation = buildFidgetVariation(lsAxes).toFVar() logger.updateAxes(lsFontVariation, aodFontVariation) @@ -225,19 +219,16 @@ open class SimpleDigitalClockTextView( invalidate() } - fun buildFidgetVariation(axes: List<ClockFontAxisSetting>): List<ClockFontAxisSetting> { - val result = mutableListOf<ClockFontAxisSetting>() - for (axis in axes) { - result.add( - FIDGET_DISTS.get(axis.key)?.let { (dist, midpoint) -> - ClockFontAxisSetting( - axis.key, - axis.value + dist * if (axis.value > midpoint) -1 else 1, - ) - } ?: axis - ) - } - return result + fun buildFidgetVariation(axes: ClockAxisStyle): ClockAxisStyle { + return ClockAxisStyle( + axes.items + .map { (key, value) -> + FIDGET_DISTS.get(key)?.let { (dist, midpoint) -> + key to value + dist * if (value > midpoint) -1 else 1 + } ?: (key to value) + } + .toMap() + ) } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { @@ -247,7 +238,18 @@ open class SimpleDigitalClockTextView( val layout = this.layout if (layout != null) { if (!this::textAnimator.isInitialized) { - textAnimator = textAnimatorFactory(layout, ::invalidate) + textAnimator = + TextAnimator( + layout, + typefaceCache, + object : TextAnimatorListener { + override fun onInvalidate() = invalidate() + + override fun onRebased() = updateTextBounds() + + override fun onPaintModified() = updateTextBounds() + }, + ) setInterpolatorPaint() } else { textAnimator.updateLayout(layout) @@ -272,7 +274,7 @@ open class SimpleDigitalClockTextView( override fun onDraw(canvas: Canvas) { logger.onDraw(textAnimator.textInterpolator.shapedText) - val interpProgress = getInterpolatedProgress() + val interpProgress = textAnimator.progress val interpBounds = getInterpolatedTextBounds(interpProgress) if (interpProgress != drawnProgress) { drawnProgress = interpProgress @@ -336,7 +338,6 @@ open class SimpleDigitalClockTextView( interpolator = aodDozingInterpolator, ), ) - updateTextBoundsForTextAnimator() if (!isAnimated) { requestLayout() @@ -367,11 +368,9 @@ open class SimpleDigitalClockTextView( duration = CHARGE_ANIMATION_DURATION, ), ) - updateTextBoundsForTextAnimator() }, ), ) - updateTextBoundsForTextAnimator() } fun animateFidget(x: Float, y: Float) = animateFidget(0L) @@ -401,11 +400,9 @@ open class SimpleDigitalClockTextView( interpolator = FIDGET_INTERPOLATOR, ), ) - updateTextBoundsForTextAnimator() }, ), ) - updateTextBoundsForTextAnimator() } fun refreshText() { @@ -428,12 +425,8 @@ open class SimpleDigitalClockTextView( id == R.id.MINUTE_SECOND_DIGIT } - private fun getInterpolatedProgress(): Float { - return textAnimator.animator?.let { it.animatedValue as Float } ?: 1f - } - /** Returns the interpolated text bounding rect based on interpolation progress */ - private fun getInterpolatedTextBounds(progress: Float = getInterpolatedProgress()): VRectF { + private fun getInterpolatedTextBounds(progress: Float = textAnimator.progress): VRectF { if (progress <= 0f) { return prevTextBounds } else if (!textAnimator.isRunning || progress >= 1f) { @@ -487,6 +480,15 @@ open class SimpleDigitalClockTextView( MeasureSpec.makeMeasureSpec(measureBounds.x.roundToInt(), mode.x), MeasureSpec.makeMeasureSpec(measureBounds.y.roundToInt(), mode.y), ) + + logger.d({ + val size = VPointF.fromLong(long1) + val mode = VPoint.fromLong(long2) + "setInterpolatedSize(size=$size, mode=$mode)" + }) { + long1 = measureBounds.toLong() + long2 = mode.toLong() + } } /** Set the location of the view to match the interpolated text bounds */ @@ -514,6 +516,9 @@ open class SimpleDigitalClockTextView( targetRect.bottom.roundToInt(), ) onViewBoundsChanged?.let { it(targetRect) } + logger.d({ "setInterpolatedLocation(${VRectF.fromLong(long1)})" }) { + long1 = targetRect.toLong() + } return targetRect } @@ -616,7 +621,8 @@ open class SimpleDigitalClockTextView( * rebase if previous animator is canceled so basePaint will store the state we transition from * and targetPaint will store the state we transition to */ - private fun updateTextBoundsForTextAnimator() { + private fun updateTextBounds() { + drawnProgress = null prevTextBounds = textAnimator.textInterpolator.basePaint.getTextBounds(text) targetTextBounds = textAnimator.textInterpolator.targetPaint.getTextBounds(text) } @@ -656,18 +662,22 @@ open class SimpleDigitalClockTextView( ) val AOD_COLOR = Color.WHITE - val LS_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(400f) - val AOD_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(200f) - val WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(85f) - val ROUND_AXIS = GSFAxes.ROUND.toClockAxisSetting(0f) - val SLANT_AXIS = GSFAxes.SLANT.toClockAxisSetting(0f) + private val LS_WEIGHT_AXIS = GSFAxes.WEIGHT to 400f + private val AOD_WEIGHT_AXIS = GSFAxes.WEIGHT to 200f + private val WIDTH_AXIS = GSFAxes.WIDTH to 85f + private val ROUND_AXIS = GSFAxes.ROUND to 0f + private val SLANT_AXIS = GSFAxes.SLANT to 0f // Axes for Legacy version of the Flex Clock - val FLEX_LS_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(600f) - val FLEX_AOD_LARGE_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(74f) - val FLEX_AOD_SMALL_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(133f) - val FLEX_LS_WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(100f) - val FLEX_AOD_WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(43f) - val FLEX_ROUND_AXIS = GSFAxes.ROUND.toClockAxisSetting(100f) + private val FLEX_LS_WEIGHT_AXIS = GSFAxes.WEIGHT to 600f + private val FLEX_AOD_LARGE_WEIGHT_AXIS = GSFAxes.WEIGHT to 74f + private val FLEX_AOD_SMALL_WEIGHT_AXIS = GSFAxes.WEIGHT to 133f + private val FLEX_LS_WIDTH_AXIS = GSFAxes.WIDTH to 100f + private val FLEX_AOD_WIDTH_AXIS = GSFAxes.WIDTH to 43f + private val FLEX_ROUND_AXIS = GSFAxes.ROUND to 100f + + private fun fromAxes(vararg axes: Pair<AxisDefinition, Float>): ClockAxisStyle { + return ClockAxisStyle(axes.map { (def, value) -> def.tag to value }.toMap()) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/ActiveUnlockConfigTest.kt index 14d34d79512f..162218d8f071 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/ActiveUnlockConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/ActiveUnlockConfigTest.kt @@ -87,7 +87,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { contentResolver, selectedUserInteractor, lazyKeyguardUpdateMonitor, - dumpManager + dumpManager, ) } @@ -116,9 +116,9 @@ class ActiveUnlockConfigTest : SysuiTestCase() { ) ) assertFalse( - activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT_LEGACY - ) + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT_LEGACY + ) ) assertTrue( activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -212,7 +212,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { secureSettings.putStringForUser( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "", - currentUser + currentUser, ) updateSetting( secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED) @@ -285,7 +285,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" + "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}", - currentUser + currentUser, ) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)) @@ -328,7 +328,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { secureSettings.putStringForUser( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "${ActiveUnlockConfig.BiometricType.NONE.intValue}", - currentUser + currentUser, ) updateSetting( secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED) @@ -358,7 +358,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" + "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}", - currentUser + currentUser, ) updateSetting( secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED) @@ -397,10 +397,10 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Test fun isWakeupConsideredUnlockIntent_singleValue() { // GIVEN lift is considered an unlock intent - secureSettings.putIntForUser( + secureSettings.putStringForUser( ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, - PowerManager.WAKE_REASON_LIFT, - currentUser + PowerManager.WAKE_REASON_LIFT.toString(), + currentUser, ) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) @@ -422,7 +422,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { PowerManager.WAKE_REASON_LIFT.toString() + "|" + PowerManager.WAKE_REASON_TAP.toString(), - currentUser + currentUser, ) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) @@ -452,7 +452,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { secureSettings.putStringForUser( ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, " ", - currentUser + currentUser, ) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) @@ -479,7 +479,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { secureSettings.putStringForUser( ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD, PowerManager.WAKE_REASON_LIFT.toString(), - currentUser + currentUser, ) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD)) @@ -501,7 +501,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { secureSettings.putStringForUser( ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD, " ", - currentUser + currentUser, ) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD)) @@ -521,7 +521,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { PowerManager.WAKE_REASON_LIFT.toString() + "|" + PowerManager.WAKE_REASON_TAP.toString(), - currentUser + currentUser, ) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD)) @@ -544,7 +544,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { secureSettings.putStringForUser( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "-1", - currentUser + currentUser, ) // WHEN the setting updates @@ -581,7 +581,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { eq(uri), eq(false), capture(settingsObserverCaptor), - eq(UserHandle.USER_ALL) + eq(UserHandle.USER_ALL), ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt index 245388c214a5..b0db8b70d296 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt @@ -22,12 +22,12 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.ui.view.KeyguardRootView -import com.android.systemui.kosmos.Kosmos import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.StatusBarState.SHADE +import com.android.systemui.testKosmos import com.android.systemui.unfold.FakeUnfoldTransitionProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider @@ -47,16 +47,14 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class KeyguardUnfoldTransitionTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val progressProvider: FakeUnfoldTransitionProvider = kosmos.fakeUnfoldTransitionProgressProvider - @Mock - private lateinit var keyguardRootView: KeyguardRootView + @Mock private lateinit var keyguardRootView: KeyguardRootView - @Mock - private lateinit var notificationShadeWindowView: NotificationShadeWindowView + @Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView @Mock private lateinit var statusBarStateController: StatusBarStateController @@ -71,10 +69,14 @@ class KeyguardUnfoldTransitionTest : SysuiTestCase() { xTranslationMax = context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat() - underTest = KeyguardUnfoldTransition( - context, keyguardRootView, notificationShadeWindowView, - statusBarStateController, progressProvider - ) + underTest = + KeyguardUnfoldTransition( + context, + keyguardRootView, + notificationShadeWindowView, + statusBarStateController, + progressProvider, + ) underTest.setup() underTest.statusViewCentered = false @@ -88,9 +90,8 @@ class KeyguardUnfoldTransitionTest : SysuiTestCase() { underTest.statusViewCentered = true val view = View(context) - whenever(keyguardRootView.findViewById<View>(customR.id.lockscreen_clock_view_large)).thenReturn( - view - ) + whenever(keyguardRootView.findViewById<View>(customR.id.lockscreen_clock_view_large)) + .thenReturn(view) progressListener.onTransitionStarted() assertThat(view.translationX).isZero() @@ -110,9 +111,8 @@ class KeyguardUnfoldTransitionTest : SysuiTestCase() { whenever(statusBarStateController.getState()).thenReturn(SHADE) val view = View(context) - whenever(keyguardRootView.findViewById<View>(customR.id.lockscreen_clock_view_large)).thenReturn( - view - ) + whenever(keyguardRootView.findViewById<View>(customR.id.lockscreen_clock_view_large)) + .thenReturn(view) progressListener.onTransitionStarted() assertThat(view.translationX).isZero() @@ -133,9 +133,11 @@ class KeyguardUnfoldTransitionTest : SysuiTestCase() { val view = View(context) whenever( - notificationShadeWindowView - .findViewById<View>(customR.id.lockscreen_clock_view_large) - ).thenReturn(view) + notificationShadeWindowView.findViewById<View>( + customR.id.lockscreen_clock_view_large + ) + ) + .thenReturn(view) progressListener.onTransitionStarted() assertThat(view.translationX).isZero() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt index cde42bd00ba5..76606230a124 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt @@ -23,7 +23,6 @@ import androidx.test.filters.SmallTest import com.android.internal.accessibility.AccessibilityShortcutController import com.android.internal.accessibility.common.ShortcutConstants import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope @@ -31,6 +30,7 @@ import com.android.systemui.model.SysUiState import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.testKosmos import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -58,7 +58,7 @@ class ExtraDimDialogDelegateTest : SysuiTestCase() { private lateinit var extraDimDialogDelegate: ExtraDimDialogDelegate - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().also { it.testCase = this } private val testScope = kosmos.testScope @Mock private lateinit var dialog: SystemUIDialog @@ -79,7 +79,7 @@ class ExtraDimDialogDelegateTest : SysuiTestCase() { kosmos.testDispatcher, dialogFactory, accessibilityManager, - userTracker + userTracker, ) } @@ -94,7 +94,7 @@ class ExtraDimDialogDelegateTest : SysuiTestCase() { verify(dialog) .setPositiveButton( eq(R.string.accessibility_deprecate_extra_dim_dialog_button), - clickListener.capture() + clickListener.capture(), ) clickListener.firstValue.onClick(dialog, 0) @@ -110,7 +110,7 @@ class ExtraDimDialogDelegateTest : SysuiTestCase() { .flattenToString() ) ), - anyInt() + anyInt(), ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java index 7e4704a6179a..d118ace08b85 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java @@ -141,6 +141,10 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { @Mock private QSSettingsPackageRepository mQSSettingsPackageRepository; @Mock + private HearingDevicesInputRoutingController.Factory mInputRoutingFactory; + @Mock + private HearingDevicesInputRoutingController mInputRoutingController; + @Mock private CachedBluetoothDevice mCachedDevice; @Mock private BluetoothDevice mDevice; @@ -184,6 +188,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { when(mCachedDevice.getBondState()).thenReturn(BOND_BONDED); when(mCachedDevice.getDeviceSide()).thenReturn(SIDE_LEFT); when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice); + when(mInputRoutingFactory.create(any())).thenReturn(mInputRoutingController); mContext.setMockPackageManager(mPackageManager); } @@ -349,6 +354,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { setUpDeviceDialogWithoutPairNewDeviceButton(); mDialog.show(); + mExecutor.runAllReady(); ViewGroup ambientLayout = getAmbientLayout(mDialog); assertThat(ambientLayout.getVisibility()).isEqualTo(View.VISIBLE); @@ -401,7 +407,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { mExecutor, mAudioManager, mUiEventLogger, - mQSSettingsPackageRepository + mQSSettingsPackageRepository, + mInputRoutingFactory ); mDialog = mDialogDelegate.createDialog(); } @@ -438,7 +445,6 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { return dialog.requireViewById(R.id.ambient_layout); } - private int countChildWithoutSpace(ViewGroup viewGroup) { int spaceCount = 0; for (int i = 0; i < viewGroup.getChildCount(); i++) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingControllerTest.kt new file mode 100644 index 000000000000..0a41b771a335 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingControllerTest.kt @@ -0,0 +1,201 @@ +/* + * 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.accessibility.hearingaid + +import android.media.AudioDeviceInfo +import android.media.AudioManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.HapClientProfile +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.hearingaid.HearingDevicesInputRoutingController.InputRoutingControlAvailableCallback +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub +import org.mockito.kotlin.verify + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class HearingDevicesInputRoutingControllerTest : SysuiTestCase() { + + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private var hapClientProfile: HapClientProfile = mock() + private var cachedDevice: CachedBluetoothDevice = mock() + private var memberCachedDevice: CachedBluetoothDevice = mock() + private var btDevice: android.bluetooth.BluetoothDevice = mock() + private var audioManager: AudioManager = mock() + private lateinit var underTest: HearingDevicesInputRoutingController + private val testDispatcher = kosmos.testDispatcher + + @Before + fun setUp() { + hapClientProfile.stub { on { isProfileReady } doReturn true } + cachedDevice.stub { + on { device } doReturn btDevice + on { profiles } doReturn listOf(hapClientProfile) + } + memberCachedDevice.stub { + on { device } doReturn btDevice + on { profiles } doReturn listOf(hapClientProfile) + } + + underTest = HearingDevicesInputRoutingController(mContext, audioManager, testDispatcher) + underTest.setDevice(cachedDevice) + } + + @Test + fun isInputRoutingControlAvailable_validInput_supportHapProfile_returnTrue() { + testScope.runTest { + val mockInfoAddress = arrayOf(mockTestAddressInfo(TEST_ADDRESS)) + cachedDevice.stub { + on { address } doReturn TEST_ADDRESS + on { profiles } doReturn listOf(hapClientProfile) + } + audioManager.stub { + on { getDevices(AudioManager.GET_DEVICES_INPUTS) } doReturn mockInfoAddress + } + + var result: Boolean? = null + underTest.isInputRoutingControlAvailable( + object : InputRoutingControlAvailableCallback { + override fun onResult(available: Boolean) { + result = available + } + } + ) + + runCurrent() + assertThat(result).isTrue() + } + } + + @Test + fun isInputRoutingControlAvailable_notSupportHapProfile_returnFalse() { + testScope.runTest { + val mockInfoAddress = arrayOf(mockTestAddressInfo(TEST_ADDRESS)) + cachedDevice.stub { + on { address } doReturn TEST_ADDRESS + on { profiles } doReturn emptyList() + } + audioManager.stub { + on { getDevices(AudioManager.GET_DEVICES_INPUTS) } doReturn mockInfoAddress + } + + var result: Boolean? = null + underTest.isInputRoutingControlAvailable( + object : InputRoutingControlAvailableCallback { + override fun onResult(available: Boolean) { + result = available + } + } + ) + + runCurrent() + assertThat(result).isFalse() + } + } + + @Test + fun isInputRoutingControlAvailable_validInputMember_supportHapProfile_returnTrue() { + testScope.runTest { + val mockInfoAddress2 = arrayOf(mockTestAddressInfo(TEST_ADDRESS_2)) + cachedDevice.stub { + on { address } doReturn TEST_ADDRESS + on { profiles } doReturn listOf(hapClientProfile) + on { memberDevice } doReturn (setOf(memberCachedDevice)) + } + memberCachedDevice.stub { on { address } doReturn TEST_ADDRESS_2 } + audioManager.stub { + on { getDevices(AudioManager.GET_DEVICES_INPUTS) } doReturn mockInfoAddress2 + } + + var result: Boolean? = null + underTest.isInputRoutingControlAvailable( + object : InputRoutingControlAvailableCallback { + override fun onResult(available: Boolean) { + result = available + } + } + ) + + runCurrent() + assertThat(result).isTrue() + } + } + + @Test + fun isAvailable_notValidInputDevice_returnFalse() { + testScope.runTest { + cachedDevice.stub { + on { address } doReturn TEST_ADDRESS + on { profiles } doReturn listOf(hapClientProfile) + } + audioManager.stub { + on { getDevices(AudioManager.GET_DEVICES_INPUTS) } doReturn emptyArray() + } + + var result: Boolean? = null + underTest.isInputRoutingControlAvailable( + object : InputRoutingControlAvailableCallback { + override fun onResult(available: Boolean) { + result = available + } + } + ) + + runCurrent() + assertThat(result).isFalse() + } + } + + @Test + fun selectInputRouting_builtinMic_setMicrophonePreferredForCallsFalse() { + underTest.selectInputRouting( + HearingDevicesInputRoutingController.InputRoutingValue.BUILTIN_MIC.ordinal + ) + + verify(btDevice).isMicrophonePreferredForCalls = false + } + + private fun mockTestAddressInfo(address: String): AudioDeviceInfo { + val info: AudioDeviceInfo = mock() + info.stub { + on { type } doReturn AudioDeviceInfo.TYPE_BLE_HEADSET + on { this.address } doReturn address + } + + return info + } + + companion object { + private const val TEST_ADDRESS = "55:66:77:88:99:AA" + private const val TEST_ADDRESS_2 = "55:66:77:88:99:BB" + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt index 8c5fad3906ed..85733124aedb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt @@ -32,7 +32,6 @@ import com.android.internal.statusbar.IStatusBarService import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest @@ -50,6 +49,7 @@ import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -74,7 +74,7 @@ import org.mockito.junit.MockitoJUnit @SmallTest @RunWith(AndroidJUnit4::class) class BackActionInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val executor = FakeExecutor(FakeSystemClock()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt index 648d74d20cc5..29a0b6922b2f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt @@ -22,8 +22,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent @@ -34,7 +34,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class CameraAutoRotateRepositoryImplTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val settings = kosmos.fakeSettings private val testUser = UserHandle.of(1) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt index b73a212c9bd1..2e357d8a8652 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt @@ -22,8 +22,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent @@ -38,7 +38,7 @@ import org.mockito.Mockito @RunWith(AndroidJUnit4::class) @android.platform.test.annotations.EnabledOnRavenwood class CameraSensorPrivacyRepositoryImplTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val testUser = UserHandle.of(1) private val privacyManager = mock<SensorPrivacyManager>() @@ -46,7 +46,7 @@ class CameraSensorPrivacyRepositoryImplTest : SysuiTestCase() { CameraSensorPrivacyRepositoryImpl( testScope.testScheduler, testScope.backgroundScope, - privacyManager + privacyManager, ) @Test @@ -87,7 +87,7 @@ class CameraSensorPrivacyRepositoryImplTest : SysuiTestCase() { .addSensorPrivacyListener( ArgumentMatchers.eq(SensorPrivacyManager.Sensors.CAMERA), ArgumentMatchers.eq(testUser.identifier), - captor.capture() + captor.capture(), ) val sensorPrivacyCallback = captor.value!! diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt index 6c8097ed7166..b3d898396497 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt @@ -21,7 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues -import com.android.systemui.kosmos.Kosmos +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -32,7 +32,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @android.platform.test.annotations.EnabledOnRavenwood class FakeCameraAutoRotateRepositoryTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val underTest = kosmos.fakeCameraAutoRotateRepository private val testUser = UserHandle.of(1) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt index 7161c2c13a60..6b9a7de2e781 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt @@ -21,7 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues -import com.android.systemui.kosmos.Kosmos +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -32,7 +32,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @android.platform.test.annotations.EnabledOnRavenwood class FakeCameraSensorPrivacyRepositoryTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val underTest = kosmos.fakeCameraSensorPrivacyRepository private val testUser = UserHandle.of(1) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt index 239e02640908..652a2ff21e9b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt @@ -25,6 +25,10 @@ import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase import com.android.systemui.res.R +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test @@ -33,7 +37,11 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ActionIntentCreatorTest : SysuiTestCase() { - val creator = ActionIntentCreator() + private val scheduler = TestCoroutineScheduler() + private val mainDispatcher = UnconfinedTestDispatcher(scheduler) + private val testScope = TestScope(mainDispatcher) + + val creator = ActionIntentCreator(testScope.backgroundScope) @Test fun test_getTextEditorIntent() { @@ -65,7 +73,7 @@ class ActionIntentCreatorTest : SysuiTestCase() { } @Test - fun test_getImageEditIntent() { + fun test_getImageEditIntent() = runTest { context.getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, "") val fakeUri = Uri.parse("content://foo") var intent = creator.getImageEditIntent(fakeUri, context) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java index 126b3fa9e7ca..10c5c52d21a7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java @@ -34,6 +34,8 @@ import com.android.systemui.res.R; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.atomic.AtomicReference; + @SmallTest @RunWith(AndroidJUnit4.class) public class DefaultIntentCreatorTest extends SysuiTestCase { @@ -73,12 +75,16 @@ public class DefaultIntentCreatorTest extends SysuiTestCase { } @Test - public void test_getImageEditIntent() { + public void test_getImageEditIntentAsync() { getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, ""); Uri fakeUri = Uri.parse("content://foo"); - Intent intent = mIntentCreator.getImageEditIntent(fakeUri, getContext()); + final AtomicReference<Intent> intentHolder = new AtomicReference<>(null); + mIntentCreator.getImageEditIntentAsync(fakeUri, getContext(), output -> { + intentHolder.set(output); + }); + Intent intent = intentHolder.get(); assertEquals(Intent.ACTION_EDIT, intent.getAction()); assertEquals("image/*", intent.getType()); assertEquals(null, intent.getComponent()); @@ -90,8 +96,10 @@ public class DefaultIntentCreatorTest extends SysuiTestCase { "com.android.remotecopy.RemoteCopyActivity"); getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, fakeComponent.flattenToString()); - intent = mIntentCreator.getImageEditIntent(fakeUri, getContext()); - assertEquals(fakeComponent, intent.getComponent()); + mIntentCreator.getImageEditIntentAsync(fakeUri, getContext(), output -> { + intentHolder.set(output); + }); + assertEquals(fakeComponent, intentHolder.get().getComponent()); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt index df10d058c5d1..b08e6761d92f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt @@ -16,12 +16,14 @@ package com.android.systemui.communal.widgets +import android.appwidget.AppWidgetProviderInfo import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository +import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.communal.domain.interactor.setCommunalEnabled @@ -49,6 +51,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.never +import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -65,6 +68,7 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { private lateinit var appWidgetIdToRemove: MutableSharedFlow<Int> + private lateinit var communalInteractorSpy: CommunalInteractor private lateinit var underTest: CommunalAppWidgetHostStartable @Before @@ -78,12 +82,13 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { helper = kosmos.fakeGlanceableHubMultiUserHelper appWidgetIdToRemove = MutableSharedFlow() whenever(appWidgetHost.appWidgetIdToRemove).thenReturn(appWidgetIdToRemove) + communalInteractorSpy = spy(kosmos.communalInteractor) underTest = CommunalAppWidgetHostStartable( { appWidgetHost }, { communalWidgetHost }, - { kosmos.communalInteractor }, + { communalInteractorSpy }, { kosmos.communalSettingsInteractor }, { kosmos.keyguardInteractor }, { kosmos.fakeUserTracker }, @@ -259,6 +264,41 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { } @Test + fun removeNotLockscreenWidgets_whenCommunalIsAvailable() = + with(kosmos) { + testScope.runTest { + // Communal is available + setCommunalAvailable(true) + kosmos.fakeUserTracker.set( + userInfos = listOf(MAIN_USER_INFO), + selectedUserIndex = 0, + ) + fakeCommunalWidgetRepository.addWidget( + appWidgetId = 1, + userId = MAIN_USER_INFO.id, + category = AppWidgetProviderInfo.WIDGET_CATEGORY_NOT_KEYGUARD, + ) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget( + appWidgetId = 3, + userId = MAIN_USER_INFO.id, + category = AppWidgetProviderInfo.WIDGET_CATEGORY_NOT_KEYGUARD, + ) + + underTest.start() + runCurrent() + + val communalWidgets by + collectLastValue(fakeCommunalWidgetRepository.communalWidgets) + assertThat(communalWidgets).hasSize(1) + assertThat(communalWidgets!![0].appWidgetId).isEqualTo(2) + + verify(communalInteractorSpy).deleteWidget(1) + verify(communalInteractorSpy).deleteWidget(3) + } + } + + @Test fun onStartHeadlessSystemUser_registerWidgetManager_whenCommunalIsAvailable() = with(kosmos) { testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt index 197b0eea05cb..859137507bbf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt @@ -26,6 +26,7 @@ import android.view.Display.TYPE_INTERNAL import android.view.IWindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.app.displaylib.DisplayRepository.PendingDisplay import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.FlowValue import com.android.systemui.coroutines.collectLastValue @@ -71,14 +72,20 @@ class DisplayRepositoryTest : SysuiTestCase() { // that the initial state (soon after construction) contains the expected ones set in every // test. private val displayRepository: DisplayRepositoryImpl by lazy { - DisplayRepositoryImpl( + // TODO b/401305290 - move this to kosmos + val displayRepositoryFromLib = + com.android.app.displaylib.DisplayRepositoryImpl( displayManager, - commandQueue, - windowManager, testHandler, - TestScope(UnconfinedTestDispatcher()), + testScope.backgroundScope, UnconfinedTestDispatcher(), ) + DisplayRepositoryImpl( + commandQueue, + windowManager, + testScope.backgroundScope, + displayRepositoryFromLib, + ) .also { verify(displayManager, never()).registerDisplayListener(any(), any()) // It needs to be called, just once, for the initial value. @@ -403,7 +410,7 @@ class DisplayRepositoryTest : SysuiTestCase() { val pendingDisplay by lastPendingDisplay() sendOnDisplayConnected(1, TYPE_EXTERNAL) - val initialPendingDisplay: DisplayRepository.PendingDisplay? = pendingDisplay + val initialPendingDisplay: PendingDisplay? = pendingDisplay assertThat(pendingDisplay).isNotNull() sendOnDisplayChanged(1) @@ -416,7 +423,7 @@ class DisplayRepositoryTest : SysuiTestCase() { val pendingDisplay by lastPendingDisplay() sendOnDisplayConnected(1, TYPE_EXTERNAL) - val initialPendingDisplay: DisplayRepository.PendingDisplay? = pendingDisplay + val initialPendingDisplay: PendingDisplay? = pendingDisplay assertThat(pendingDisplay).isNotNull() sendOnDisplayConnected(2, TYPE_EXTERNAL) @@ -648,7 +655,7 @@ class DisplayRepositoryTest : SysuiTestCase() { return flowValue } - private fun TestScope.lastPendingDisplay(): FlowValue<DisplayRepository.PendingDisplay?> { + private fun TestScope.lastPendingDisplay(): FlowValue<PendingDisplay?> { val flowValue = collectLastValue(displayRepository.pendingDisplay) captureAddedRemovedListener() verify(displayManager) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt index e41d46ce90af..6c7783ae44e1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt @@ -31,7 +31,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyString -import org.mockito.kotlin.eq +import org.mockito.kotlin.any import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) @@ -105,7 +105,7 @@ class PerDisplayInstanceRepositoryImplTest : SysuiTestCase() { @Test fun start_registersDumpable() { - verify(kosmos.dumpManager).registerNormalDumpable(anyString(), eq(underTest)) + verify(kosmos.dumpManager).registerNormalDumpable(anyString(), any()) } private fun createDisplay(displayId: Int): Display = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt index f2a6c11b872e..722976131caf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt @@ -26,9 +26,9 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.education.data.model.EduDeviceConnectionTime import com.android.systemui.education.data.model.GestureEduModel import com.android.systemui.education.domain.interactor.mockEduInputManager -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import java.io.File import javax.inject.Provider @@ -48,7 +48,7 @@ import org.junit.runner.RunWith class ContextualEducationRepositoryTest : SysuiTestCase() { private lateinit var underTest: UserContextualEducationRepository - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val dsScopeProvider: Provider<CoroutineScope> = Provider { TestScope(kosmos.testDispatcher).backgroundScope @@ -70,7 +70,7 @@ class ContextualEducationRepositoryTest : SysuiTestCase() { testContext, dsScopeProvider, kosmos.mockEduInputManager, - kosmos.testDispatcher + kosmos.testDispatcher, ) underTest.setUser(testUserId) } @@ -109,7 +109,7 @@ class ContextualEducationRepositoryTest : SysuiTestCase() { lastEducationTime = kosmos.fakeEduClock.instant(), usageSessionStartTime = kosmos.fakeEduClock.instant(), userId = testUserId, - gestureType = BACK + gestureType = BACK, ) underTest.updateGestureEduModel(BACK) { newModel } val model by collectLastValue(underTest.readGestureEduModelFlow(BACK)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/HapticSliderPluginTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/HapticSliderPluginTest.kt index 5030d1e49da0..0f75e57ce771 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/HapticSliderPluginTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/HapticSliderPluginTest.kt @@ -21,9 +21,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.haptics.msdl.msdlPlayer -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.VibratorHelper +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat @@ -45,7 +45,7 @@ import org.mockito.junit.MockitoRule @RunWith(AndroidJUnit4::class) class HapticSliderPluginTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule() @Mock private lateinit var vibratorHelper: VibratorHelper diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/UserInputDeviceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/UserInputDeviceRepositoryTest.kt index 798c5ab817a5..39b5e81345ee 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/UserInputDeviceRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/UserInputDeviceRepositoryTest.kt @@ -23,9 +23,9 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.inputdevice.data.model.UserDeviceConnectionStatus import com.android.systemui.keyboard.data.repository.keyboardRepository -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.android.systemui.touchpad.data.repository.touchpadRepository import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.user.data.repository.userRepository @@ -41,7 +41,7 @@ import org.junit.runner.RunWith class UserInputDeviceRepositoryTest : SysuiTestCase() { private lateinit var underTest: UserInputDeviceRepository - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val keyboardRepository = kosmos.keyboardRepository private val touchpadRepository = kosmos.touchpadRepository @@ -54,7 +54,7 @@ class UserInputDeviceRepositoryTest : SysuiTestCase() { kosmos.testDispatcher, keyboardRepository, touchpadRepository, - kosmos.userRepository + kosmos.userRepository, ) userRepository.setUserInfos(USER_INFOS) } @@ -72,7 +72,7 @@ class UserInputDeviceRepositoryTest : SysuiTestCase() { assertThat(isAnyKeyboardConnected) .containsExactly( UserDeviceConnectionStatus(isConnected = true, USER_INFOS[0].id), - UserDeviceConnectionStatus(isConnected = true, USER_INFOS[1].id) + UserDeviceConnectionStatus(isConnected = true, USER_INFOS[1].id), ) .inOrder() } @@ -90,16 +90,13 @@ class UserInputDeviceRepositoryTest : SysuiTestCase() { assertThat(isAnyTouchpadConnected) .containsExactly( UserDeviceConnectionStatus(isConnected = true, USER_INFOS[0].id), - UserDeviceConnectionStatus(isConnected = true, USER_INFOS[1].id) + UserDeviceConnectionStatus(isConnected = true, USER_INFOS[1].id), ) .inOrder() } companion object { private val USER_INFOS = - listOf( - UserInfo(100, "First User", 0), - UserInfo(101, "Second User", 0), - ) + listOf(UserInfo(100, "First User", 0), UserInfo(101, "Second User", 0)) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt index 274880b484cc..6bd0fb0e17a1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt @@ -23,9 +23,9 @@ import android.view.inputmethod.InputMethodSubtype import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock @@ -47,7 +47,7 @@ class InputMethodRepositoryTest : SysuiTestCase() { @Mock private lateinit var inputMethodManager: InputMethodManager - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private lateinit var underTest: InputMethodRepository @@ -72,7 +72,7 @@ class InputMethodRepositoryTest : SysuiTestCase() { inputMethodManager.getEnabledInputMethodSubtypeListAsUser( any(), anyBoolean(), - eq(USER_HANDLE) + eq(USER_HANDLE), ) ) .thenReturn(listOf()) @@ -97,7 +97,7 @@ class InputMethodRepositoryTest : SysuiTestCase() { inputMethodManager.getEnabledInputMethodSubtypeListAsUser( eq(selectedImiId), anyBoolean(), - eq(USER_HANDLE) + eq(USER_HANDLE), ) ) .thenReturn( @@ -125,7 +125,7 @@ class InputMethodRepositoryTest : SysuiTestCase() { verify(inputMethodManager) .showInputMethodPickerFromSystem( /* showAuxiliarySubtypes = */ eq(true), - /* displayId = */ eq(displayId) + /* displayId = */ eq(displayId), ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt index 8e6de2f04279..9e129a43b4cb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt @@ -22,8 +22,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.inputmethod.data.model.InputMethodModel import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository import com.android.systemui.inputmethod.data.repository.inputMethodRepository -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import java.util.UUID import kotlinx.coroutines.test.runTest @@ -34,7 +34,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class InputMethodInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val fakeInputMethodRepository = kosmos.fakeInputMethodRepository @@ -148,7 +148,7 @@ class InputMethodInteractorTest : SysuiTestCase() { subtypes = List(auxiliarySubtypes + nonAuxiliarySubtypes) { InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes) - } + }, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt index 1bb4805d4f16..655c646cd34c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt @@ -34,7 +34,6 @@ import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcut import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper import com.android.systemui.keyboard.shortcut.shortcutHelperViewModel -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testDispatcher @@ -43,6 +42,7 @@ import com.android.systemui.plugins.activityStarter import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.userTracker import com.android.systemui.statusbar.phone.systemUIDialogFactory +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest @@ -60,7 +60,7 @@ class ShortcutHelperDialogStarterTest : SysuiTestCase() { private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource() private val mockUserContext: Context = mock() private val kosmos = - Kosmos().also { + testKosmos().also { it.testCase = this it.testDispatcher = UnconfinedTestDispatcher() it.shortcutHelperSystemShortcutsSource = fakeSystemSource diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt index be9e93c64053..ba57ffd37053 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt @@ -26,7 +26,7 @@ import com.android.systemui.keyboard.stickykeys.StickyKeysLogger import com.android.systemui.keyboard.stickykeys.shared.model.Locked import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel -import com.android.systemui.kosmos.Kosmos +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -52,19 +52,19 @@ class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() { fun setup() { val dialogFactory = mock<StickyKeyDialogFactory>() whenever(dialogFactory.create(any())).thenReturn(dialog) - val keyboardRepository = Kosmos().keyboardRepository + val keyboardRepository = testKosmos().keyboardRepository val viewModel = StickyKeysIndicatorViewModel( stickyKeysRepository, keyboardRepository, - testScope.backgroundScope + testScope.backgroundScope, ) coordinator = StickyKeysIndicatorCoordinator( testScope.backgroundScope, dialogFactory, viewModel, - mock<StickyKeysLogger>() + mock<StickyKeysLogger>(), ) coordinator.startListening() keyboardRepository.setIsAnyKeyboardConnected(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt index 9daf0ffd34b4..1c0041969504 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt @@ -33,7 +33,6 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos @@ -64,7 +63,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { private val inputManager = mock<InputManager>() private val keyboardRepository = FakeKeyboardRepository() private val secureSettings = kosmos.fakeSettings - private val userRepository = Kosmos().fakeUserRepository + private val userRepository = testKosmos().fakeUserRepository private val captor = ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index baf3b5b4430f..3f1cadc5fe76 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -23,12 +23,12 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.defaultDeviceState import com.android.systemui.deviceStateManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.kosmos.Kosmos import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat import java.util.function.Predicate @@ -68,7 +68,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var wallpaperManager: WallpaperManager - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val deviceStateManager = kosmos.deviceStateManager @Mock @@ -92,7 +92,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { surfaceControl1, Rect(), mock(ActivityManager.RunningTaskInfo::class.java), - false + false, ) private var surfaceControl2 = mock(SurfaceControl::class.java) @@ -113,7 +113,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { surfaceControl2, Rect(), mock(ActivityManager.RunningTaskInfo::class.java), - false + false, ) private lateinit var remoteAnimationTargets: Array<RemoteAnimationTarget> @@ -135,7 +135,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { surfaceControlWp, Rect(), mock(ActivityManager.RunningTaskInfo::class.java), - false + false, ) private lateinit var wallpaperTargets: Array<RemoteAnimationTarget> @@ -157,7 +157,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { surfaceControlLockWp, Rect(), mock(ActivityManager.RunningTaskInfo::class.java), - false + false, ) private lateinit var lockWallpaperTargets: Array<RemoteAnimationTarget> private var shouldPerformSmartspaceTransition = false @@ -179,14 +179,14 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { notificationShadeWindowController, powerManager, wallpaperManager, - deviceStateManager + deviceStateManager, ) { override fun shouldPerformSmartspaceTransition(): Boolean = shouldPerformSmartspaceTransition } keyguardUnlockAnimationController.setLauncherUnlockController( "", - launcherUnlockAnimationController + launcherUnlockAnimationController, ) whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java)) @@ -227,7 +227,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { arrayOf(), arrayOf(), 0 /* startTime */, - false /* requestedShowSurfaceBehindKeyguard */ + false, /* requestedShowSurfaceBehindKeyguard */ ) val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>() @@ -259,7 +259,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { wallpaperTargets, arrayOf(), 0 /* startTime */, - false /* requestedShowSurfaceBehindKeyguard */ + false, /* requestedShowSurfaceBehindKeyguard */ ) // Since the animation is running, we should not have finished the remote animation. @@ -282,7 +282,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { wallpaperTargets, arrayOf(), 0 /* startTime */, - false /* requestedShowSurfaceBehindKeyguard */ + false, /* requestedShowSurfaceBehindKeyguard */ ) verify(listener).onUnlockAnimationStarted(any(), eq(true), any(), any()) @@ -303,7 +303,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { wallpaperTargets, arrayOf(), 0 /* startTime */, - false /* requestedShowSurfaceBehindKeyguard */ + false, /* requestedShowSurfaceBehindKeyguard */ ) verify(listener).onUnlockAnimationStarted(any(), eq(false), any(), any()) @@ -327,7 +327,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { wallpaperTargets, arrayOf(), 0 /* startTime */, - true /* requestedShowSurfaceBehindKeyguard */ + true, /* requestedShowSurfaceBehindKeyguard */ ) assertTrue(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning) @@ -351,7 +351,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { wallpaperTargets, arrayOf(), 0 /* startTime */, - true /* requestedShowSurfaceBehindKeyguard */ + true, /* requestedShowSurfaceBehindKeyguard */ ) assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) @@ -373,7 +373,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { wallpaperTargets, arrayOf(), 0 /* startTime */, - false /* requestedShowSurfaceBehindKeyguard */ + false, /* requestedShowSurfaceBehindKeyguard */ ) assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) @@ -389,7 +389,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { wallpaperTargets, arrayOf(), 0 /* startTime */, - true /* requestedShowSurfaceBehindKeyguard */ + true, /* requestedShowSurfaceBehindKeyguard */ ) assertFalse(keyguardUnlockAnimationController.canPerformInWindowLauncherAnimations()) @@ -406,7 +406,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { wallpaperTargets, arrayOf(), 0 /* startTime */, - false /* requestedShowSurfaceBehindKeyguard */ + false, /* requestedShowSurfaceBehindKeyguard */ ) assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) @@ -427,7 +427,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { wallpaperTargets, lockWallpaperTargets, 0 /* startTime */, - false /* requestedShowSurfaceBehindKeyguard */ + false, /* requestedShowSurfaceBehindKeyguard */ ) for (i in 0..10) { @@ -471,7 +471,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { wallpaperTargets, arrayOf(), 0 /* startTime */, - false /* requestedShowSurfaceBehindKeyguard */ + false, /* requestedShowSurfaceBehindKeyguard */ ) // Cancel the animator so we can verify only the setSurfaceBehind call below. @@ -492,7 +492,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>() verify( surfaceTransactionApplier, - times(1).description("WallpaperSurface was expected to receive scheduleApply once") + times(1).description("WallpaperSurface was expected to receive scheduleApply once"), ) .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp }) @@ -523,7 +523,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { wallpaperTargets, arrayOf(), 0 /* startTime */, - false /* requestedShowSurfaceBehindKeyguard */ + false, /* requestedShowSurfaceBehindKeyguard */ ) // Cancel the animator so we can verify only the setSurfaceBehind call below. @@ -539,7 +539,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>() verify( surfaceTransactionApplier, - atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply") + atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply"), ) .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp }) @@ -562,7 +562,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { wallpaperTargets, arrayOf(), 0 /* startTime */, - false /* requestedShowSurfaceBehindKeyguard */ + false, /* requestedShowSurfaceBehindKeyguard */ ) // Stop the animator - we just want to test whether the override is not applied. @@ -578,7 +578,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>() verify( surfaceTransactionApplier, - atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply") + atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply"), ) .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp }) @@ -588,7 +588,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { assertEquals( "Wallpaper surface was expected to have opacity 1", 1f, - captorWp.getLastValue().alpha + captorWp.getLastValue().alpha, ) verifyNoMoreInteractions(surfaceTransactionApplier) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt index 97c746c49cba..d0762a3797c0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt @@ -25,10 +25,13 @@ import android.view.IRemoteAnimationFinishedCallback import android.view.RemoteAnimationTarget import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardShowWhileAwakeInteractor import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.android.window.flags.Flags @@ -63,6 +66,9 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { @Mock private lateinit var keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor @Mock private lateinit var keyguardTransitions: KeyguardTransitions + @Mock private lateinit var lockPatternUtils: LockPatternUtils + @Mock private lateinit var keyguardShowWhileAwakeInteractor: KeyguardShowWhileAwakeInteractor + @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor @Before fun setUp() { @@ -77,6 +83,9 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator, keyguardDismissTransitionInteractor = keyguardDismissTransitionInteractor, keyguardTransitions = keyguardTransitions, + selectedUserInteractor = selectedUserInteractor, + lockPatternUtils = lockPatternUtils, + keyguardShowWhileAwakeInteractor = keyguardShowWhileAwakeInteractor, ) } @@ -236,6 +245,8 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { .whenever(keyguardDismissTransitionInteractor) .startDismissKeyguardTransition(any(), any()) + whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(-1) + underTest.onKeyguardGoingAwayRemoteAnimationStart( transit = 0, apps = arrayOf(mock<RemoteAnimationTarget>()), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 83bee7c66d31..fe213a6ebbf0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -458,6 +458,56 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() @Test @DisableSceneContainer + fun alpha_shadeExpansionIgnoredWhenTransitioningAwayFromLockscreen() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha(viewState)) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + + shadeTestUtil.setQsExpansion(0f) + assertThat(alpha).isEqualTo(1f) + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + transitionState = TransitionState.STARTED, + value = 0f, + ), + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + transitionState = TransitionState.RUNNING, + value = 0.8f, + ), + ), + testScope, + ) + val priorAlpha = alpha + shadeTestUtil.setQsExpansion(0.5f) + assertThat(alpha).isEqualTo(priorAlpha) + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + transitionState = TransitionState.FINISHED, + value = 1f, + ) + ), + testScope, + ) + assertThat(alpha).isEqualTo(0f) + } + + @Test + @DisableSceneContainer fun alphaFromShadeExpansion_doesNotEmitWhenTransitionRunning() = testScope.runTest { keyguardTransitionRepository.sendTransitionSteps( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt index 91cb1ff266c9..9c168298b9a5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.BrokenWithSceneContainer +import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic @@ -44,7 +45,7 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.shadeTestUtil import com.android.systemui.testKosmos import com.google.common.collect.Range -import com.google.common.truth.Truth +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.runCurrent @@ -101,20 +102,30 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza // immediately 0f repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - runCurrent() - Truth.assertThat(actual).isEqualTo(0f) + assertThat(actual).isEqualTo(0f) repository.sendTransitionStep(step(.2f)) - runCurrent() - Truth.assertThat(actual).isEqualTo(0f) + assertThat(actual).isEqualTo(0f) repository.sendTransitionStep(step(0.8f)) - runCurrent() - Truth.assertThat(actual).isEqualTo(0f) + assertThat(actual).isEqualTo(0f) repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) + } + + @Test + @DisableSceneContainer + fun lockscreenAlphaEndsWithZero() = + testScope.runTest { + val alpha by collectLastValue(underTest.lockscreenAlpha) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) runCurrent() - Truth.assertThat(actual).isEqualTo(0f) + + // Jump right to the end and validate the value + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(alpha).isEqualTo(0f) } @Test @@ -138,21 +149,17 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza runCurrent() // fade out repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - runCurrent() - Truth.assertThat(actual).isEqualTo(1f) + assertThat(actual).isEqualTo(1f) repository.sendTransitionStep(step(.1f)) - runCurrent() - Truth.assertThat(actual).isIn(Range.open(.1f, .9f)) + assertThat(actual).isIn(Range.open(.1f, .9f)) // alpha is 1f before the full transition starts ending repository.sendTransitionStep(step(0.8f)) - runCurrent() - Truth.assertThat(actual).isEqualTo(0f) + assertThat(actual).isEqualTo(0f) repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) - runCurrent() - Truth.assertThat(actual).isEqualTo(0f) + assertThat(actual).isEqualTo(0f) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt index 61119cce7bc8..8592c424ca02 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt @@ -235,6 +235,19 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() { verify(mediaCarousel, never()).animationTargetX = anyFloat() } + @Test + fun testScrollingDisabled_noScroll_notDismissible() { + setupMediaContainer(visibleIndex = 1, showsSettingsButton = false) + + mediaCarouselScrollHandler.scrollingDisabled = true + + clock.advanceTime(DISMISS_DELAY) + executor.runAllReady() + + verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt()) + verify(mediaCarousel, never()).animationTargetX = anyFloat() + } + private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) { whenever(contentContainer.childCount).thenReturn(2) val child1: View = mock() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt index 6bc8000b0519..04f7fe1a6487 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt @@ -21,10 +21,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.policy.CastDevice import com.android.systemui.statusbar.policy.fakeCastController +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.test.runCurrent @@ -34,7 +34,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class MediaRouterRepositoryTest : SysuiTestCase() { - val kosmos = Kosmos() + val kosmos = testKosmos() val testScope = kosmos.testScope val castController = kosmos.fakeCastController diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/UserSettingObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/UserSettingObserverTest.kt index 858ed6a6b54a..473d7b6d0dfa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/UserSettingObserverTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/UserSettingObserverTest.kt @@ -22,8 +22,8 @@ import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat @@ -64,7 +64,7 @@ class UserSettingObserverTest(flags: FlagsParameterization) : SysuiTestCase() { mSetFlagsRule.setFlagsParameterization(flags) } - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private lateinit var testableLooper: TestableLooper @@ -85,7 +85,7 @@ class UserSettingObserverTest(flags: FlagsParameterization) : SysuiTestCase() { Handler(testableLooper.looper), TEST_SETTING, USER, - DEFAULT_VALUE + DEFAULT_VALUE, ) { override fun handleValueChanged(value: Int, observedChange: Boolean) { callback(value, observedChange) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt index 00490427f80b..a2829b5f42cd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt @@ -27,7 +27,6 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.pipeline.data.model.RestoreData import com.android.systemui.qs.pipeline.data.model.RestoreProcessor import com.android.systemui.qs.pipeline.data.model.workTileRestoreProcessor @@ -36,6 +35,7 @@ import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.WorkModeTile import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -47,7 +47,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class WorkTileAutoAddableTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val restoreProcessor: RestoreProcessor get() = kosmos.workTileRestoreProcessor @@ -62,7 +62,7 @@ class WorkTileAutoAddableTest : SysuiTestCase() { FakeUserTracker( _userId = USER_INFO_0.id, _userInfo = USER_INFO_0, - _userProfiles = listOf(USER_INFO_0) + _userProfiles = listOf(USER_INFO_0), ) underTest = WorkTileAutoAddable(userTracker, kosmos.workTileRestoreProcessor) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt index 3a9c3d066b23..4e0adcab8ea3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt @@ -22,7 +22,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.FakeQSFactory @@ -39,6 +38,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.qsTileFactory import com.android.systemui.settings.fakeUserTracker import com.android.systemui.settings.userTracker +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -55,22 +55,12 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class NoLowNumberOfTilesTest : SysuiTestCase() { - private val USER_0_INFO = - UserInfo( - 0, - "zero", - "", - UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, - ) + private val USER_0_INFO = UserInfo(0, "zero", "", UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL) - private val defaultTiles = - listOf( - TileSpec.create("internet"), - TileSpec.create("bt"), - ) + private val defaultTiles = listOf(TileSpec.create("internet"), TileSpec.create("bt")) private val kosmos = - Kosmos().apply { + testKosmos().apply { fakeMinimumTilesRepository = MinimumTilesFixedRepository(minNumberOfTiles = 2) fakeUserTracker.set(listOf(USER_0_INFO), 0) qsTileFactory = FakeQSFactory(::tileCreator) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt index 9d9bfda99bd9..77030aceb477 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt @@ -22,7 +22,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.FakeQSFactory @@ -34,6 +33,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.qsTileFactory import com.android.systemui.settings.fakeUserTracker import com.android.systemui.settings.userTracker +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -52,7 +52,9 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() { - private val kosmos by lazy { Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) } } + private val kosmos by lazy { + testKosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) } + } // Getter here so it can change when there is a managed profile. private val workTileAvailable: Boolean get() = hasManagedProfile() @@ -143,30 +145,15 @@ class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() { } private fun TestScope.createManagedProfileAndAdd() { - kosmos.fakeUserTracker.set( - listOf(USER_0_INFO, MANAGED_USER_INFO), - 0, - ) + kosmos.fakeUserTracker.set(listOf(USER_0_INFO, MANAGED_USER_INFO), 0) runCurrent() } private companion object { val WORK_TILE_SPEC = "work".toTileSpec() - val USER_0_INFO = - UserInfo( - 0, - "zero", - "", - UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, - ) + val USER_0_INFO = UserInfo(0, "zero", "", UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL) val MANAGED_USER_INFO = - UserInfo( - 10, - "ten-managed", - "", - 0, - UserManager.USER_TYPE_PROFILE_MANAGED, - ) + UserInfo(10, "ten-managed", "", 0, UserManager.USER_TYPE_PROFILE_MANAGED) fun String.toTileSpec() = TileSpec.create(this) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt index 557f4ea262a3..311f1f792bba 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt @@ -23,13 +23,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.airplane.domain.AirplaneModeMapper import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel import com.android.systemui.qs.tiles.impl.airplane.qsAirplaneModeTileConfig import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -37,7 +37,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class AirplaneModeMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val airplaneModeConfig = kosmos.qsAirplaneModeTileConfig private lateinit var mapper: AirplaneModeMapper diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt index 24e46686e23d..bb58ecaa2566 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt @@ -23,12 +23,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel import com.android.systemui.qs.tiles.impl.alarm.qsAlarmTileConfig import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import com.android.systemui.util.time.FakeSystemClock import java.time.Instant import java.time.LocalDateTime @@ -41,7 +41,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class AlarmTileMapperTest : SysuiTestCase() { private val oneMinute = 60000L - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val alarmTileConfig = kosmos.qsAlarmTileConfig private val fakeClock = FakeSystemClock() // Using lazy (versus =) to make sure we override the right context -- see b/311612168 diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt index 44c175ab7156..1ef1a72b7483 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt @@ -23,10 +23,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor +import com.android.systemui.testKosmos import com.android.systemui.utils.leaks.FakeBatteryController import com.google.common.truth.Truth import kotlinx.coroutines.flow.flowOf @@ -40,7 +40,7 @@ import org.junit.runner.RunWith @EnabledOnRavenwood @RunWith(AndroidJUnit4::class) class BatterySaverTileDataInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val batteryController = FakeBatteryController(LeakCheck()) private val testUser = UserHandle.of(1) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt index 2ddaddd5ad35..cb50ec9a70f4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt @@ -22,12 +22,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -35,7 +35,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class BatterySaverTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val batterySaverTileConfig = kosmos.qsBatterySaverTileConfig private lateinit var mapper: BatterySaverTileMapper diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt index a3c159820a94..bcd443cc78e1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt @@ -22,19 +22,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel import com.android.systemui.qs.tiles.impl.colorcorrection.qsColorCorrectionTileConfig import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ColorCorrectionTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val colorCorrectionTileConfig = kosmos.qsColorCorrectionTileConfig private val subtitleArray by lazy { context.resources.getStringArray(R.array.tile_states_color_correction) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt index 83e61d1b36bf..3c0e7d51b4e8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt @@ -24,7 +24,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.qs.external.TileServiceKey import com.android.systemui.qs.pipeline.shared.TileSpec @@ -34,6 +33,7 @@ import com.android.systemui.qs.tiles.impl.custom.customTileSpec import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runCurrent @@ -45,7 +45,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class CustomTileRepositoryTest : SysuiTestCase() { - private val kosmos = Kosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) } + private val kosmos = testKosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) } private val underTest: CustomTileRepository = with(kosmos) { CustomTileRepositoryImpl( @@ -213,7 +213,7 @@ class CustomTileRepositoryTest : SysuiTestCase() { underTest.updateWithTile( TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, - true + true, ) runCurrent() @@ -247,7 +247,7 @@ class CustomTileRepositoryTest : SysuiTestCase() { underTest.updateWithTile( TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, - true + true, ) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt index a115c1235210..2da144e5ee98 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt @@ -21,11 +21,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import org.junit.Test @@ -34,7 +34,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class FlashlightMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val qsTileConfig = kosmos.qsFlashlightTileConfig private val mapper by lazy { FlashlightMapper( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt index 8f8f7105415f..45720b86a859 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt @@ -22,19 +22,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel import com.android.systemui.qs.tiles.impl.fontscaling.qsFontScalingTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class FontScalingTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val fontScalingTileConfig = kosmos.qsFontScalingTileConfig private val mapper by lazy { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt index 9bd4895ac21c..93f2bc34372e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt @@ -26,7 +26,6 @@ import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.animation.LaunchableView -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.shared.QSSettingsPackageRepository @@ -37,6 +36,7 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel import com.android.systemui.statusbar.phone.FakeKeyguardStateController import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before @@ -67,7 +67,7 @@ class FontScalingUserActionInteractorTest : SysuiTestCase() { @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable> - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val scope = kosmos.testScope private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler() private val keyguardStateController = FakeKeyguardStateController() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt index 3d3447da15a1..c41034202c88 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt @@ -21,19 +21,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel import com.android.systemui.qs.tiles.impl.hearingdevices.qsHearingDevicesTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class HearingDevicesTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val qsTileConfig = kosmos.qsHearingDevicesTileConfig private val mapper by lazy { HearingDevicesTileMapper( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt index 4b9d11d2f706..4d38e7588578 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt @@ -24,11 +24,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.hearingaid.HearingDevicesChecker import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel import com.android.systemui.statusbar.policy.fakeBluetoothController +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent @@ -46,7 +46,7 @@ import org.mockito.kotlin.whenever @EnabledOnRavenwood @RunWith(AndroidJUnit4::class) class HearingDevicesTileDataInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val testUser = UserHandle.of(1) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt index 1b497a2b36ed..0ba057b1881b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt @@ -22,13 +22,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager import com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.Companion.LAUNCH_SOURCE_QS_TILE -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.qs.shared.QSSettingsPackageRepository import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before @@ -47,7 +47,7 @@ import org.mockito.kotlin.whenever @EnabledOnRavenwood @RunWith(AndroidJUnit4::class) class HearingDevicesTileUserActionInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val inputHandler = FakeQSTileIntentUserInputHandler() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt index 54a653df696f..e15664eba6b9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt @@ -28,7 +28,6 @@ import com.android.systemui.common.shared.model.ContentDescription.Companion.loa import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.Text.Companion.loadText -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig @@ -38,13 +37,14 @@ import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileIconModel +import com.android.systemui.testKosmos import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class InternetTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val internetTileConfig = kosmos.qsInternetTileConfig private val handler = kosmos.fakeExecutorHandler private val mapper by lazy { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt index 63607f1edd59..45582f017fb0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt @@ -29,7 +29,6 @@ import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger @@ -54,6 +53,7 @@ import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkMode import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository +import com.android.systemui.testKosmos import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat @@ -67,7 +67,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class InternetTileDataInteractorTest : SysuiTestCase() { private val testUser = UserHandle.of(1) - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private lateinit var underTest: InternetTileDataInteractor diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt index 261e3de939f2..d019a456a4f5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt @@ -21,7 +21,6 @@ import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject @@ -29,6 +28,7 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx import com.android.systemui.qs.tiles.dialog.InternetDialogManager import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel import com.android.systemui.statusbar.connectivity.AccessPointController +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.nullable import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -44,7 +44,7 @@ import org.mockito.kotlin.verify @EnabledOnRavenwood @RunWith(AndroidJUnit4::class) class InternetTileUserActionInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val inputHandler = FakeQSTileIntentUserInputHandler() private lateinit var underTest: InternetTileUserActionInteractor diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt index 780d58c83e5b..48e114603723 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt @@ -22,20 +22,20 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tileimpl.SubtitleArrayMapping import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel import com.android.systemui.qs.tiles.impl.inversion.qsColorInversionTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ColorInversionTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val colorInversionTileConfig = kosmos.qsColorInversionTileConfig private val subtitleArrayId = SubtitleArrayMapping.getSubtitleId(colorInversionTileConfig.tileSpec.spec) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingDataInteractorTest.kt index 7562ac00e4a6..57f85bd8c5ac 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingDataInteractorTest.kt @@ -22,13 +22,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.recordissue.IssueRecordingState import com.android.systemui.settings.fakeUserFileManager import com.android.systemui.settings.userTracker +import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeGlobalSettings import com.google.common.truth.Truth import kotlinx.coroutines.flow.flowOf @@ -42,7 +42,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class IssueRecordingDataInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().also { it.testCase = this } private val userTracker = kosmos.userTracker private val userFileManager = kosmos.fakeUserFileManager private val testUser = UserHandle.of(1) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt index fa6d8bf4a317..0201168181a9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt @@ -20,7 +20,6 @@ import android.content.res.mainResources import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.qsEventLogger @@ -30,6 +29,7 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig import com.android.systemui.recordissue.RecordIssueModule import com.android.systemui.res.R +import com.android.systemui.testKosmos import com.google.common.truth.Truth import org.junit.Test import org.junit.runner.RunWith @@ -37,7 +37,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class IssueRecordingMapperTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().also { it.testCase = this } private val uiConfig = QSTileUIConfig.Resource(R.drawable.qs_record_issue_icon_off, R.string.qs_record_issue_label) private val config = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt index 9c2be899b8ca..125419ba3bdf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt @@ -22,7 +22,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.dialogTransitionAnimator -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope @@ -39,6 +38,7 @@ import com.android.systemui.settings.userFileManager import com.android.systemui.settings.userTracker import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.statusbar.policy.keyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeGlobalSettings import com.google.common.truth.Truth import kotlinx.coroutines.test.runTest @@ -56,7 +56,7 @@ class IssueRecordingUserActionInteractorTest : SysuiTestCase() { @Mock private lateinit var recordingController: RecordingController val user = UserHandle(1) - val kosmos = Kosmos().also { it.testCase = this } + val kosmos = testKosmos().also { it.testCase = this } private lateinit var userContextProvider: UserContextProvider private lateinit var underTest: IssueRecordingUserActionInteractor diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt index 4ebe23dcdef1..a6ad54935f08 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt @@ -21,11 +21,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel import com.android.systemui.qs.tiles.impl.location.qsLocationTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import com.google.common.truth.Truth import junit.framework.Assert import org.junit.Test @@ -34,7 +34,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LocationTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val qsTileConfig = kosmos.qsLocationTileConfig private val mapper by lazy { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt index 8b21cb4a97d5..435aea07c9a0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt @@ -21,7 +21,6 @@ import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler @@ -32,6 +31,7 @@ import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTil import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel import com.android.systemui.statusbar.phone.FakeKeyguardStateController import com.android.systemui.statusbar.policy.LocationController +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.test.runTest @@ -58,7 +58,7 @@ class LocationTileUserActionInteractorTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - val kosmos = Kosmos() + val kosmos = testKosmos() underTest = LocationTileUserActionInteractor( EmptyCoroutineContext, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt index a0aa2d4a9a6c..a5e2922ffb3d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt @@ -26,7 +26,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.data.repository.NightDisplayRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dagger.NightDisplayListenerModule -import com.android.systemui.kosmos.Kosmos +import com.android.systemui.testKosmos import com.android.systemui.user.utils.UserScopedService import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock @@ -47,7 +47,7 @@ import org.mockito.ArgumentMatchers.anyInt @SmallTest @RunWith(AndroidJUnit4::class) class NightDisplayTileDataInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testUser = UserHandle.of(1)!! private val testStartTime = LocalTime.MIDNIGHT private val testEndTime = LocalTime.NOON diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt index adc8bcba5a5c..51975b0508a9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt @@ -26,12 +26,12 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.data.repository.NightDisplayRepository import com.android.systemui.dagger.NightDisplayListenerModule -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.intentInputs import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx import com.android.systemui.qs.tiles.impl.custom.qsTileLogger import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.testKosmos import com.android.systemui.user.utils.UserScopedService import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock @@ -51,7 +51,7 @@ import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidJUnit4::class) class NightDisplayTileUserActionInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler() private val testUser = UserHandle.of(1) private val colorDisplayManager = @@ -89,7 +89,7 @@ class NightDisplayTileUserActionInteractorTest : SysuiTestCase() { NightDisplayTileUserActionInteractor( nightDisplayRepository, qsTileIntentUserActionHandler, - kosmos.qsTileLogger + kosmos.qsTileLogger, ) @Test @@ -143,7 +143,7 @@ class NightDisplayTileUserActionInteractorTest : SysuiTestCase() { underTest.handleInput( QSTileInputTestKtx.longClick( NightDisplayTileModel.AutoModeOff(enabledState, false), - testUser + testUser, ) ) @@ -163,7 +163,7 @@ class NightDisplayTileUserActionInteractorTest : SysuiTestCase() { underTest.handleInput( QSTileInputTestKtx.longClick( NightDisplayTileModel.AutoModeOff(enabledState, false), - testUser + testUser, ) ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt index 7c853261aa1c..0b0b88e455da 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt @@ -24,13 +24,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.base.logging.QSTileLogger import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel import com.android.systemui.qs.tiles.impl.night.qsNightDisplayTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import java.time.LocalTime import java.time.format.DateTimeFormatter @@ -41,7 +41,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class NightDisplayTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val config = kosmos.qsNightDisplayTileConfig private val testStartTime = LocalTime.MIDNIGHT diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt index b6caa22a3012..ecf6f2a9cd1d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt @@ -22,19 +22,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel import com.android.systemui.qs.tiles.impl.notes.qsNotesTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import kotlin.test.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class NotesTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val qsTileConfig = kosmos.qsNotesTileConfig private val mapper by lazy { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractorTest.kt index 4786fc40562a..84ab6905a007 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractorTest.kt @@ -24,9 +24,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toCollection @@ -38,12 +38,11 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class NotesTileDataInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val testUser = UserHandle.of(1) private lateinit var underTest: NotesTileDataInteractor - @EnableFlags(Flags.FLAG_NOTES_ROLE_QS_TILE) @Test fun availability_qsFlagEnabled_notesRoleEnabled_returnTrue() = @@ -92,7 +91,7 @@ class NotesTileDataInteractorTest : SysuiTestCase() { fun tileData_notEmpty() = runTest { underTest = NotesTileDataInteractor(isNoteTaskEnabled = true) val flowValue by - collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))) + collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractorTest.kt index 54911e612291..282af9159e2b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractorTest.kt @@ -20,7 +20,6 @@ import android.content.Intent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEntryPoint @@ -29,18 +28,19 @@ import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandl import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.mock import org.mockito.Mockito.verify +import org.mockito.kotlin.mock @SmallTest @RunWith(AndroidJUnit4::class) class NotesTileUserActionInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val inputHandler = FakeQSTileIntentUserInputHandler() private val panelInteractor = mock<PanelInteractor>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt index 59eb069a5f3b..16b0caa78cfa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt @@ -22,9 +22,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.data.repository.oneHandedModeRepository import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor +import com.android.systemui.testKosmos import com.android.wm.shell.onehanded.OneHanded import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf @@ -37,7 +37,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class OneHandedModeTileDataInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testUser = UserHandle.of(1)!! private val oneHandedModeRepository = kosmos.oneHandedModeRepository private val underTest: OneHandedModeTileDataInteractor = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt index 5b39810e3477..3fba857bae1a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt @@ -22,13 +22,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tileimpl.SubtitleArrayMapping import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel import com.android.systemui.qs.tiles.impl.onehanded.qsOneHandedModeTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -36,7 +36,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class OneHandedModeTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val config = kosmos.qsOneHandedModeTileConfig private val subtitleArrayId = SubtitleArrayMapping.getSubtitleId(config.tileSpec.spec) private val subtitleArray by lazy { context.resources.getStringArray(subtitleArrayId) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt index 312f18029570..ef21df6e2636 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt @@ -21,12 +21,12 @@ import android.platform.test.annotations.EnabledOnRavenwood import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel import com.android.systemui.qs.tiles.impl.qr.qrCodeScannerTileUserActionInteractor +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import kotlinx.coroutines.test.runTest import org.junit.Test @@ -36,7 +36,7 @@ import org.junit.runner.RunWith @EnabledOnRavenwood @RunWith(AndroidJUnit4::class) class QRCodeScannerTileUserActionInteractorTest : SysuiTestCase() { - val kosmos = Kosmos() + val kosmos = testKosmos() private val inputHandler = kosmos.qsTileIntentUserInputHandler private val underTest = kosmos.qrCodeScannerTileUserActionInteractor private val intent = mock<Intent>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt index c572ff60b61a..9e20aae6103e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt @@ -23,11 +23,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel import com.android.systemui.qs.tiles.impl.qr.qsQRCodeScannerTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import org.junit.Before import org.junit.Test @@ -36,7 +36,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class QRCodeScannerTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val config = kosmos.qsQRCodeScannerTileConfig private lateinit var mapper: QRCodeScannerTileMapper diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt index dc3248d42d62..fecb3deaaf36 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt @@ -23,10 +23,10 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.reduceBrightColorsController import com.android.systemui.coroutines.collectValues -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent @@ -40,14 +40,14 @@ import org.junit.runner.RunWith class ReduceBrightColorsTileDataInteractorTest : SysuiTestCase() { private val isAvailable = true - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val reduceBrightColorsController = kosmos.reduceBrightColorsController private val underTest: ReduceBrightColorsTileDataInteractor = ReduceBrightColorsTileDataInteractor( testScope.testScheduler, isAvailable, - reduceBrightColorsController + reduceBrightColorsController, ) @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt index 75b090c4034b..77321385b09e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt @@ -28,11 +28,11 @@ import com.android.server.display.feature.flags.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.extradim.ExtraDimDialogManager import com.android.systemui.accessibility.reduceBrightColorsController -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before @@ -48,7 +48,7 @@ import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class ReduceBrightColorsTileUserActionInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val inputHandler = FakeQSTileIntentUserInputHandler() private val controller = kosmos.reduceBrightColorsController diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt index 00017f9059de..ebf70dad7149 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt @@ -23,12 +23,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel import com.android.systemui.qs.tiles.impl.reducebrightness.qsReduceBrightColorsTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -36,7 +36,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ReduceBrightColorsTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val config = kosmos.qsReduceBrightColorsTileConfig private lateinit var mapper: ReduceBrightColorsTileMapper diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt index 283fa601f8df..6f05c3178754 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt @@ -28,9 +28,9 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.camera.data.repository.fakeCameraAutoRotateRepository import com.android.systemui.camera.data.repository.fakeCameraSensorPrivacyRepository import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.android.systemui.utils.leaks.FakeBatteryController @@ -48,7 +48,7 @@ import org.junit.runner.RunWith @EnabledOnRavenwood @RunWith(AndroidJUnit4::class) class RotationLockTileDataInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val batteryController = FakeBatteryController(LeakCheck()) private val rotationController = FakeRotationLockController(LeakCheck()) @@ -65,7 +65,7 @@ class RotationLockTileDataInteractorTest : SysuiTestCase() { whenever( packageManager.checkPermission( eq(Manifest.permission.CAMERA), - eq(TEST_PACKAGE_NAME) + eq(TEST_PACKAGE_NAME), ) ) .thenReturn(PackageManager.PERMISSION_GRANTED) @@ -81,7 +81,7 @@ class RotationLockTileDataInteractorTest : SysuiTestCase() { .apply { addOverride(com.android.internal.R.bool.config_allowRotationResolver, true) } - .resources + .resources, ) } @@ -182,7 +182,7 @@ class RotationLockTileDataInteractorTest : SysuiTestCase() { whenever( packageManager.checkPermission( eq(Manifest.permission.CAMERA), - eq(TEST_PACKAGE_NAME) + eq(TEST_PACKAGE_NAME), ) ) .thenReturn(PackageManager.PERMISSION_DENIED) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt index 74010143166b..0e6aaa6320c8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt @@ -25,7 +25,6 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.defaultDeviceState import com.android.systemui.deviceStateManager import com.android.systemui.foldedDeviceStateList -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel import com.android.systemui.qs.tiles.impl.rotation.qsRotationLockTileConfig @@ -33,6 +32,7 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.devicePostureController +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -42,7 +42,7 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class RotationLockTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val rotationLockTileConfig = kosmos.qsRotationLockTileConfig private val devicePostureController = kosmos.devicePostureController private val deviceStateManager = kosmos.deviceStateManager diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt index 1fb5048dd4c9..079921640705 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt @@ -22,19 +22,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel import com.android.systemui.qs.tiles.impl.saver.qsDataSaverTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class DataSaverTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val dataSaverTileConfig = kosmos.qsDataSaverTileConfig // Using lazy (versus =) to make sure we override the right context -- see b/311612168 diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileDataInteractorTest.kt index 41174e7ca6af..127160d8728d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileDataInteractorTest.kt @@ -22,11 +22,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent @@ -38,7 +38,7 @@ import org.junit.runner.RunWith @EnabledOnRavenwood @RunWith(AndroidJUnit4::class) class ScreenRecordTileDataInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val screenRecordRepo = kosmos.screenRecordRepository private val underTest: ScreenRecordTileDataInteractor = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt index 778c73fd8638..f47e0578461a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt @@ -26,7 +26,6 @@ import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.plugins.ActivityStarter.OnDismissAction @@ -36,6 +35,7 @@ import com.android.systemui.screenrecord.RecordingController import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.ScreenRecordRepositoryImpl import com.android.systemui.statusbar.phone.KeyguardDismissUtil +import com.android.systemui.testKosmos import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -49,7 +49,7 @@ import org.mockito.kotlin.mock @SmallTest @RunWith(AndroidJUnit4::class) class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val keyguardInteractor = kosmos.keyguardInteractor private val dialogTransitionAnimator = mock<DialogTransitionAnimator>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt index 363255695131..d118c096fd16 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt @@ -23,13 +23,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.screenrecord.domain.ui.ScreenRecordTileMapper import com.android.systemui.qs.tiles.impl.screenrecord.qsScreenRecordTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R import com.android.systemui.screenrecord.data.model.ScreenRecordModel +import com.android.systemui.testKosmos import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -37,7 +37,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ScreenRecordTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val config = kosmos.qsScreenRecordTileConfig private lateinit var mapper: ScreenRecordTileMapper diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt index 6c7bb1b46c36..4a28fc02042d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt @@ -23,11 +23,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.impl.sensorprivacy.SensorPrivacyToggleTileDataInteractor import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -44,7 +44,7 @@ import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidJUnit4::class) class SensorPrivacyToggleTileDataInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val mockSensorPrivacyController = mock<IndividualSensorPrivacyController> { @@ -55,7 +55,7 @@ class SensorPrivacyToggleTileDataInteractorTest : SysuiTestCase() { SensorPrivacyToggleTileDataInteractor( testScope.testScheduler, mockSensorPrivacyController, - CAMERA + CAMERA, ) @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt index 562e6ebcc029..7856fcaf4a1b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt @@ -26,7 +26,6 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor -import com.android.systemui.kosmos.Kosmos import com.android.systemui.plugins.activityStarter import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject @@ -34,6 +33,7 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.SensorPrivacyToggleTileUserActionInteractor import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -48,7 +48,7 @@ import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidJUnit4::class) class SensorPrivacyToggleTileUserActionInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val inputHandler = FakeQSTileIntentUserInputHandler() private val keyguardInteractor = kosmos.keyguardInteractor // The keyguard repository below is the same one kosmos used to create the interactor above @@ -64,7 +64,7 @@ class SensorPrivacyToggleTileUserActionInteractorTest : SysuiTestCase() { mockActivityStarter, mockSensorPrivacyController, fakeSafetyCenterManager, - CAMERA + CAMERA, ) @Test @@ -79,7 +79,7 @@ class SensorPrivacyToggleTileUserActionInteractorTest : SysuiTestCase() { .setSensorBlocked( eq(SensorPrivacyManager.Sources.QS_TILE), eq(CAMERA), - eq(!originalIsBlocked) + eq(!originalIsBlocked), ) } @@ -95,7 +95,7 @@ class SensorPrivacyToggleTileUserActionInteractorTest : SysuiTestCase() { .setSensorBlocked( eq(SensorPrivacyManager.Sources.QS_TILE), eq(CAMERA), - eq(!originalIsBlocked) + eq(!originalIsBlocked), ) } @@ -114,7 +114,7 @@ class SensorPrivacyToggleTileUserActionInteractorTest : SysuiTestCase() { .setSensorBlocked( eq(SensorPrivacyManager.Sources.QS_TILE), eq(CAMERA), - eq(!originalIsBlocked) + eq(!originalIsBlocked), ) verify(mockActivityStarter).postQSRunnableDismissingKeyguard(any()) } @@ -150,7 +150,7 @@ class SensorPrivacyToggleTileUserActionInteractorTest : SysuiTestCase() { mockActivityStarter, mockSensorPrivacyController, fakeSafetyCenterManager, - MICROPHONE + MICROPHONE, ) micUserActionInteractor.handleInput( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt index e4cd0e0ec215..3b810dc451f9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt @@ -24,7 +24,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel import com.android.systemui.qs.tiles.impl.sensorprivacy.qsCameraSensorPrivacyToggleTileConfig @@ -33,13 +32,14 @@ import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyTileReso import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyTileResources.MicrophonePrivacyTileResources import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class SensorPrivacyToggleTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val cameraConfig = kosmos.qsCameraSensorPrivacyToggleTileConfig private val micConfig = kosmos.qsMicrophoneSensorPrivacyToggleTileConfig diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt index 8f5f2d3e6689..547bbbc895a6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt @@ -25,12 +25,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.uimodenight.UiModeNightTileModelHelper.createModel import com.android.systemui.qs.tiles.impl.uimodenight.qsUiModeNightTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import kotlin.reflect.KClass import org.junit.Test import org.junit.runner.RunWith @@ -38,7 +38,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class UiModeNightTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val qsTileConfig = kosmos.qsUiModeNightTileConfig private val mapper by lazy { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt index 2c81f39a03ec..ed3fc5058c44 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt @@ -26,12 +26,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.impl.work.domain.model.WorkModeTileModel import com.android.systemui.qs.tiles.impl.work.qsWorkModeTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock @@ -43,7 +43,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class WorkModeTileMapperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val qsTileConfig = kosmos.qsWorkModeTileConfig private val devicePolicyManager = kosmos.devicePolicyManager private val testLabel = context.getString(R.string.quick_settings_work_mode_label) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt index a8b005fb6554..12ed3f0c96fe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt @@ -30,7 +30,6 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorI import com.android.systemui.coroutines.collectLastValue import com.android.systemui.display.data.repository.displayStateRepository import com.android.systemui.dump.DumpManager -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope @@ -40,6 +39,7 @@ import com.android.systemui.qs.dagger.QSSceneComponent import com.android.systemui.settings.brightness.MirrorController import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.shade.domain.interactor.shadeModeInteractor +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture @@ -65,7 +65,7 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class QSSceneAdapterImplTest : SysuiTestCase() { - private val kosmos = Kosmos().apply { testCase = this@QSSceneAdapterImplTest } + private val kosmos = testKosmos().apply { testCase = this@QSSceneAdapterImplTest } private val testDispatcher = kosmos.testDispatcher private val testScope = kosmos.testScope diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt index 7bcaeabfee69..390a5d8efff1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt @@ -28,12 +28,12 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.dialogTransitionAnimator import com.android.systemui.concurrency.fakeExecutor -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor import com.android.systemui.settings.UserContextProvider import com.android.systemui.settings.userFileManager import com.android.systemui.settings.userTracker +import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeGlobalSettings import com.android.traceur.TraceConfig import com.google.common.truth.Truth @@ -52,7 +52,7 @@ import org.mockito.kotlin.verify @TestableLooper.RunWithLooper(setAsMainLooper = true) class IssueRecordingServiceSessionTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().also { it.testCase = this } private val bgExecutor = kosmos.fakeExecutor private val userContextProvider: UserContextProvider = kosmos.userTracker private val dialogTransitionAnimator: DialogTransitionAnimator = kosmos.dialogTransitionAnimator diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt index 83bdcd2161ee..0510e6ca3ced 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt @@ -22,9 +22,9 @@ import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.settings.userFileManager import com.android.systemui.settings.userTracker +import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeGlobalSettings import com.google.common.truth.Truth import org.junit.Before @@ -40,7 +40,7 @@ import org.mockito.kotlin.verify @TestableLooper.RunWithLooper(setAsMainLooper = true) class IssueRecordingStateTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private lateinit var underTest: IssueRecordingState @Mock private lateinit var resolver: ContentResolver diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/ScreenRecordingStartTimeStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/ScreenRecordingStartTimeStoreTest.kt index 737b10166199..6f0dd16eacd4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/ScreenRecordingStartTimeStoreTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/ScreenRecordingStartTimeStoreTest.kt @@ -20,10 +20,10 @@ import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.settings.UserTracker import com.android.systemui.settings.userTracker +import com.android.systemui.testKosmos import com.google.common.truth.Truth import org.junit.Before import org.junit.Test @@ -34,7 +34,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class ScreenRecordingStartTimeStoreTest : SysuiTestCase() { - private val userTracker: UserTracker = Kosmos().also { it.testCase = this }.userTracker + private val userTracker: UserTracker = testKosmos().also { it.testCase = this }.userTracker private lateinit var underTest: ScreenRecordingStartTimeStore diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt index a82a7de75cc0..7e9487b04862 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt @@ -25,7 +25,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.shared.model.StatusBarState -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest @@ -38,6 +37,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.activeNotif import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.notification.init.NotificationsController import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor +import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -57,7 +57,7 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val testDispatcher = StandardTestDispatcher() private val iStatusBarService = mock<IStatusBarService>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt index 9724974e3044..bd5416676c8a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt @@ -21,10 +21,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.screenrecord.RecordingController import com.android.systemui.screenrecord.data.model.ScreenRecordModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -39,7 +39,7 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class ScreenRecordRepositoryTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val recordingController = mock<RecordingController>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 268d62952fc7..3788049256a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -41,6 +41,8 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper.RunWithLooper; import android.view.View; @@ -76,6 +78,7 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.google.common.util.concurrent.MoreExecutors; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -94,6 +97,9 @@ import java.util.concurrent.Executor; @RunWithLooper(setAsMainLooper = true) @SmallTest public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { + @Rule public final CheckFlagsRule checkFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Mock private ViewCaptureAwareWindowManager mWindowManager; @Mock private DozeParameters mDozeParameters; @Spy private final NotificationShadeWindowView mNotificationShadeWindowView = spy( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index c6ce58185cf0..0c90d077273d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -25,7 +25,6 @@ import com.android.internal.statusbar.IStatusBarService import com.android.systemui.SysuiTestCase import com.android.systemui.assist.AssistManager import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.log.LogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -43,6 +42,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.statusbar.window.StatusBarWindowControllerStore +import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -63,7 +63,7 @@ import org.mockito.MockitoAnnotations @SmallTest class ShadeControllerImplTest : SysuiTestCase() { private val executor = FakeExecutor(FakeSystemClock()) - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope @Mock private lateinit var commandQueue: CommandQueue diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt index 054c1b8207eb..32eec56af8ea 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt @@ -27,7 +27,6 @@ import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -35,6 +34,7 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow @@ -52,7 +52,7 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) @EnableSceneContainer class ShadeControllerSceneImplTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val sceneInteractor by lazy { kosmos.sceneInteractor } private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index ddad230f04e9..2f2fafab53d5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -25,8 +25,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.PluginLifecycleManager import com.android.systemui.plugins.PluginListener import com.android.systemui.plugins.PluginManager +import com.android.systemui.plugins.clocks.ClockAxisStyle import com.android.systemui.plugins.clocks.ClockController -import com.android.systemui.plugins.clocks.ClockFontAxisSetting import com.android.systemui.plugins.clocks.ClockId import com.android.systemui.plugins.clocks.ClockMessageBuffers import com.android.systemui.plugins.clocks.ClockMetadata @@ -543,7 +543,7 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun jsonDeserialization_fontAxes() { - val expected = ClockSettings(axes = listOf(ClockFontAxisSetting("KEY", 10f))) + val expected = ClockSettings(axes = ClockAxisStyle("KEY", 10f)) val json = JSONObject("""{"axes":[{"key":"KEY","value":10}]}""") val actual = ClockSettings.fromJson(json) assertEquals(expected, actual) @@ -576,7 +576,7 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun jsonSerialization_axisSettings() { - val settings = ClockSettings(axes = listOf(ClockFontAxisSetting("KEY", 10f))) + val settings = ClockSettings(axes = ClockAxisStyle("KEY", 10f)) val actual = ClockSettings.toJson(settings) val expected = JSONObject("""{"metadata":{},"axes":[{"key":"KEY","value":10}]}""") assertEquals(expected.toString(), actual.toString()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index 01046cd10d87..3c19179bc1c2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -227,7 +227,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { if (NotificationBundleUi.isEnabled()) { return row.getEntryAdapter().getSbn().getNotification(); } else { - return row.getEntry().getSbn().getNotification(); + return row.getEntryLegacy().getSbn().getNotification(); } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index 326d8ffd3c7c..3ecf302204bc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -32,14 +32,12 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.res.R import com.android.systemui.shade.ShadeExpansionChangeEvent +import com.android.systemui.shade.domain.interactor.ShadeModeInteractor import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScrimController -import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.testKosmos import com.android.systemui.util.WallpaperController import com.android.systemui.util.mockito.eq @@ -77,7 +75,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { private val kosmos = testKosmos() private val applicationScope = kosmos.testScope.backgroundScope - @Mock private lateinit var windowRootViewBlurInteractor: WindowRootViewBlurInteractor @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var blurUtils: BlurUtils @Mock private lateinit var biometricUnlockController: BiometricUnlockController @@ -87,6 +84,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Mock private lateinit var wallpaperController: WallpaperController @Mock private lateinit var wallpaperInteractor: WallpaperInteractor @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock private lateinit var windowRootViewBlurInteractor: WindowRootViewBlurInteractor + @Mock private lateinit var shadeModeInteractor: ShadeModeInteractor @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var appZoomOutOptional: Optional<AppZoomOut> @Mock private lateinit var root: View @@ -103,7 +102,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { private var statusBarState = StatusBarState.SHADE private val maxBlur = 150 private lateinit var notificationShadeDepthController: NotificationShadeDepthController - private val configurationController = FakeConfigurationController() @Before fun setup() { @@ -133,13 +131,11 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { wallpaperInteractor, notificationShadeWindowController, dozeParameters, - context, - ResourcesSplitShadeStateController(), + shadeModeInteractor, windowRootViewBlurInteractor, appZoomOutOptional, applicationScope, - dumpManager, - configurationController, + dumpManager ) notificationShadeDepthController.shadeAnimation = shadeAnimation notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring @@ -492,15 +488,10 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { } private fun enableSplitShade() { - setSplitShadeEnabled(true) + `when` (shadeModeInteractor.isSplitShade).thenReturn(true) } private fun disableSplitShade() { - setSplitShadeEnabled(false) - } - - private fun setSplitShadeEnabled(enabled: Boolean) { - overrideResource(R.bool.config_use_split_notification_shade, enabled) - configurationController.notifyConfigurationChanged() + `when` (shadeModeInteractor.isSplitShade).thenReturn(false) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt index 03dee3aeb75c..72d21f1064af 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt @@ -24,13 +24,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.testKosmos import com.android.systemui.tuner.TunerService import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.kotlin.JavaAdapter @@ -54,7 +54,7 @@ class OperatorNameViewControllerTest : SysuiTestCase() { private lateinit var underTest: OperatorNameViewController private lateinit var airplaneModeInteractor: AirplaneModeInteractor - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = TestScope() private val view = OperatorNameView(mContext) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt index 485b9febc284..2fa9a02b9e87 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt @@ -17,12 +17,17 @@ package com.android.systemui.statusbar.chips.call.ui.viewmodel import android.app.PendingIntent +import android.content.ComponentName +import android.content.Intent import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.data.repository.activityManagerRepository +import com.android.systemui.activity.data.repository.fake +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Icon @@ -51,6 +56,7 @@ import com.google.common.truth.Truth.assertThat import kotlin.test.Test import org.junit.runner.RunWith import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -481,6 +487,294 @@ class CallChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null) } + @Test + @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME) + @EnableChipsModernization + fun chipWithReturnAnimation_updatesCorrectly_withStateAndTransitionState() = + kosmos.runTest { + val pendingIntent = mock<PendingIntent>() + val intent = mock<Intent>() + whenever(pendingIntent.intent).thenReturn(intent) + val component = mock<ComponentName>() + whenever(intent.component).thenReturn(component) + + val expandable = mock<Expandable>() + val activityController = mock<ActivityTransitionAnimator.Controller>() + whenever( + expandable.activityTransitionController( + anyOrNull(), + anyOrNull(), + any(), + anyOrNull(), + any(), + ) + ) + .thenReturn(activityController) + + val latest by collectLastValue(underTest.chip) + + // Start off with no call. + removeOngoingCallState(key = NOTIFICATION_KEY) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java) + assertThat(latest!!.transitionManager!!.controllerFactory).isNull() + + // Call starts [NoCall -> InCall(isAppVisible=true), NoTransition]. + addOngoingCallState( + key = NOTIFICATION_KEY, + startTimeMs = 345, + contentIntent = pendingIntent, + uid = NOTIFICATION_UID, + isAppVisible = true, + ) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isTrue() + val factory = latest!!.transitionManager!!.controllerFactory + assertThat(factory!!.component).isEqualTo(component) + + // Request a return transition [InCall(isAppVisible=true), NoTransition -> + // ReturnRequested]. + factory.onCompose(expandable) + var controller = factory.createController(forLaunch = false) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + assertThat(latest!!.transitionManager!!.controllerFactory).isEqualTo(factory) + + // Start the return transition [InCall(isAppVisible=true), ReturnRequested -> + // Returning]. + controller.onTransitionAnimationStart(isExpandingFullyAbove = false) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + assertThat(latest!!.transitionManager!!.controllerFactory).isEqualTo(factory) + + // End the return transition [InCall(isAppVisible=true), Returning -> NoTransition]. + controller.onTransitionAnimationEnd(isExpandingFullyAbove = false) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + assertThat(latest!!.transitionManager!!.controllerFactory).isEqualTo(factory) + + // Settle the return transition [InCall(isAppVisible=true) -> + // InCall(isAppVisible=false), NoTransition]. + kosmos.activityManagerRepository.fake.setIsAppVisible(NOTIFICATION_UID, false) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + assertThat(latest!!.transitionManager!!.controllerFactory).isEqualTo(factory) + + // Trigger a launch transition [InCall(isAppVisible=false) -> InCall(isAppVisible=true), + // NoTransition]. + kosmos.activityManagerRepository.fake.setIsAppVisible(NOTIFICATION_UID, true) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + assertThat(latest!!.transitionManager!!.controllerFactory).isEqualTo(factory) + + // Request the return transition [InCall(isAppVisible=true), NoTransition -> + // LaunchRequested]. + controller = factory.createController(forLaunch = true) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + assertThat(latest!!.transitionManager!!.controllerFactory).isEqualTo(factory) + + // Start the return transition [InCall(isAppVisible=true), LaunchRequested -> + // Launching]. + controller.onTransitionAnimationStart(isExpandingFullyAbove = false) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + assertThat(latest!!.transitionManager!!.controllerFactory).isEqualTo(factory) + + // End the return transition [InCall(isAppVisible=true), Launching -> NoTransition]. + controller.onTransitionAnimationStart(isExpandingFullyAbove = false) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + assertThat(latest!!.transitionManager!!.controllerFactory).isEqualTo(factory) + + // End the call with the app visible [InCall(isAppVisible=true) -> NoCall, + // NoTransition]. + removeOngoingCallState(key = NOTIFICATION_KEY) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java) + assertThat(latest!!.transitionManager!!.controllerFactory).isNull() + + // End the call with the app hidden [InCall(isAppVisible=false) -> NoCall, + // NoTransition]. + addOngoingCallState( + key = NOTIFICATION_KEY, + startTimeMs = 345, + contentIntent = pendingIntent, + isAppVisible = false, + ) + removeOngoingCallState(key = NOTIFICATION_KEY) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java) + assertThat(latest!!.transitionManager!!.controllerFactory).isNull() + } + + @Test + @DisableFlags(StatusBarChipsReturnAnimations.FLAG_NAME) + fun chipLegacy_hasNoTransitionAnimationInformation() = + kosmos.runTest { + val latest by collectLastValue(underTest.chip) + + // NoCall + removeOngoingCallState(key = NOTIFICATION_KEY) + assertThat(latest!!.transitionManager).isNull() + + // InCall with visible app + addOngoingCallState( + key = NOTIFICATION_KEY, + startTimeMs = 345, + uid = NOTIFICATION_UID, + isAppVisible = true, + ) + assertThat(latest!!.transitionManager).isNull() + + // InCall with hidden app + kosmos.activityManagerRepository.fake.setIsAppVisible(NOTIFICATION_UID, false) + assertThat(latest!!.transitionManager).isNull() + } + + @Test + @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME) + @EnableChipsModernization + fun chipWithReturnAnimation_chipDataChangesMidTransition() = + kosmos.runTest { + val pendingIntent = mock<PendingIntent>() + val intent = mock<Intent>() + whenever(pendingIntent.intent).thenReturn(intent) + val component = mock<ComponentName>() + whenever(intent.component).thenReturn(component) + + val expandable = mock<Expandable>() + val activityController = mock<ActivityTransitionAnimator.Controller>() + whenever( + expandable.activityTransitionController( + anyOrNull(), + anyOrNull(), + any(), + anyOrNull(), + any(), + ) + ) + .thenReturn(activityController) + + val latest by collectLastValue(underTest.chip) + + // Start with the app visible and trigger a return animation. + addOngoingCallState( + key = NOTIFICATION_KEY, + startTimeMs = 345, + contentIntent = pendingIntent, + uid = NOTIFICATION_UID, + isAppVisible = true, + ) + var factory = latest!!.transitionManager!!.controllerFactory!! + factory.onCompose(expandable) + var controller = factory.createController(forLaunch = false) + controller.onTransitionAnimationStart(isExpandingFullyAbove = false) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + + // The chip changes state. + addOngoingCallState( + key = NOTIFICATION_KEY, + startTimeMs = 0, + contentIntent = pendingIntent, + uid = NOTIFICATION_UID, + isAppVisible = true, + ) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + + // Reset the state and trigger a launch animation. + controller.onTransitionAnimationEnd(isExpandingFullyAbove = false) + addOngoingCallState( + key = NOTIFICATION_KEY, + startTimeMs = 345, + contentIntent = pendingIntent, + uid = NOTIFICATION_UID, + isAppVisible = true, + ) + factory = latest!!.transitionManager!!.controllerFactory!! + factory.onCompose(expandable) + controller = factory.createController(forLaunch = true) + controller.onTransitionAnimationStart(isExpandingFullyAbove = false) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + + // The chip changes state. + addOngoingCallState( + key = NOTIFICATION_KEY, + startTimeMs = -2, + contentIntent = pendingIntent, + uid = NOTIFICATION_UID, + isAppVisible = true, + ) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + } + + @Test + @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME) + @EnableChipsModernization + fun chipWithReturnAnimation_chipDisappearsMidTransition() = + kosmos.runTest { + val pendingIntent = mock<PendingIntent>() + val intent = mock<Intent>() + whenever(pendingIntent.intent).thenReturn(intent) + val component = mock<ComponentName>() + whenever(intent.component).thenReturn(component) + + val expandable = mock<Expandable>() + val activityController = mock<ActivityTransitionAnimator.Controller>() + whenever( + expandable.activityTransitionController( + anyOrNull(), + anyOrNull(), + any(), + anyOrNull(), + any(), + ) + ) + .thenReturn(activityController) + + val latest by collectLastValue(underTest.chip) + + // Start with the app visible and trigger a return animation. + addOngoingCallState( + key = NOTIFICATION_KEY, + startTimeMs = 345, + contentIntent = pendingIntent, + uid = NOTIFICATION_UID, + isAppVisible = true, + ) + var factory = latest!!.transitionManager!!.controllerFactory!! + factory.onCompose(expandable) + var controller = factory.createController(forLaunch = false) + controller.onTransitionAnimationStart(isExpandingFullyAbove = false) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + + // The chip disappears. + removeOngoingCallState(key = NOTIFICATION_KEY) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java) + + // Reset the state and trigger a launch animation. + controller.onTransitionAnimationEnd(isExpandingFullyAbove = false) + addOngoingCallState( + key = NOTIFICATION_KEY, + startTimeMs = 345, + contentIntent = pendingIntent, + uid = NOTIFICATION_UID, + isAppVisible = true, + ) + factory = latest!!.transitionManager!!.controllerFactory!! + factory.onCompose(expandable) + controller = factory.createController(forLaunch = true) + controller.onTransitionAnimationStart(isExpandingFullyAbove = false) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + + // The chip disappears. + removeOngoingCallState(key = NOTIFICATION_KEY) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java) + } + companion object { fun createStatusBarIconViewOrNull(): StatusBarIconView? = if (StatusBarConnectedDisplays.isEnabled) { @@ -500,6 +794,8 @@ class CallChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { } .build() + private const val NOTIFICATION_KEY = "testKey" + private const val NOTIFICATION_UID = 12345 private const val PROMOTED_BACKGROUND_COLOR = 65 private const val PROMOTED_PRIMARY_TEXT_COLOR = 98 diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt index b2174c1b1d8c..21a4560dafee 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt @@ -20,12 +20,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.mediarouter.data.repository.fakeMediaRouterRepository import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel import com.android.systemui.statusbar.policy.CastDevice +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.test.runCurrent @@ -35,7 +35,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class MediaRouterChipInteractorTest : SysuiTestCase() { - val kosmos = Kosmos() + val kosmos = testKosmos() val testScope = kosmos.testScope val mediaRouterRepository = kosmos.fakeMediaRouterRepository diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt index 274efbb50788..568129706458 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt @@ -30,7 +30,6 @@ import android.view.Window import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.data.model.MediaProjectionState @@ -41,6 +40,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.me import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.test.runCurrent @@ -57,7 +57,7 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class EndCastScreenToOtherDeviceDialogDelegateTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().also { it.testCase = this } private val sysuiDialog = mock<SystemUIDialog>() private lateinit var underTest: EndCastScreenToOtherDeviceDialogDelegate diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt index 88207d1c61d8..30a415cb9906 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt @@ -26,7 +26,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.mediarouter.data.repository.fakeMediaRouterRepository @@ -35,6 +34,7 @@ import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor. import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.CastDevice +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.test.runCurrent @@ -51,7 +51,7 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class EndGenericCastToOtherDeviceDialogDelegateTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().also { it.testCase = this } private val sysuiDialog = mock<SystemUIDialog>() private lateinit var underTest: EndGenericCastToOtherDeviceDialogDelegate diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt index ccc844ad5837..d921ab3b284d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt @@ -30,7 +30,6 @@ import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.data.model.MediaProjectionState @@ -52,6 +51,7 @@ import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization import com.android.systemui.statusbar.policy.CastDevice +import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import kotlin.test.Test @@ -69,7 +69,7 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().also { it.testCase = this } private val testScope = kosmos.testScope private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository private val mediaRouterRepo = kosmos.fakeMediaRouterRepository diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt index 795988f0087f..fac50b38d838 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt @@ -24,12 +24,12 @@ import android.content.pm.PackageManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import org.junit.runner.RunWith @@ -42,7 +42,7 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class EndMediaProjectionDialogHelperTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().also { it.testCase = this } private val underTest = kosmos.endMediaProjectionDialogHelper diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index 4993b5661373..b5cfc7e9080d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -573,6 +573,8 @@ class NotifChipsViewModelTest : SysuiTestCase() { assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java) assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).startTimeMs) .isEqualTo(whenElapsed) + assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).isEventInFuture) + .isFalse() } @Test @@ -608,6 +610,8 @@ class NotifChipsViewModelTest : SysuiTestCase() { assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java) assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).startTimeMs) .isEqualTo(whenElapsed) + assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).isEventInFuture) + .isTrue() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt index f560ee7730ae..981c9525f636 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt @@ -30,7 +30,6 @@ import android.view.Window import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask @@ -39,6 +38,7 @@ import com.android.systemui.screenrecord.data.repository.screenRecordRepository import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.screenRecordChipInteractor import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.test.runCurrent @@ -55,7 +55,7 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class EndScreenRecordingDialogDelegateTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().also { it.testCase = this } private val sysuiDialog = mock<SystemUIDialog>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegateTest.kt index 95aa6cd3250b..b2e90ecbeef1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegateTest.kt @@ -30,7 +30,6 @@ import android.view.Window import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.data.model.MediaProjectionState @@ -41,6 +40,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.me import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.test.runCurrent @@ -57,7 +57,7 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class EndShareScreenToAppDialogDelegateTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().also { it.testCase = this } private val sysuiDialog = mock<SystemUIDialog>() private lateinit var underTest: EndShareScreenToAppDialogDelegate diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt index e3a84fd2c2eb..6d91fb5a10cb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt @@ -22,12 +22,12 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.flow.MutableStateFlow @@ -39,7 +39,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ChipTransitionHelperTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = kosmos.testScope @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt index 4e92540396d3..cd9970cfa614 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt @@ -35,55 +35,153 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ChronometerStateTest : SysuiTestCase() { - private lateinit var mockTimeSource: MutableTimeSource + private lateinit var fakeTimeSource: MutableTimeSource @Before fun setup() { - mockTimeSource = MutableTimeSource() + fakeTimeSource = MutableTimeSource() } @Test - fun initialText_isCorrect() = runTest { - val state = ChronometerState(mockTimeSource, 0L) - assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(0)) + fun initialText_isEventInFutureFalse_timeIsNow() = runTest { + fakeTimeSource.time = 3_000 + val state = + ChronometerState(fakeTimeSource, eventTimeMillis = 3_000, isEventInFuture = false) + assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 0)) } @Test - fun textUpdates_withTime() = runTest { - val startTime = 1000L - val state = ChronometerState(mockTimeSource, startTime) + fun initialText_isEventInFutureFalse_timeInPast() = runTest { + fakeTimeSource.time = 3_000 + val state = + ChronometerState(fakeTimeSource, eventTimeMillis = 1_000, isEventInFuture = false) + assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 2)) + } + + @Test + fun initialText_isEventInFutureFalse_timeInFuture() = runTest { + fakeTimeSource.time = 3_000 + val state = + ChronometerState(fakeTimeSource, eventTimeMillis = 5_000, isEventInFuture = false) + // When isEventInFuture=false, eventTimeMillis needs to be in the past if we want text to + // show + assertThat(state.currentTimeText).isNull() + } + + @Test + fun initialText_isEventInFutureTrue_timeIsNow() = runTest { + fakeTimeSource.time = 3_000 + val state = + ChronometerState(fakeTimeSource, eventTimeMillis = 3_000, isEventInFuture = true) + assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 0)) + } + + @Test + fun initialText_isEventInFutureTrue_timeInFuture() = runTest { + fakeTimeSource.time = 3_000 + val state = + ChronometerState(fakeTimeSource, eventTimeMillis = 5_000, isEventInFuture = true) + assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 2)) + } + + @Test + fun initialText_isEventInFutureTrue_timeInPast() = runTest { + fakeTimeSource.time = 3_000 + val state = + ChronometerState(fakeTimeSource, eventTimeMillis = 1_000, isEventInFuture = true) + // When isEventInFuture=true, eventTimeMillis needs to be in the future if we want text to + // show + assertThat(state.currentTimeText).isNull() + } + + @Test + fun textUpdates_isEventInFutureFalse_timeInPast() = runTest { + val eventTime = 1000L + val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = false) val job = launch { state.run() } val elapsedTime = 5000L - mockTimeSource.time = startTime + elapsedTime + fakeTimeSource.time = eventTime + elapsedTime advanceTimeBy(elapsedTime) assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(elapsedTime / 1000)) + val additionalTime = 6000L + fakeTimeSource.time += additionalTime + advanceTimeBy(additionalTime) + assertThat(state.currentTimeText) + .isEqualTo(formatElapsedTime((elapsedTime + additionalTime) / 1000)) + job.cancelAndJoin() } @Test - fun textUpdates_toLargerValue() = runTest { - val startTime = 1000L - val state = ChronometerState(mockTimeSource, startTime) + fun textUpdates_isEventInFutureFalse_timeChangesFromFutureToPast() = runTest { + val eventTime = 15_000L + val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = false) val job = launch { state.run() } - val elapsedTime = 15000L - mockTimeSource.time = startTime + elapsedTime - advanceTimeBy(elapsedTime) - assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(elapsedTime / 1000)) + // WHEN the time is 5 but the eventTime is 15 + fakeTimeSource.time = 5_000L + advanceTimeBy(5_000L) + // THEN no text is shown + assertThat(state.currentTimeText).isNull() + + // WHEN the time advances to 40 + fakeTimeSource.time = 40_000L + advanceTimeBy(35_000) + // THEN text is shown as 25 seconds (40 - 15) + assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 25)) job.cancelAndJoin() } @Test - fun textUpdates_afterResettingBase() = runTest { + fun textUpdates_isEventInFutureTrue_timeInFuture() = runTest { + val eventTime = 15_000L + val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true) + val job = launch { state.run() } + + fakeTimeSource.time = 5_000L + advanceTimeBy(5_000L) + assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10)) + + val additionalTime = 6000L + fakeTimeSource.time += additionalTime + advanceTimeBy(additionalTime) + assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 4)) + + job.cancelAndJoin() + } + + @Test + fun textUpdates_isEventInFutureTrue_timeChangesFromFutureToPast() = runTest { + val eventTime = 15_000L + val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true) + val job = launch { state.run() } + + // WHEN the time is 5 and the eventTime is 15 + fakeTimeSource.time = 5_000L + advanceTimeBy(5_000L) + // THEN 10 seconds is shown + assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10)) + + // WHEN the time advances to 40 (past the event time) + fakeTimeSource.time = 40_000L + advanceTimeBy(35_000) + // THEN no text is shown + assertThat(state.currentTimeText).isNull() + + job.cancelAndJoin() + } + + @Test + fun textUpdates_afterResettingBase_isEventInFutureFalse() = runTest { val initialElapsedTime = 30000L val startTime = 50000L - val state = ChronometerState(mockTimeSource, startTime) + val state = ChronometerState(fakeTimeSource, startTime, isEventInFuture = false) val job = launch { state.run() } - mockTimeSource.time = startTime + initialElapsedTime + fakeTimeSource.time = startTime + initialElapsedTime advanceTimeBy(initialElapsedTime) assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(initialElapsedTime / 1000)) @@ -91,15 +189,68 @@ class ChronometerStateTest : SysuiTestCase() { val newElapsedTime = 5000L val newStartTime = 100000L - val newState = ChronometerState(mockTimeSource, newStartTime) + val newState = ChronometerState(fakeTimeSource, newStartTime, isEventInFuture = false) val newJob = launch { newState.run() } - mockTimeSource.time = newStartTime + newElapsedTime + fakeTimeSource.time = newStartTime + newElapsedTime advanceTimeBy(newElapsedTime) assertThat(newState.currentTimeText).isEqualTo(formatElapsedTime(newElapsedTime / 1000)) newJob.cancelAndJoin() } + + @Test + fun textUpdates_afterResettingBase_isEventInFutureTrue() = runTest { + val initialElapsedTime = 40_000L + val eventTime = 50_000L + val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true) + val job = launch { state.run() } + + fakeTimeSource.time = initialElapsedTime + advanceTimeBy(initialElapsedTime) + // Time should be 50 - 40 = 10 + assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10)) + + job.cancelAndJoin() + + val newElapsedTime = 75_000L + val newEventTime = 100_000L + val newState = ChronometerState(fakeTimeSource, newEventTime, isEventInFuture = true) + val newJob = launch { newState.run() } + + fakeTimeSource.time = newElapsedTime + advanceTimeBy(newElapsedTime - initialElapsedTime) + // Time should be 100 - 75 = 25 + assertThat(newState.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 25)) + + newJob.cancelAndJoin() + } + + @Test + fun textUpdates_afterResettingisEventInFuture() = runTest { + val initialElapsedTime = 40_000L + val eventTime = 50_000L + val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true) + val job = launch { state.run() } + + fakeTimeSource.time = initialElapsedTime + advanceTimeBy(initialElapsedTime) + // Time should be 50 - 40 = 10 + assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10)) + + job.cancelAndJoin() + + val newElapsedTime = 70_000L + val newState = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = false) + val newJob = launch { newState.run() } + + fakeTimeSource.time = newElapsedTime + advanceTimeBy(newElapsedTime - initialElapsedTime) + // Time should be 70 - 50 = 20 + assertThat(newState.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 20)) + + newJob.cancelAndJoin() + } } /** A fake implementation of [TimeSource] that allows the caller to set the current time */ diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt index f06244f4f637..7135cf01394a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt @@ -931,7 +931,40 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { } @Test - fun visibleChipKeys_fourPromotedNotifs_topThreeInList() = + @DisableChipsModernization + fun visibleChipKeys_chipsModOff_threePromotedNotifs_topTwoInList() = + kosmos.runTest { + val latest by collectLastValue(underTest.visibleChipKeys) + + setNotifs( + listOf( + activeNotificationModel( + key = "firstNotif", + statusBarChipIcon = createStatusBarIconViewOrNull(), + promotedContent = + PromotedNotificationContentModel.Builder("firstNotif").build(), + ), + activeNotificationModel( + key = "secondNotif", + statusBarChipIcon = createStatusBarIconViewOrNull(), + promotedContent = + PromotedNotificationContentModel.Builder("secondNotif").build(), + ), + activeNotificationModel( + key = "thirdNotif", + statusBarChipIcon = createStatusBarIconViewOrNull(), + promotedContent = + PromotedNotificationContentModel.Builder("thirdNotif").build(), + ), + ) + ) + + assertThat(latest).containsExactly("firstNotif", "secondNotif").inOrder() + } + + @Test + @EnableChipsModernization + fun visibleChipKeys_chipsModOn_fourPromotedNotifs_topThreeInList() = kosmos.runTest { val latest by collectLastValue(underTest.visibleChipKeys) @@ -1069,7 +1102,37 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { } @Test - fun visibleChipKeys_screenRecordAndCallAndPromotedNotifs_topThreeInList() = + @DisableChipsModernization + fun visibleChipKeys_chipsModOff_screenRecordAndCallAndPromotedNotifs_topTwoInList() = + kosmos.runTest { + val latest by collectLastValue(underTest.visibleChipKeys) + + val callNotificationKey = "call" + addOngoingCallState(callNotificationKey) + screenRecordState.value = ScreenRecordModel.Recording + activeNotificationListRepository.addNotif( + activeNotificationModel( + key = "notif1", + statusBarChipIcon = createStatusBarIconViewOrNull(), + promotedContent = PromotedNotificationContentModel.Builder("notif1").build(), + ) + ) + activeNotificationListRepository.addNotif( + activeNotificationModel( + key = "notif2", + statusBarChipIcon = createStatusBarIconViewOrNull(), + promotedContent = PromotedNotificationContentModel.Builder("notif2").build(), + ) + ) + + assertThat(latest) + .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey) + .inOrder() + } + + @Test + @EnableChipsModernization + fun visibleChipKeys_chipsModOn_screenRecordAndCallAndPromotedNotifs_topThreeInList() = kosmos.runTest { val latest by collectLastValue(underTest.visibleChipKeys) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt index 2f6bedb42e45..ea61b715475d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt @@ -29,7 +29,6 @@ import com.android.internal.statusbar.LetterboxDetails import com.android.internal.view.AppearanceRegion import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.data.model.StatusBarMode import com.android.systemui.statusbar.layout.BoundsPair @@ -42,6 +41,7 @@ import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture @@ -59,7 +59,7 @@ import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidJUnit4::class) class StatusBarModeRepositoryImplTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope = TestScope() private val commandQueue = mock<CommandQueue>() private val letterboxAppearanceCalculator = mock<LetterboxAppearanceCalculator>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 790b2c343a11..bfd700dcc302 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -58,6 +58,7 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; +import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi; import com.android.systemui.util.time.FakeSystemClock; @@ -151,7 +152,8 @@ public class NotificationEntryTest extends SysuiTestCase { .build(); NotificationEntry entry = - new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); + new NotificationEntry(sbn, ranking, + UseElapsedRealtimeForCreationTime.getCurrentTime(mClock)); assertFalse(entry.isBlockable()); } @@ -251,7 +253,8 @@ public class NotificationEntryTest extends SysuiTestCase { .build(); NotificationEntry entry = - new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); + new NotificationEntry(sbn, ranking, + UseElapsedRealtimeForCreationTime.getCurrentTime(mClock)); assertEquals(systemGeneratedSmartActions, entry.getSmartActions()); assertEquals(NOTIFICATION_CHANNEL, entry.getChannel()); @@ -365,7 +368,8 @@ public class NotificationEntryTest extends SysuiTestCase { .setKey(sbn.getKey()) .build(); NotificationEntry entry = - new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); + new NotificationEntry(sbn, ranking, + UseElapsedRealtimeForCreationTime.getCurrentTime(mClock)); assertFalse(entry.isChannelVisibilityPrivate()); } @@ -378,7 +382,8 @@ public class NotificationEntryTest extends SysuiTestCase { .setKey(sbn.getKey()) .build(); NotificationEntry entry = - new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); + new NotificationEntry(sbn, ranking, + UseElapsedRealtimeForCreationTime.getCurrentTime(mClock)); assertFalse(entry.isChannelVisibilityPrivate()); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.kt index ef0a4169d98e..d532010f4c55 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.kt @@ -50,6 +50,7 @@ import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable.PluggableListener import com.android.systemui.statusbar.notification.collection.notifPipeline @@ -323,7 +324,10 @@ class VisualStabilityCoordinatorTest(flags: FlagsParameterization) : SysuiTestCa setPulsing(true) // WHEN we temporarily allow section changes for this notification entry - underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.currentTimeMillis()) + underTest.temporarilyAllowSectionChanges( + entry, + UseElapsedRealtimeForCreationTime.getCurrentTime(fakeSystemClock), + ) // THEN group changes aren't allowed assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isFalse() @@ -349,7 +353,10 @@ class VisualStabilityCoordinatorTest(flags: FlagsParameterization) : SysuiTestCa setPulsing(false) // WHEN we temporarily allow section changes for this notification entry - underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.uptimeMillis()) + underTest.temporarilyAllowSectionChanges( + entry, + UseElapsedRealtimeForCreationTime.getCurrentTime(fakeSystemClock), + ) // THEN the notification list is invalidated verifyStabilityManagerWasInvalidated(times(1)) @@ -365,7 +372,10 @@ class VisualStabilityCoordinatorTest(flags: FlagsParameterization) : SysuiTestCa setPulsing(false) // WHEN we temporarily allow section changes for this notification entry - underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.currentTimeMillis()) + underTest.temporarilyAllowSectionChanges( + entry, + UseElapsedRealtimeForCreationTime.getCurrentTime(fakeSystemClock), + ) // THEN invalidate is not called because this entry was never suppressed from reordering verifyStabilityManagerWasInvalidated(never()) @@ -382,7 +392,10 @@ class VisualStabilityCoordinatorTest(flags: FlagsParameterization) : SysuiTestCa assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isTrue() // WHEN we temporarily allow section changes for this notification entry - underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.currentTimeMillis()) + underTest.temporarilyAllowSectionChanges( + entry, + UseElapsedRealtimeForCreationTime.getCurrentTime(fakeSystemClock), + ) // THEN invalidate is not called because this entry was never suppressed from // reordering; @@ -415,7 +428,10 @@ class VisualStabilityCoordinatorTest(flags: FlagsParameterization) : SysuiTestCa setPulsing(true) // WHEN we temporarily allow section changes for this notification entry - underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.currentTimeMillis()) + underTest.temporarilyAllowSectionChanges( + entry, + UseElapsedRealtimeForCreationTime.getCurrentTime(fakeSystemClock), + ) // can now reorder, so invalidates verifyStabilityManagerWasInvalidated(times(1)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt index 3116143504eb..893c17998a17 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt @@ -376,12 +376,67 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun extractContent_fromBigTextStyle() { - val entry = createEntry { setStyle(BigTextStyle()) } + val entry = createEntry { + setContentTitle(TEST_CONTENT_TITLE) + setContentText(TEST_CONTENT_TEXT) + setStyle( + BigTextStyle() + .bigText(TEST_BIG_TEXT) + .setBigContentTitle(TEST_BIG_CONTENT_TITLE) + .setSummaryText(TEST_SUMMARY_TEXT) + ) + } + + val content = extractContent(entry) + + assertThat(content).isNotNull() + assertThat(content?.style).isEqualTo(Style.BigText) + assertThat(content?.title).isEqualTo(TEST_BIG_CONTENT_TITLE) + assertThat(content?.text).isEqualTo(TEST_BIG_TEXT) + } + + @Test + @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) + fun extractContent_fromBigTextStyle_fallbackToContentTitle() { + val entry = createEntry { + setContentTitle(TEST_CONTENT_TITLE) + setContentText(TEST_CONTENT_TEXT) + setStyle( + BigTextStyle() + .bigText(TEST_BIG_TEXT) + // bigContentTitle unset + .setSummaryText(TEST_SUMMARY_TEXT) + ) + } + + val content = extractContent(entry) + + assertThat(content).isNotNull() + assertThat(content?.style).isEqualTo(Style.BigText) + assertThat(content?.title).isEqualTo(TEST_CONTENT_TITLE) + assertThat(content?.text).isEqualTo(TEST_BIG_TEXT) + } + + @Test + @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) + fun extractContent_fromBigTextStyle_fallbackToContentText() { + val entry = createEntry { + setContentTitle(TEST_CONTENT_TITLE) + setContentText(TEST_CONTENT_TEXT) + setStyle( + BigTextStyle() + // bigText unset + .setBigContentTitle(TEST_BIG_CONTENT_TITLE) + .setSummaryText(TEST_SUMMARY_TEXT) + ) + } val content = extractContent(entry) assertThat(content).isNotNull() assertThat(content?.style).isEqualTo(Style.BigText) + assertThat(content?.title).isEqualTo(TEST_BIG_CONTENT_TITLE) + assertThat(content?.text).isEqualTo(TEST_CONTENT_TEXT) } @Test @@ -498,6 +553,10 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() { private const val TEST_CONTENT_TEXT = "content text" private const val TEST_SHORT_CRITICAL_TEXT = "short" + private const val TEST_BIG_CONTENT_TITLE = "big content title" + private const val TEST_BIG_TEXT = "big text" + private const val TEST_SUMMARY_TEXT = "summary text" + private const val TEST_PROGRESS = 50 private const val TEST_PROGRESS_MAX = 100 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 1b8d64d5483c..387c62d76083 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 @@ -71,6 +71,7 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.headsup.HeadsUpManager 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 @@ -80,6 +81,9 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.testKosmos import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.wmshell.BubblesManager +import java.util.Optional +import kotlin.test.assertNotNull +import kotlin.test.fail import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -107,9 +111,6 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters -import java.util.Optional -import kotlin.test.assertNotNull -import kotlin.test.fail /** Tests for [NotificationGutsManager]. */ @SmallTest @@ -149,6 +150,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( @Mock private lateinit var launcherApps: LauncherApps @Mock private lateinit var shortcutManager: ShortcutManager @Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController + @Mock private lateinit var packageDemotionInteractor: PackageDemotionInteractor @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier @Mock private lateinit var contextTracker: UserContextProvider @Mock private lateinit var bubblesManager: BubblesManager @@ -214,6 +216,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( launcherApps, shortcutManager, channelEditorDialogController, + packageDemotionInteractor, contextTracker, assistantFeedbackController, Optional.of(bubblesManager), @@ -509,6 +512,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( .setImportance(NotificationManager.IMPORTANCE_HIGH) .build() + whenever(row.canViewBeDismissed()).thenReturn(true) whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true) val statusBarNotification = entry.sbn gutsManager.initializeNotificationInfo(row, notificationInfoView) @@ -521,6 +525,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( any<NotificationIconStyleProvider>(), eq(onUserInteractionCallback), eq(channelEditorDialogController), + any<PackageDemotionInteractor>(), eq(statusBarNotification.packageName), any<NotificationChannel>(), eq(entry), @@ -530,6 +535,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( any<UiEventLogger>(), /* isDeviceProvisioned = */ eq(false), /* isNonblockable = */ eq(false), + /* isDismissable = */ eq(true), /* wasShownHighPriority = */ eq(true), eq(assistantFeedbackController), eq(metricsLogger), @@ -545,6 +551,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( 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 @@ -560,6 +567,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( any<NotificationIconStyleProvider>(), eq(onUserInteractionCallback), eq(channelEditorDialogController), + any<PackageDemotionInteractor>(), eq(statusBarNotification.packageName), any<NotificationChannel>(), eq(entry), @@ -569,6 +577,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( any<UiEventLogger>(), /* isDeviceProvisioned = */ eq(true), /* isNonblockable = */ eq(false), + /* isDismissable = */ eq(true), /* wasShownHighPriority = */ eq(false), eq(assistantFeedbackController), eq(metricsLogger), @@ -584,6 +593,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( 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 @@ -597,6 +607,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( any<NotificationIconStyleProvider>(), eq(onUserInteractionCallback), eq(channelEditorDialogController), + any<PackageDemotionInteractor>(), eq(statusBarNotification.packageName), any<NotificationChannel>(), eq(entry), @@ -606,6 +617,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( any<UiEventLogger>(), /* isDeviceProvisioned = */ eq(false), /* isNonblockable = */ eq(false), + /* isDismissable = */ eq(true), /* wasShownHighPriority = */ eq(false), eq(assistantFeedbackController), eq(metricsLogger), 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 96ae07035ed2..0ac5fe95957c 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 @@ -49,6 +49,7 @@ import android.view.View.GONE import android.view.View.VISIBLE import android.widget.ImageView import android.widget.TextView +import androidx.core.view.isVisible import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger @@ -57,17 +58,18 @@ import com.android.internal.logging.metricsLogger import com.android.internal.logging.uiEventLoggerFake import com.android.systemui.Dependency import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos 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.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +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.testKosmos import com.android.telecom.telecomManager import com.google.common.truth.Truth.assertThat import java.util.concurrent.CountDownLatch @@ -89,7 +91,7 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) @RunWithLooper class NotificationInfoTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().also { it.testCase = this } private lateinit var underTest: NotificationInfo private lateinit var notificationChannel: NotificationChannel @@ -105,6 +107,7 @@ class NotificationInfoTest : SysuiTestCase() { private val onUserInteractionCallback = mock<OnUserInteractionCallback>() private val mockINotificationManager = mock<INotificationManager>() private val channelEditorDialogController = mock<ChannelEditorDialogController>() + private val packageDemotionInteractor = mock<PackageDemotionInteractor>() private val assistantFeedbackController = mock<AssistantFeedbackController>() @Before @@ -863,6 +866,31 @@ class NotificationInfoTest : SysuiTestCase() { assertThat(underTest.findViewById<View>(R.id.feedback).visibility).isEqualTo(GONE) } + @Test + @Throws(RemoteException::class) + fun testDismissListenerBound() { + val latch = CountDownLatch(1) + bindNotification(onCloseClick = { _: View? -> latch.countDown() }) + + val dismissView = underTest.findViewById<View>(R.id.inline_dismiss) + assertThat(dismissView.isVisible).isTrue() + dismissView.performClick() + + // Verify that listener was triggered. + assertThat(latch.count).isEqualTo(0) + } + + @Test + @Throws(RemoteException::class) + fun testDismissHiddenWhenUndismissable() { + + entry.sbn.notification.flags = + entry.sbn.notification.flags or android.app.Notification.FLAG_NO_DISMISS + bindNotification(isDismissable = false) + val dismissView = underTest.findViewById<View>(R.id.inline_dismiss) + assertThat(dismissView.isVisible).isFalse() + } + private fun bindNotification( pm: PackageManager = this.mockPackageManager, iNotificationManager: INotificationManager = this.mockINotificationManager, @@ -871,6 +899,7 @@ class NotificationInfoTest : SysuiTestCase() { onUserInteractionCallback: OnUserInteractionCallback = this.onUserInteractionCallback, channelEditorDialogController: ChannelEditorDialogController = this.channelEditorDialogController, + packageDemotionInteractor: PackageDemotionInteractor = this.packageDemotionInteractor, pkg: String = TEST_PACKAGE_NAME, notificationChannel: NotificationChannel = this.notificationChannel, entry: NotificationEntry = this.entry, @@ -880,6 +909,7 @@ class NotificationInfoTest : SysuiTestCase() { uiEventLogger: UiEventLogger = this.uiEventLogger, isDeviceProvisioned: Boolean = true, isNonblockable: Boolean = false, + isDismissable: Boolean = true, wasShownHighPriority: Boolean = true, assistantFeedbackController: AssistantFeedbackController = this.assistantFeedbackController, metricsLogger: MetricsLogger = kosmos.metricsLogger, @@ -892,6 +922,7 @@ class NotificationInfoTest : SysuiTestCase() { iconStyleProvider, onUserInteractionCallback, channelEditorDialogController, + packageDemotionInteractor, pkg, notificationChannel, entry, @@ -901,6 +932,7 @@ class NotificationInfoTest : SysuiTestCase() { uiEventLogger, isDeviceProvisioned, isNonblockable, + isDismissable, wasShownHighPriority, assistantFeedbackController, metricsLogger, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java index af67a04d2f2a..2d4063b2f667 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java @@ -27,7 +27,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.TestableResources; import android.view.View; @@ -39,7 +38,6 @@ import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; @@ -93,7 +91,6 @@ public class NotificationSnoozeTest extends SysuiTestCase { } @Test - @EnableFlags(Flags.FLAG_NOTIFICATION_UNDO_GUTS_ON_CONFIG_CHANGED) public void closeControls_withoutSave_performsUndo() { ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions(); mUnderTest.mSelectedOption = options.getFirst(); 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 5638e0b434aa..209dfb2d2ed6 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 @@ -48,6 +48,7 @@ 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.statusbar.notification.collection.NotificationEntryBuilder; +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; @@ -92,6 +93,8 @@ public class PromotedNotificationInfoTest extends SysuiTestCase { @Mock private ChannelEditorDialogController mChannelEditorDialogController; @Mock + private PackageDemotionInteractor mPackageDemotionInteractor; + @Mock private AssistantFeedbackController mAssistantFeedbackController; @Mock private TelecomManager mTelecomManager; @@ -138,6 +141,7 @@ public class PromotedNotificationInfoTest extends SysuiTestCase { mMockIconStyleProvider, mOnUserInteractionCallback, mChannelEditorDialogController, + mPackageDemotionInteractor, TEST_PACKAGE_NAME, mNotificationChannel, mEntry, @@ -148,6 +152,7 @@ public class PromotedNotificationInfoTest extends SysuiTestCase { true, false, true, + true, mAssistantFeedbackController, mMetricsLogger, null); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt index 048028cdc0fa..db0c59f6d602 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt @@ -23,12 +23,12 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.plugins.statusbar.statusBarStateController import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.statusbar.lockscreenShadeTransitionController import com.android.systemui.statusbar.phone.screenOffAnimationController +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test @@ -44,7 +44,7 @@ import org.mockito.kotlin.whenever class NotificationShelfInteractorTest : SysuiTestCase() { private val kosmos = - Kosmos().apply { + testKosmos().apply { testCase = this@NotificationShelfInteractorTest lockscreenShadeTransitionController = mock() screenOffAnimationController = mock() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt index 6381b4e0fef7..7265262183ca 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt @@ -24,7 +24,6 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testCase @@ -35,6 +34,7 @@ import com.android.systemui.shade.domain.interactor.enableSingleShade import com.android.systemui.shade.domain.interactor.enableSplitShade import com.android.systemui.statusbar.lockscreenShadeTransitionController import com.android.systemui.statusbar.phone.screenOffAnimationController +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test @@ -50,7 +50,7 @@ import org.mockito.kotlin.whenever class NotificationShelfViewModelTest : SysuiTestCase() { private val kosmos = - Kosmos().apply { + testKosmos().apply { testCase = this@NotificationShelfViewModelTest lockscreenShadeTransitionController = mock() screenOffAnimationController = mock() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java index c5abd026b369..19e98387a120 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java @@ -106,6 +106,7 @@ public final class NotificationGroupTestHelper { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); entry.setRow(row); when(row.getEntry()).thenReturn(entry); + when(row.getEntryLegacy()).thenReturn(entry); return entry; } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index eb95ddb39b40..c58b4bc9953c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -30,7 +30,6 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.dump.DumpManager -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.res.R @@ -48,6 +47,7 @@ import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingC import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.statusbar.window.StatusBarWindowControllerStore +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -70,7 +70,7 @@ import org.mockito.kotlin.whenever @TestableLooper.RunWithLooper @DisableFlags(StatusBarChipsModernization.FLAG_NAME) class OngoingCallControllerTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val mainExecutor = kosmos.fakeExecutor private val testScope = kosmos.testScope diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt index a44631348796..7de56ddc06c3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt @@ -19,12 +19,11 @@ package com.android.systemui.statusbar.phone.ongoingcall.data.repository import android.platform.test.annotations.DisableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -33,7 +32,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @DisableFlags(StatusBarChipsModernization.FLAG_NAME) class OngoingCallRepositoryTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val underTest = kosmos.ongoingCallRepository @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt index f4204af7829b..84f1d5cd4895 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt @@ -23,7 +23,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.activity.data.repository.activityManagerRepository import com.android.systemui.activity.data.repository.fake import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher @@ -36,6 +35,7 @@ import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCall import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before @@ -51,7 +51,7 @@ import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) @EnableChipsModernization class OngoingCallInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos().useUnconfinedTestDispatcher() + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val underTest = kosmos.ongoingCallInteractor @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt index 91b3896332f5..39cf02dbc772 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt @@ -54,9 +54,7 @@ class FakeHomeStatusBarViewModel( MutableStateFlow(OngoingActivityChipModel.Inactive()) override val ongoingActivityChips = - MutableStateFlow( - ChipsVisibilityModel(MultipleOngoingActivityChipsModel(), areChipsAllowed = false) - ) + ChipsVisibilityModel(MultipleOngoingActivityChipsModel(), areChipsAllowed = false) override val ongoingActivityChipsLegacy = MutableStateFlow(MultipleOngoingActivityChipsModelLegacy()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt index 2da692b4cb45..20cf3ae6b8dd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt @@ -49,6 +49,7 @@ import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.lifecycle.activateIn import com.android.systemui.log.assertLogsWtf import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository @@ -107,7 +108,8 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class HomeStatusBarViewModelImplTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() - private val Kosmos.underTest by Kosmos.Fixture { kosmos.homeStatusBarViewModel } + private val Kosmos.underTest by + Kosmos.Fixture { kosmos.homeStatusBarViewModel.also { it.activateIn(kosmos.testScope) } } @Before fun setUp() { @@ -891,32 +893,26 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { @EnableChipsModernization fun ongoingActivityChips_statusBarHidden_noSecureCamera_noHun_notAllowed() = kosmos.runTest { - val latest by collectLastValue(underTest.ongoingActivityChips) - // home status bar not allowed kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen) kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, taskInfo = null) - assertThat(latest!!.areChipsAllowed).isFalse() + assertThat(underTest.ongoingActivityChips.areChipsAllowed).isFalse() } @Test @EnableChipsModernization fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_noHun_isAllowed() = kosmos.runTest { - val latest by collectLastValue(underTest.ongoingActivityChips) - transitionKeyguardToGone() - assertThat(latest!!.areChipsAllowed).isTrue() + assertThat(underTest.ongoingActivityChips.areChipsAllowed).isTrue() } @Test @EnableChipsModernization fun ongoingActivityChips_statusBarNotHidden_secureCamera_noHun_notAllowed() = kosmos.runTest { - val latest by collectLastValue(underTest.ongoingActivityChips) - fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.OCCLUDED, @@ -924,7 +920,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { ) kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) - assertThat(latest!!.areChipsAllowed).isFalse() + assertThat(underTest.ongoingActivityChips.areChipsAllowed).isFalse() } @Test @@ -932,8 +928,6 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { @EnableChipsModernization fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOff_notAllowed() = kosmos.runTest { - val latest by collectLastValue(underTest.ongoingActivityChips) - transitionKeyguardToGone() headsUpNotificationRepository.setNotifications( @@ -943,7 +937,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { ) ) - assertThat(latest!!.areChipsAllowed).isFalse() + assertThat(underTest.ongoingActivityChips.areChipsAllowed).isFalse() } @Test @@ -951,8 +945,6 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { @EnableChipsModernization fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOff_isAllowed() = kosmos.runTest { - val latest by collectLastValue(underTest.ongoingActivityChips) - transitionKeyguardToGone() headsUpNotificationRepository.setNotifications( @@ -962,16 +954,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { ) ) - assertThat(latest!!.areChipsAllowed).isTrue() + assertThat(underTest.ongoingActivityChips.areChipsAllowed).isTrue() } @Test @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME) @EnableChipsModernization - fun ongoingActivityChips_tatusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOn_isAllowed() = + fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOn_isAllowed() = kosmos.runTest { - val latest by collectLastValue(underTest.ongoingActivityChips) - transitionKeyguardToGone() headsUpNotificationRepository.setNotifications( @@ -981,7 +971,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { ) ) - assertThat(latest!!.areChipsAllowed).isTrue() + assertThat(underTest.ongoingActivityChips.areChipsAllowed).isTrue() } @Test @@ -989,8 +979,6 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { @EnableChipsModernization fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOn_isAllowed() = kosmos.runTest { - val latest by collectLastValue(underTest.ongoingActivityChips) - transitionKeyguardToGone() headsUpNotificationRepository.setNotifications( @@ -1000,7 +988,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { ) ) - assertThat(latest!!.areChipsAllowed).isTrue() + assertThat(underTest.ongoingActivityChips.areChipsAllowed).isTrue() } @Test @@ -1008,17 +996,16 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { @EnableChipsModernization fun ongoingActivityChips_followsChipsViewModel() = kosmos.runTest { - val latest by collectLastValue(underTest.ongoingActivityChips) transitionKeyguardToGone() screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording - assertIsScreenRecordChip(latest!!.chips.active[0]) + assertIsScreenRecordChip(underTest.ongoingActivityChips.chips.active[0]) addOngoingCallState(key = "call") - assertIsScreenRecordChip(latest!!.chips.active[0]) - assertIsCallChip(latest!!.chips.active[1], "call", context) + assertIsScreenRecordChip(underTest.ongoingActivityChips.chips.active[0]) + assertIsCallChip(underTest.ongoingActivityChips.chips.active[1], "call", context) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 9a0b8125fb25..b8be34378891 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -282,6 +282,33 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.TONAL_SPOT); } + @Test + @HardwareColors(color = "BLK", options = { + "BLK|MONOCHROMATIC|#FF0000", + "*|VIBRANT|home_wallpaper" + }) + @EnableFlags(com.android.systemui.Flags.FLAG_HARDWARE_COLOR_STYLES) + public void start_checkHardwareColor_storeInSecureSetting() { + // getWallpaperColors should not be called + ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class); + verify(mMainExecutor).execute(registrationRunnable.capture()); + registrationRunnable.getValue().run(); + verify(mWallpaperManager, never()).getWallpaperColors(anyInt()); + + assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.MONOCHROMATIC); + assertThat(mThemeOverlayController.mCurrentColors.get(0).getMainColors().get( + 0).toArgb()).isEqualTo(Color.RED); + + ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class); + verify(mSecureSettings).putStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(), + anyInt()); + + assertThat(updatedSetting.getValue().contains( + "android.theme.customization.theme_style\":\"MONOCHROMATIC")).isTrue(); + assertThat(updatedSetting.getValue().contains( + "android.theme.customization.system_palette\":\"ffff0000")).isTrue(); + } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt index 92bec70d2c4d..18a124cf362e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt @@ -38,7 +38,6 @@ import com.android.systemui.display.data.repository.DeviceStateRepository.Device import com.android.systemui.display.data.repository.fakeDeviceStateRepository import com.android.systemui.foldedDeviceStateList import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.kosmos.Kosmos import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState @@ -47,6 +46,7 @@ import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_OFF import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON import com.android.systemui.shared.system.SysUiStatsLog import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.testKosmos import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.COOL_DOWN_DURATION import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_CLOSED import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_HALF_OPEN @@ -89,7 +89,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { private lateinit var displaySwitchLatencyTracker: DisplaySwitchLatencyTracker @Captor private lateinit var loggerArgumentCaptor: ArgumentCaptor<DisplaySwitchLatencyEvent> - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val mockContext = mock<Context>() private val resources = mock<Resources>() private val foldStateRepository = kosmos.fakeDeviceStateRepository diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt index dfc4d0f07df8..98d99f781879 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt @@ -28,7 +28,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState import com.android.systemui.display.data.repository.fakeDeviceStateRepository -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest @@ -36,6 +35,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.power.shared.model.ScreenPowerState import com.android.systemui.statusbar.LightRevealScrim +import com.android.systemui.testKosmos import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy @@ -61,7 +61,7 @@ import org.mockito.kotlin.whenever class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { @get:Rule val animatorTestRule = AnimatorTestRule(this) - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val testScope: TestScope = kosmos.testScope private val fakeDeviceStateRepository = kosmos.fakeDeviceStateRepository private val powerInteractor = kosmos.powerInteractor @@ -93,7 +93,7 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { fakeAnimationStatusRepository, mockControllerFactory, mockFoldLockSettingAvailabilityProvider, - mockJankMonitor + mockJankMonitor, ) foldLightRevealAnimation.init() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/UtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/UtilsTest.kt index b4ba41a5c524..a180d51ff0ba 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/UtilsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/UtilsTest.kt @@ -27,7 +27,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.deviceStateManager -import com.android.systemui.kosmos.Kosmos +import com.android.systemui.testKosmos import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.junit.Before @@ -39,7 +39,7 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class UtilsTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val deviceStateManager = kosmos.deviceStateManager private lateinit var testableResources: TestableResources @@ -53,7 +53,7 @@ class UtilsTest : SysuiTestCase() { fun isFoldableReturnsFalse_overlayConfigurationValues() { testableResources.addOverride( com.android.internal.R.array.config_foldedDeviceStates, - intArrayOf() // empty array <=> device is not foldable + intArrayOf(), // empty array <=> device is not foldable ) whenever(deviceStateManager.supportedDeviceStates).thenReturn(listOf(DEFAULT_DEVICE_STATE)) assertFalse(Utils.isDeviceFoldable(testableResources.resources, deviceStateManager)) @@ -64,7 +64,7 @@ class UtilsTest : SysuiTestCase() { fun isFoldableReturnsFalse_deviceStateManager() { testableResources.addOverride( com.android.internal.R.array.config_foldedDeviceStates, - intArrayOf() // empty array <=> device is not foldable + intArrayOf(), // empty array <=> device is not foldable ) whenever(deviceStateManager.supportedDeviceStates).thenReturn(listOf(DEFAULT_DEVICE_STATE)) assertFalse(Utils.isDeviceFoldable(testableResources.resources, deviceStateManager)) @@ -75,7 +75,7 @@ class UtilsTest : SysuiTestCase() { fun isFoldableReturnsTrue_overlayConfigurationValues() { testableResources.addOverride( com.android.internal.R.array.config_foldedDeviceStates, - intArrayOf(FOLDED_DEVICE_STATE.identifier) + intArrayOf(FOLDED_DEVICE_STATE.identifier), ) whenever(deviceStateManager.supportedDeviceStates) .thenReturn(listOf(FOLDED_DEVICE_STATE, UNFOLDED_DEVICE_STATE)) @@ -87,7 +87,7 @@ class UtilsTest : SysuiTestCase() { fun isFoldableReturnsTrue_deviceStateManager() { testableResources.addOverride( com.android.internal.R.array.config_foldedDeviceStates, - intArrayOf(FOLDED_DEVICE_STATE.identifier) + intArrayOf(FOLDED_DEVICE_STATE.identifier), ) whenever(deviceStateManager.supportedDeviceStates) .thenReturn(listOf(FOLDED_DEVICE_STATE, UNFOLDED_DEVICE_STATE)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt index f232d52615a4..93e5721e9b27 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt @@ -20,8 +20,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.android.systemui.volume.panel.domain.availableCriteria import com.android.systemui.volume.panel.domain.defaultCriteria import com.android.systemui.volume.panel.domain.model.ComponentModel @@ -39,7 +39,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ComponentsInteractorImplTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private lateinit var underTest: ComponentsInteractor @@ -60,12 +60,7 @@ class ComponentsInteractorImplTest : SysuiTestCase() { fun componentsAvailability_checked() { with(kosmos) { testScope.runTest { - enabledComponents = - setOf( - BOTTOM_BAR, - COMPONENT_1, - COMPONENT_2, - ) + enabledComponents = setOf(BOTTOM_BAR, COMPONENT_1, COMPONENT_2) criteriaByKey = mapOf( BOTTOM_BAR to Provider { availableCriteria }, @@ -90,12 +85,7 @@ class ComponentsInteractorImplTest : SysuiTestCase() { fun noCriteria_fallbackToDefaultCriteria() { with(kosmos) { testScope.runTest { - enabledComponents = - setOf( - BOTTOM_BAR, - COMPONENT_1, - COMPONENT_2, - ) + enabledComponents = setOf(BOTTOM_BAR, COMPONENT_1, COMPONENT_2) criteriaByKey = mapOf( BOTTOM_BAR to Provider { availableCriteria }, 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 235475f6b202..0fc3470716fe 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 @@ -44,5 +44,5 @@ interface ClockEvents { fun onZenDataChanged(data: ZenData) /** Update reactive axes for this clock */ - fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) + 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 f9ff75d5fdc8..5b67edda73cc 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 @@ -55,9 +55,9 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) : } fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - d({ "onLayout($bool1, ${VRect(long1.toULong())})" }) { + d({ "onLayout($bool1, ${VRect.fromLong(long1)})" }) { bool1 = changed - long1 = VRect(left, top, right, bottom).data.toLong() + long1 = VRect(left, top, right, bottom).toLong() } } @@ -116,7 +116,7 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) : } fun animateFidget(x: Float, y: Float) { - d({ "animateFidget(${VPointF(long1.toULong())})" }) { long1 = VPointF(x, y).data.toLong() } + d({ "animateFidget(${VPointF.fromLong(long1)})" }) { long1 = VPointF(x, y).toLong() } } companion object { diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt index 0cbc30d399d0..2147ca147b70 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt @@ -35,10 +35,54 @@ constructor( /** Font axes that can be modified on this clock */ val axes: List<ClockFontAxis> = listOf(), - /** List of font presets for this clock. Can be assigned directly. */ - val axisPresets: List<List<ClockFontAxisSetting>> = listOf(), + /** Presets for this clock. Null indicates the preset list should be disabled. */ + val presetConfig: AxisPresetConfig? = null, ) +data class AxisPresetConfig( + /** Groups of Presets. Each group can be used together in a single control. */ + val groups: List<Group>, + + /** Preset item currently being used, null when the current style is not a preset */ + val current: IndexedStyle? = null, +) { + /** The selected clock axis style, and its indices */ + data class IndexedStyle( + /** Index of the group that this clock axis style appears in */ + val groupIndex: Int, + + /** Index of the preset within the group */ + val presetIndex: Int, + + /** Reference to the style in question */ + val style: ClockAxisStyle, + ) + + /** A group of preset styles */ + data class Group( + /* List of preset styles in this group */ + val presets: List<ClockAxisStyle>, + + /* Icon to use when this preset-group is active */ + val icon: Drawable, + ) + + fun findStyle(style: ClockAxisStyle): IndexedStyle? { + groups.forEachIndexed { groupIndex, group -> + group.presets.forEachIndexed { presetIndex, preset -> + if (preset == style) { + return@findStyle IndexedStyle( + groupIndex = groupIndex, + presetIndex = presetIndex, + style = preset, + ) + } + } + } + return null + } +} + /** Represents an Axis that can be modified */ data class ClockFontAxis( /** Axis key, not user renderable */ @@ -62,19 +106,12 @@ data class ClockFontAxis( /** Description of the axis */ val description: String, ) { - fun toSetting() = ClockFontAxisSetting(key, currentValue) - companion object { - fun List<ClockFontAxis>.merge( - axisSettings: List<ClockFontAxisSetting> - ): List<ClockFontAxis> { - val result = mutableListOf<ClockFontAxis>() - for (axis in this) { - val setting = axisSettings.firstOrNull { axis.key == it.key } - val output = setting?.let { axis.copy(currentValue = it.value) } ?: axis - result.add(output) - } - return result + fun List<ClockFontAxis>.merge(axisStyle: ClockAxisStyle): List<ClockFontAxis> { + return this.map { axis -> + axisStyle.get(axis.key)?.let { axis.copy(currentValue = it) } ?: axis + } + .toList() } } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt index e7b36626a810..cccc55835302 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt @@ -22,7 +22,7 @@ import org.json.JSONObject data class ClockSettings( val clockId: ClockId? = null, val seedColor: Int? = null, - val axes: List<ClockFontAxisSetting> = listOf(), + val axes: ClockAxisStyle = ClockAxisStyle(), ) { // Exclude metadata from equality checks var metadata: JSONObject = JSONObject() @@ -38,15 +38,15 @@ data class ClockSettings( put(KEY_CLOCK_ID, setting.clockId) put(KEY_SEED_COLOR, setting.seedColor) put(KEY_METADATA, setting.metadata) - put(KEY_AXIS_LIST, ClockFontAxisSetting.toJson(setting.axes)) + put(KEY_AXIS_LIST, ClockAxisStyle.toJson(setting.axes)) } } fun fromJson(json: JSONObject): ClockSettings { val clockId = if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null val seedColor = if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null - val axisList = json.optJSONArray(KEY_AXIS_LIST)?.let(ClockFontAxisSetting::fromJson) - return ClockSettings(clockId, seedColor, axisList ?: listOf()).apply { + val axisList = json.optJSONArray(KEY_AXIS_LIST)?.let(ClockAxisStyle::fromJson) + return ClockSettings(clockId, seedColor, axisList ?: ClockAxisStyle()).apply { metadata = json.optJSONObject(KEY_METADATA) ?: JSONObject() } } @@ -54,64 +54,102 @@ data class ClockSettings( } @Keep -/** Axis setting value for a clock */ -data class ClockFontAxisSetting( - /** Axis key; matches ClockFontAxis.key */ - val key: String, +class ClockAxisStyle { + private val settings: MutableMap<String, Float> - /** Value to set this axis to */ - val value: Float, -) { - companion object { - private val KEY_AXIS_KEY = "key" - private val KEY_AXIS_VALUE = "value" + // Iterable would be implemented on ClockAxisStyle directly, + // but that doesn't appear to work with plugins/dynamic libs. + val items: Iterable<Map.Entry<String, Float>> + get() = settings.asIterable() - fun toJson(setting: ClockFontAxisSetting): JSONObject { - return JSONObject().apply { - put(KEY_AXIS_KEY, setting.key) - put(KEY_AXIS_VALUE, setting.value) - } - } + val isEmpty: Boolean + get() = settings.isEmpty() - fun toJson(settings: List<ClockFontAxisSetting>): JSONArray { - return JSONArray().apply { - for (axis in settings) { - put(toJson(axis)) - } - } + constructor(initialize: ClockAxisStyle.() -> Unit = {}) { + settings = mutableMapOf() + this.initialize() + } + + constructor(style: ClockAxisStyle) { + settings = style.settings.toMutableMap() + } + + constructor(items: Map<String, Float>) { + settings = items.toMutableMap() + } + + constructor(key: String, value: Float) { + settings = mutableMapOf(key to value) + } + + constructor(items: List<ClockFontAxis>) { + settings = items.associate { it.key to it.currentValue }.toMutableMap() + } + + fun copy(initialize: ClockAxisStyle.() -> Unit): ClockAxisStyle { + return ClockAxisStyle(this).apply { initialize() } + } + + operator fun get(key: String): Float? = settings[key] + + operator fun set(key: String, value: Float) = put(key, value) + + fun put(key: String, value: Float) { + settings.put(key, value) + } + + fun toFVar(): String { + val sb = StringBuilder() + for (axis in settings) { + if (sb.length > 0) sb.append(", ") + sb.append("'${axis.key}' ${axis.value.toInt()}") } + return sb.toString() + } - fun fromJson(jsonObj: JSONObject): ClockFontAxisSetting { - return ClockFontAxisSetting( - key = jsonObj.getString(KEY_AXIS_KEY), - value = jsonObj.getDouble(KEY_AXIS_VALUE).toFloat(), - ) + fun copyWith(replacements: ClockAxisStyle): ClockAxisStyle { + val result = ClockAxisStyle(this) + for ((key, value) in replacements.settings) { + result[key] = value } + return result + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ClockAxisStyle) return false + return settings == other.settings + } - fun fromJson(jsonArray: JSONArray): List<ClockFontAxisSetting> { - val result = mutableListOf<ClockFontAxisSetting>() + companion object { + private val KEY_AXIS_KEY = "key" + private val KEY_AXIS_VALUE = "value" + + fun fromJson(jsonArray: JSONArray): ClockAxisStyle { + val result = ClockAxisStyle() for (i in 0..jsonArray.length() - 1) { val obj = jsonArray.getJSONObject(i) if (obj == null) continue - result.add(fromJson(obj)) + + result.put( + key = obj.getString(KEY_AXIS_KEY), + value = obj.getDouble(KEY_AXIS_VALUE).toFloat(), + ) } return result } - fun List<ClockFontAxisSetting>.toFVar(): String { - val sb = StringBuilder() - for (axis in this) { - if (sb.length > 0) sb.append(", ") - sb.append("'${axis.key}' ${axis.value.toInt()}") + fun toJson(style: ClockAxisStyle): JSONArray { + return JSONArray().apply { + for ((key, value) in style.settings) { + put( + JSONObject().apply { + put(KEY_AXIS_KEY, key) + put(KEY_AXIS_VALUE, value) + } + ) + } } - return sb.toString() - } - - fun List<ClockFontAxisSetting>.replace( - replacements: List<ClockFontAxisSetting> - ): List<ClockFontAxisSetting> { - var remaining = this.filterNot { lhs -> replacements.any { rhs -> lhs.key == rhs.key } } - return remaining + replacements } } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt index 1fb37ec28835..de62f9c8be74 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt @@ -56,6 +56,8 @@ value class VPointF(val data: ULong) { fun toPointF() = PointF(x, y) + fun toLong(): Long = data.toLong() + fun lengthSq(): Float = x * x + y * y fun length(): Float = sqrt(lengthSq()) @@ -110,6 +112,8 @@ value class VPointF(val data: ULong) { companion object { val ZERO = VPointF(0, 0) + fun fromLong(data: Long) = VPointF(data.toULong()) + fun max(lhs: VPointF, rhs: VPointF) = VPointF(max(lhs.x, rhs.x), max(lhs.y, rhs.y)) fun min(lhs: VPointF, rhs: VPointF) = VPointF(min(lhs.x, rhs.x), min(lhs.y, rhs.y)) @@ -148,6 +152,8 @@ value class VPoint(val data: ULong) { fun toPoint() = Point(x, y) + fun toLong(): Long = data.toLong() + fun abs() = VPoint(abs(x), abs(y)) operator fun component1(): Int = x @@ -191,6 +197,8 @@ value class VPoint(val data: ULong) { companion object { val ZERO = VPoint(0, 0) + fun fromLong(data: Long) = VPoint(data.toULong()) + fun max(lhs: VPoint, rhs: VPoint) = VPoint(max(lhs.x, rhs.x), max(lhs.y, rhs.y)) fun min(lhs: VPoint, rhs: VPoint) = VPoint(min(lhs.x, rhs.x), min(lhs.y, rhs.y)) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt index 3c1adf22a405..1bd29aa6a073 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt @@ -84,6 +84,10 @@ value class VRectF(val data: ULong) { val size: VPointF get() = VPointF(width, height) + fun toRectF(): RectF = RectF(left, top, right, bottom) + + fun toLong(): Long = data.toLong() + override fun toString() = "($left, $top) -> ($right, $bottom)" companion object { @@ -91,6 +95,8 @@ value class VRectF(val data: ULong) { private fun fromBits(value: Short): Float = Half.toFloat(Half.intBitsToHalf(value.toInt())) + fun fromLong(data: Long) = VRectF(data.toULong()) + fun fromCenter(center: VPointF, size: VPointF): VRectF { return VRectF( center.x - size.x / 2, @@ -162,11 +168,17 @@ value class VRect(val data: ULong) { val size: VPoint get() = VPoint(width, height) + fun toRect(): Rect = Rect(left, top, right, bottom) + + fun toLong(): Long = data.toLong() + override fun toString() = "($left, $top) -> ($right, $bottom)" companion object { val ZERO = VRect(0, 0, 0, 0) + fun fromLong(data: Long) = VRect(data.toULong()) + fun fromCenter(center: VPoint, size: VPoint): VRect { return VRect( (center.x - size.x / 2).toShort(), diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml index 124aec6a92dd..da2ec43e470c 100644 --- a/packages/SystemUI/res/layout/bluetooth_device_item.xml +++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml @@ -52,7 +52,7 @@ android:gravity="center_vertical" android:textSize="14sp" /> - <TextView + <com.android.systemui.util.DelayableMarqueeTextView android:layout_width="0dp" android:layout_height="wrap_content" android:id="@+id/bluetooth_device_summary" @@ -60,7 +60,9 @@ android:paddingEnd="10dp" android:paddingBottom="15dp" android:maxLines="1" - android:ellipsize="end" + android:ellipsize="marquee" + android:marqueeRepeatLimit="1" + android:singleLine="true" app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name" app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon" app:layout_constraintEnd_toStartOf="@+id/guideline" diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml index 949a6abb9b9d..a1b26fc9bb40 100644 --- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml +++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml @@ -85,13 +85,49 @@ android:longClickable="false"/> </LinearLayout> + <LinearLayout + android:id="@+id/input_routing_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/preset_layout" + android:layout_marginTop="@dimen/hearing_devices_layout_margin" + android:orientation="vertical" + android:visibility="gone"> + <TextView + android:id="@+id/input_routing_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin" + android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin" + android:paddingStart="@dimen/hearing_devices_small_title_padding_horizontal" + android:text="@string/hearing_devices_input_routing_label" + android:textAppearance="@style/TextAppearance.Dialog.Title" + android:textSize="14sp" + android:gravity="center_vertical" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:textDirection="locale"/> + <Spinner + android:id="@+id/input_routing_spinner" + style="@style/BluetoothTileDialog.Device" + android:layout_height="@dimen/bluetooth_dialog_device_height" + android:layout_marginTop="4dp" + android:paddingStart="0dp" + android:paddingEnd="0dp" + android:background="@drawable/hearing_devices_spinner_background" + android:popupBackground="@drawable/hearing_devices_spinner_popup_background" + android:dropDownWidth="match_parent" + android:longClickable="false"/> + </LinearLayout> + <com.android.systemui.accessibility.hearingaid.AmbientVolumeLayout android:id="@+id/ambient_layout" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/preset_layout" + app:layout_constraintTop_toBottomOf="@id/input_routing_layout" android:layout_marginTop="@dimen/hearing_devices_layout_margin" /> <LinearLayout diff --git a/packages/SystemUI/res/layout/notification_2025_info.xml b/packages/SystemUI/res/layout/notification_2025_info.xml index 7b6916652924..fa852a2b8e85 100644 --- a/packages/SystemUI/res/layout/notification_2025_info.xml +++ b/packages/SystemUI/res/layout/notification_2025_info.xml @@ -18,6 +18,7 @@ <!-- extends LinearLayout --> <com.android.systemui.statusbar.notification.row.NotificationInfo xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/notification_guts" android:layout_width="match_parent" @@ -324,18 +325,34 @@ </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> </LinearLayout> - - <LinearLayout + <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/bottom_buttons" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@*android:dimen/notification_2025_margin" android:minHeight="@dimen/notification_2025_guts_button_size" - android:gravity="center_vertical" - > + android:gravity="center_vertical"> + + <TextView + android:id="@+id/inline_dismiss" + android:text="@string/notification_inline_dismiss" + android:paddingEnd="@dimen/notification_importance_button_padding" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingTop="8dp" + android:paddingBottom="@*android:dimen/notification_2025_margin" + app:layout_constraintStart_toStartOf="parent" + android:gravity="center" + android:minWidth="@dimen/notification_2025_min_tap_target_size" + android:minHeight="@dimen/notification_2025_min_tap_target_size" + android:maxWidth="200dp" + style="@style/TextAppearance.NotificationInfo.Button" + android:textSize="@*android:dimen/notification_2025_action_text_size" + /> <TextView android:id="@+id/turn_off_notifications" android:text="@string/inline_turn_off_notifications" + android:paddingStart="@dimen/notification_importance_button_padding" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="32dp" @@ -345,6 +362,8 @@ android:minWidth="@dimen/notification_2025_min_tap_target_size" android:minHeight="@dimen/notification_2025_min_tap_target_size" android:maxWidth="200dp" + app:layout_constraintStart_toEndOf="@id/inline_dismiss" + app:layout_constraintBaseline_toBaselineOf="@id/inline_dismiss" style="@style/TextAppearance.NotificationInfo.Button" android:textSize="@*android:dimen/notification_2025_action_text_size"/> <TextView @@ -354,12 +373,18 @@ android:layout_height="wrap_content" android:paddingTop="8dp" android:paddingBottom="@*android:dimen/notification_2025_margin" - android:gravity="center" + android:gravity="end" + app:layout_constraintEnd_toEndOf="parent" android:minWidth="@dimen/notification_2025_min_tap_target_size" android:minHeight="@dimen/notification_2025_min_tap_target_size" android:maxWidth="125dp" style="@style/TextAppearance.NotificationInfo.Button" android:textSize="@*android:dimen/notification_2025_action_text_size"/> - </LinearLayout> + <androidx.constraintlayout.helper.widget.Flow + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:constraint_referenced_ids="inline_dismiss,turn_off_notifications,done" + app:flow_wrapMode="chain"/> + </androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout> </com.android.systemui.statusbar.notification.row.NotificationInfo> diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 089ceaee6ce3..d4bd142d9089 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -341,15 +341,28 @@ asked for it --> android:paddingEnd="4dp" > <TextView + android:id="@+id/inline_dismiss" + android:text="@string/notification_inline_dismiss" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:gravity="start|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="200dp" + android:paddingEnd="@dimen/notification_importance_button_padding" + style="@style/TextAppearance.NotificationInfo.Button"/> + <TextView android:id="@+id/turn_off_notifications" android:text="@string/inline_turn_off_notifications" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentStart="true" + android:layout_toEndOf="@id/inline_dismiss" android:gravity="start|center_vertical" android:minWidth="@dimen/notification_importance_toggle_size" android:minHeight="@dimen/notification_importance_toggle_size" android:maxWidth="200dp" + android:paddingStart="@dimen/notification_importance_button_padding" style="@style/TextAppearance.NotificationInfo.Button"/> <TextView android:id="@+id/done" diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml index 4850b35833e5..d1755eff6dab 100644 --- a/packages/SystemUI/res/layout/partial_conversation_info.xml +++ b/packages/SystemUI/res/layout/partial_conversation_info.xml @@ -143,15 +143,28 @@ android:paddingEnd="4dp" > <TextView + android:id="@+id/inline_dismiss" + android:text="@string/notification_inline_dismiss" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:gravity="start|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="200dp" + android:paddingEnd="@dimen/notification_importance_button_padding" + style="@style/TextAppearance.NotificationInfo.Button"/> + <TextView android:id="@+id/turn_off_notifications" android:text="@string/inline_turn_off_notifications" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentStart="true" + android:layout_toEndOf="@id/inline_dismiss" android:gravity="start|center_vertical" android:minWidth="@dimen/notification_importance_toggle_size" android:minHeight="@dimen/notification_importance_toggle_size" android:maxWidth="200dp" + android:paddingStart="@dimen/notification_importance_button_padding" style="@style/TextAppearance.NotificationInfo.Button"/> <TextView android:id="@+id/done" diff --git a/packages/SystemUI/res/layout/promoted_notification_info.xml b/packages/SystemUI/res/layout/promoted_notification_info.xml index 2e0a0ca1185c..3982a6638666 100644 --- a/packages/SystemUI/res/layout/promoted_notification_info.xml +++ b/packages/SystemUI/res/layout/promoted_notification_info.xml @@ -373,15 +373,28 @@ asked for it --> android:paddingEnd="4dp" > <TextView + android:id="@+id/inline_dismiss" + android:text="@string/notification_inline_dismiss" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:gravity="start|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="200dp" + android:paddingEnd="@dimen/notification_importance_button_padding" + style="@style/TextAppearance.NotificationInfo.Button"/> + <TextView android:id="@+id/turn_off_notifications" android:text="@string/inline_turn_off_notifications" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentStart="true" + android:layout_toEndOf="@id/inline_dismiss" android:gravity="start|center_vertical" android:minWidth="@dimen/notification_importance_toggle_size" android:minHeight="@dimen/notification_importance_toggle_size" android:maxWidth="200dp" + android:paddingStart="@dimen/notification_importance_button_padding" style="@style/TextAppearance.NotificationInfo.Button"/> <TextView android:id="@+id/done" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index f4c6904028ca..15519ff37d0c 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -34,7 +34,7 @@ <!-- Base colors for notification shade/scrim, the alpha component is adjusted programmatically to match the spec --> - <color name="shade_panel_base">@android:color/system_accent1_900</color> + <color name="shade_panel_base">@android:color/system_accent1_100</color> <color name="notification_scrim_base">@android:color/system_accent1_100</color> <!-- Fallback colors for notification shade/scrim --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 8c1fd65d96d4..3fdb98b4e00b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1016,6 +1016,13 @@ <string name="hearing_devices_presets_error">Couldn\'t update preset</string> <!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]--> <string name="hearing_devices_preset_label">Preset</string> + <!-- QuickSettings: Title for hearing aids input routing control in hearing device dialog. [CHAR LIMIT=40]--> + <string name="hearing_devices_input_routing_label">Default microphone for calls</string> + <!-- QuickSettings: Option for hearing aids input routing control in hearing device dialog. It will alter input routing for calls for hearing aid. [CHAR LIMIT=40]--> + <string-array name="hearing_device_input_routing_options"> + <item>Hearing aid microphone</item> + <item>This phone\'s microphone</item> + </string-array> <!-- QuickSettings: Content description for the icon that indicates the item is selected [CHAR LIMIT=NONE]--> <string name="hearing_devices_spinner_item_selected">Selected</string> <!-- QuickSettings: Title for ambient controls. [CHAR LIMIT=40]--> @@ -2056,7 +2063,7 @@ <string name="inline_ok_button">Apply</string> <!-- Notification inline controls: button to show block screen [CHAR_LIMIT=35] --> - <string name="inline_turn_off_notifications">Turn off notifications</string> + <string name="inline_turn_off_notifications">Turn off</string> <!-- [CHAR LIMIT=100] Notification Importance title --> <string name="notification_silence_title">Silent</string> @@ -3181,8 +3188,6 @@ <string name="controls_media_active_session">The current media session cannot be hidden.</string> <!-- Label for a button that will hide media controls [CHAR_LIMIT=30] --> <string name="controls_media_dismiss_button">Hide</string> - <!-- Label for button to resume media playback [CHAR_LIMIT=NONE] --> - <string name="controls_media_resume">Resume</string> <!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] --> <string name="controls_media_settings_button">Settings</string> <!-- Description for media control's playing media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]--> diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 763b1072f968..f2f177356fab 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -572,10 +572,6 @@ constructor( } fun handleFidgetTap(x: Float, y: Float) { - if (!com.android.systemui.Flags.clockFidgetAnimation()) { - return - } - clock?.run { smallClock.animations.onFidgetTap(x, y) largeClock.animations.onFidgetTap(x, y) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index d84b034eade8..60ec051315d0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -124,7 +124,6 @@ import com.android.settingslib.Utils; import com.android.settingslib.WirelessUtils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.CoreStartable; -import com.android.systemui.Dumpable; import com.android.systemui.Flags; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; @@ -301,7 +300,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt private final Provider<SceneInteractor> mSceneInteractor; private final Provider<AlternateBouncerInteractor> mAlternateBouncerInteractor; private final Provider<CommunalSceneInteractor> mCommunalSceneInteractor; - private final KeyguardServiceShowLockscreenInteractor mKeyguardServiceShowLockscreenInteractor; + private final Provider<KeyguardServiceShowLockscreenInteractor> + mKeyguardServiceShowLockscreenInteractor; private final AuthController mAuthController; private final UiEventLogger mUiEventLogger; private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage; @@ -2219,7 +2219,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt Provider<JavaAdapter> javaAdapter, Provider<SceneInteractor> sceneInteractor, Provider<CommunalSceneInteractor> communalSceneInteractor, - KeyguardServiceShowLockscreenInteractor keyguardServiceShowLockscreenInteractor) { + Provider<KeyguardServiceShowLockscreenInteractor> + keyguardServiceShowLockscreenInteractor) { mContext = context; mSubscriptionManager = subscriptionManager; mUserTracker = userTracker; @@ -2553,7 +2554,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt if (KeyguardWmStateRefactor.isEnabled()) { mJavaAdapter.get().alwaysCollectFlow( - mKeyguardServiceShowLockscreenInteractor.getShowNowEvents(), + mKeyguardServiceShowLockscreenInteractor.get().getShowNowEvents(), this::onKeyguardServiceShowLockscreenNowEvents ); } diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index c78f75a334fd..089466707298 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -894,8 +894,8 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { if (NotificationBundleUi.isEnabled()) { return enr.getEntryAdapter().canDragAndDrop(); } else { - boolean canBubble = enr.getEntry().canBubble(); - Notification notif = enr.getEntry().getSbn().getNotification(); + boolean canBubble = enr.getEntryLegacy().canBubble(); + Notification notif = enr.getEntryLegacy().getSbn().getNotification(); PendingIntent dragIntent = notif.contentIntent != null ? notif.contentIntent : notif.fullScreenIntent; if (dragIntent != null && dragIntent.isActivity() && !canBubble) { 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 fb3bc620ee68..14b13d105482 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -121,6 +121,13 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, private Spinner mPresetSpinner; private HearingDevicesPresetsController mPresetController; private HearingDevicesSpinnerAdapter mPresetInfoAdapter; + + private View mInputRoutingLayout; + private Spinner mInputRoutingSpinner; + private HearingDevicesInputRoutingController.Factory mInputRoutingControllerFactory; + private HearingDevicesInputRoutingController mInputRoutingController; + private HearingDevicesSpinnerAdapter mInputRoutingAdapter; + private final HearingDevicesPresetsController.PresetCallback mPresetCallback = new HearingDevicesPresetsController.PresetCallback() { @Override @@ -169,7 +176,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, @Background Executor bgExecutor, AudioManager audioManager, HearingDevicesUiEventLogger uiEventLogger, - QSSettingsPackageRepository qsSettingsPackageRepository) { + QSSettingsPackageRepository qsSettingsPackageRepository, + HearingDevicesInputRoutingController.Factory inputRoutingControllerFactory) { mShowPairNewDevice = showPairNewDevice; mSystemUIDialogFactory = systemUIDialogFactory; mActivityStarter = activityStarter; @@ -182,6 +190,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mUiEventLogger = uiEventLogger; mLaunchSourceId = launchSourceId; mQSSettingsPackageRepository = qsSettingsPackageRepository; + mInputRoutingControllerFactory = inputRoutingControllerFactory; } @Override @@ -239,6 +248,12 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mPresetLayout.setVisibility( mPresetController.isPresetControlAvailable() ? VISIBLE : GONE); } + if (mInputRoutingController != null) { + mInputRoutingController.setDevice(device); + mInputRoutingController.isInputRoutingControlAvailable( + available -> mMainExecutor.execute(() -> mInputRoutingLayout.setVisibility( + available ? VISIBLE : GONE))); + } if (mAmbientController != null) { mAmbientController.loadDevice(device); } @@ -310,6 +325,11 @@ 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()) { + setupInputRoutingSpinner(dialog, activeHearingDevice); + } if (com.android.settingslib.flags.Flags.hearingDevicesAmbientVolumeControl()) { setupAmbientControls(activeHearingDevice); } @@ -383,6 +403,52 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mBgExecutor.execute(() -> mPresetController.registerHapCallback()); } + private void setupInputRoutingSpinner(SystemUIDialog dialog, + CachedBluetoothDevice activeHearingDevice) { + mInputRoutingController = mInputRoutingControllerFactory.create(dialog.getContext()); + mInputRoutingController.setDevice(activeHearingDevice); + + mInputRoutingSpinner = dialog.requireViewById(R.id.input_routing_spinner); + mInputRoutingAdapter = new HearingDevicesSpinnerAdapter(dialog.getContext()); + mInputRoutingAdapter.addAll( + HearingDevicesInputRoutingController.getInputRoutingOptions(dialog.getContext())); + mInputRoutingSpinner.setAdapter(mInputRoutingAdapter); + // Disable redundant Touch & Hold accessibility action for Switch Access + mInputRoutingSpinner.setAccessibilityDelegate(new View.AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(@NonNull View host, + @NonNull AccessibilityNodeInfo info) { + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); + super.onInitializeAccessibilityNodeInfo(host, info); + } + }); + // Should call setSelection(index, false) for the spinner before setOnItemSelectedListener() + // to avoid extra onItemSelected() get called when first register the listener. + final int initialPosition = + mInputRoutingController.getUserPreferredInputRoutingValue(); + mInputRoutingSpinner.setSelection(initialPosition, false); + mInputRoutingAdapter.setSelected(initialPosition); + mInputRoutingSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + mInputRoutingAdapter.setSelected(position); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_INPUT_ROUTING_SELECT, + mLaunchSourceId); + mInputRoutingController.selectInputRouting(position); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + // Do nothing + } + }); + + mInputRoutingLayout = dialog.requireViewById(R.id.input_routing_layout); + mInputRoutingController.isInputRoutingControlAvailable( + available -> mMainExecutor.execute(() -> mInputRoutingLayout.setVisibility( + available ? VISIBLE : GONE))); + } + private void setupAmbientControls(CachedBluetoothDevice activeHearingDevice) { final AmbientVolumeLayout ambientLayout = mDialog.requireViewById(R.id.ambient_layout); ambientLayout.setUiEventLogger(mUiEventLogger, mLaunchSourceId); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingController.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingController.kt new file mode 100644 index 000000000000..e8fa7ba2268b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingController.kt @@ -0,0 +1,170 @@ +/* + * 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.accessibility.hearingaid + +import android.content.Context +import android.media.AudioManager +import android.util.Log +import androidx.collection.ArraySet +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.HapClientProfile +import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants +import com.android.settingslib.bluetooth.HearingAidAudioRoutingHelper +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.res.R +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * The controller of the hearing device input routing. + * + * <p> It manages and update the input routing according to the value. + */ +open class HearingDevicesInputRoutingController +@AssistedInject +constructor( + @Assisted context: Context, + private val audioManager: AudioManager, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + private val audioRoutingHelper = HearingAidAudioRoutingHelper(context) + private var cachedDevice: CachedBluetoothDevice? = null + private val bgCoroutineScope = CoroutineScope(backgroundDispatcher) + + /** Factory to create a [HearingDevicesInputRoutingController] instance. */ + @AssistedFactory + interface Factory { + fun create(context: Context): HearingDevicesInputRoutingController + } + + /** Possible input routing UI. Need to align with [getInputRoutingOptions] */ + enum class InputRoutingValue { + HEARING_DEVICE, + BUILTIN_MIC, + } + + companion object { + private const val TAG = "HearingDevicesInputRoutingController" + + /** Gets input routing options as strings. */ + @JvmStatic + fun getInputRoutingOptions(context: Context): Array<String> { + return context.resources.getStringArray(R.array.hearing_device_input_routing_options) + } + } + + fun interface InputRoutingControlAvailableCallback { + fun onResult(available: Boolean) + } + + /** + * Sets the device for this controller to control the input routing. + * + * @param device the [CachedBluetoothDevice] set to the controller + */ + fun setDevice(device: CachedBluetoothDevice?) { + this@HearingDevicesInputRoutingController.cachedDevice = device + } + + fun isInputRoutingControlAvailable(callback: InputRoutingControlAvailableCallback) { + bgCoroutineScope.launch { + val result = isInputRoutingControlAvailableInternal() + callback.onResult(result) + } + } + + /** + * Checks if input routing control is available for the currently set device. + * + * @return `true` if input routing control is available. + */ + private suspend fun isInputRoutingControlAvailableInternal(): Boolean { + val device = cachedDevice ?: return false + + val memberDevices = device.memberDevice + + val inputInfos = + withContext(backgroundDispatcher) { + audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS) + } + val supportedInputDeviceAddresses = ArraySet<String>() + supportedInputDeviceAddresses.add(device.address) + if (memberDevices.isNotEmpty()) { + memberDevices.forEach { supportedInputDeviceAddresses.add(it.address) } + } + + val isValidInputDevice = + inputInfos.any { supportedInputDeviceAddresses.contains(it.address) } + // Not support ASHA hearing device for input routing feature + val isHapHearingDevice = device.profiles.any { profile -> profile is HapClientProfile } + + if (isHapHearingDevice && !isValidInputDevice) { + Log.d(TAG, "Not supported input type hearing device.") + } + return isHapHearingDevice && isValidInputDevice + } + + /** Gets the user's preferred [InputRoutingValue]. */ + fun getUserPreferredInputRoutingValue(): Int { + val device = cachedDevice ?: return InputRoutingValue.HEARING_DEVICE.ordinal + + return if (device.device.isMicrophonePreferredForCalls) { + InputRoutingValue.HEARING_DEVICE.ordinal + } else { + InputRoutingValue.BUILTIN_MIC.ordinal + } + } + + /** + * Sets the input routing to [android.bluetooth.BluetoothDevice.setMicrophonePreferredForCalls] + * based on the input routing index. + * + * @param inputRoutingIndex The desired input routing index. + */ + fun selectInputRouting(inputRoutingIndex: Int) { + val device = cachedDevice ?: return + + val useBuiltinMic = (inputRoutingIndex == InputRoutingValue.BUILTIN_MIC.ordinal) + val status = + audioRoutingHelper.setPreferredInputDeviceForCalls( + device, + if (useBuiltinMic) HearingAidAudioRoutingConstants.RoutingValue.BUILTIN_DEVICE + else HearingAidAudioRoutingConstants.RoutingValue.AUTO, + ) + if (!status) { + Log.d(TAG, "Fail to configure setPreferredInputDeviceForCalls") + } + setMicrophonePreferredForCallsForDeviceSet(device, !useBuiltinMic) + } + + private fun setMicrophonePreferredForCallsForDeviceSet( + device: CachedBluetoothDevice?, + enabled: Boolean, + ) { + device ?: return + device.device.isMicrophonePreferredForCalls = enabled + val memberDevices = device.memberDevice + if (memberDevices.isNotEmpty()) { + memberDevices.forEach { d -> d.device.isMicrophonePreferredForCalls = enabled } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt index 4a695d638713..82ac10d85e70 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt @@ -40,6 +40,8 @@ enum class HearingDevicesUiEvent(private val id: Int) : UiEventLogger.UiEventEnu HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS(2153), @UiEvent(doc = "Collapse the ambient volume controls") HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS(2154), + @UiEvent(doc = "Select a input routing option from input routing spinner") + HEARING_DEVICES_INPUT_ROUTING_SELECT(2155), @UiEvent(doc = "Click on the device settings to enter hearing devices page") HEARING_DEVICES_SETTINGS_CLICK(2172); diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt index 5a5a51e53d63..8d066bbaa915 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt @@ -419,6 +419,8 @@ constructor( } nameView.text = item.deviceName summaryView.text = item.connectionSummary + // needed for marquee + summaryView.isSelected = true actionIconView.setOnClickListener { mutableDeviceItemClick.value = diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt index df6c1b18e3e9..8cebe04d4e01 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt @@ -24,11 +24,17 @@ import android.content.Intent import android.net.Uri import android.text.TextUtils import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.res.R +import java.util.function.Consumer import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch @SysUISingleton -class ActionIntentCreator @Inject constructor() : IntentCreator { +class ActionIntentCreator +@Inject +constructor(@Application private val applicationScope: CoroutineScope) : IntentCreator { override fun getTextEditorIntent(context: Context?) = Intent(context, EditTextActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) @@ -65,7 +71,7 @@ class ActionIntentCreator @Inject constructor() : IntentCreator { .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } - override fun getImageEditIntent(uri: Uri?, context: Context): Intent { + suspend fun getImageEditIntent(uri: Uri?, context: Context): Intent { val editorPackage = context.getString(R.string.config_screenshotEditor) return Intent(Intent.ACTION_EDIT).apply { if (!TextUtils.isEmpty(editorPackage)) { @@ -78,6 +84,14 @@ class ActionIntentCreator @Inject constructor() : IntentCreator { } } + override fun getImageEditIntentAsync( + uri: Uri?, + context: Context, + outputConsumer: Consumer<Intent>, + ) { + applicationScope.launch { outputConsumer.accept(getImageEditIntent(uri, context)) } + } + override fun getRemoteCopyIntent(clipData: ClipData?, context: Context): Intent { val remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage) return Intent(REMOTE_COPY_ACTION).apply { diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 314b6e7f5a28..984d2478eb72 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -558,8 +558,10 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private void editImage(Uri uri) { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); - mContext.startActivity(mIntentCreator.getImageEditIntent(uri, mContext)); - animateOut(); + mIntentCreator.getImageEditIntentAsync(uri, mContext, intent -> { + mContext.startActivity(intent); + animateOut(); + }); } private void editText() { @@ -747,8 +749,10 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv mIntentCreator.getTextEditorIntent(mContext)); break; case IMAGE: - finishWithSharedTransition(CLIPBOARD_OVERLAY_EDIT_TAPPED, - mIntentCreator.getImageEditIntent(mClipboardModel.getUri(), mContext)); + mIntentCreator.getImageEditIntentAsync(mClipboardModel.getUri(), mContext, + intent -> { + finishWithSharedTransition(CLIPBOARD_OVERLAY_EDIT_TAPPED, intent); + }); break; default: Log.w(TAG, "Got preview tapped callback for non-editable type " diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java index 4b24536ad28f..e9a9cbf106fc 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java @@ -27,6 +27,8 @@ import android.text.TextUtils; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.res.R; +import java.util.function.Consumer; + import javax.inject.Inject; @SysUISingleton @@ -73,7 +75,7 @@ public class DefaultIntentCreator implements IntentCreator { return chooserIntent; } - public Intent getImageEditIntent(Uri uri, Context context) { + public void getImageEditIntentAsync(Uri uri, Context context, Consumer<Intent> outputConsumer) { String editorPackage = context.getString(R.string.config_screenshotEditor); Intent editIntent = new Intent(Intent.ACTION_EDIT); if (!TextUtils.isEmpty(editorPackage)) { @@ -83,7 +85,7 @@ public class DefaultIntentCreator implements IntentCreator { editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); editIntent.putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD); - return editIntent; + outputConsumer.accept(editIntent); } public Intent getRemoteCopyIntent(ClipData clipData, Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java index c8a6b05f090b..283596f9f309 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java @@ -21,9 +21,11 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; +import java.util.function.Consumer; + public interface IntentCreator { Intent getTextEditorIntent(Context context); Intent getShareIntent(ClipData clipData, Context context); - Intent getImageEditIntent(Uri uri, Context context); + void getImageEditIntentAsync(Uri uri, Context context, Consumer<Intent> outputConsumer); Intent getRemoteCopyIntent(ClipData clipData, Context context); } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 857fa5cac3e2..756edb3d048d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -247,7 +247,7 @@ constructor( showsOnlyActiveMedia = false } falsingProtectionNeeded = false - disablePagination = true + disableScrolling = true init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt index 06a14eaa5c85..440c3001a2f9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.widgets +import android.appwidget.AppWidgetProviderInfo import com.android.systemui.CoreStartable import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor @@ -101,6 +102,7 @@ constructor( val (_, isActive) = withPrev // The validation is performed once the hub becomes active. if (isActive) { + removeNotLockscreenWidgets(widgets) validateWidgetsAndDeleteOrphaned(widgets) } } @@ -144,6 +146,19 @@ constructor( } } + private fun removeNotLockscreenWidgets(widgets: List<CommunalWidgetContentModel>) { + widgets + .filter { widget -> + when (widget) { + is CommunalWidgetContentModel.Available -> + widget.providerInfo.widgetCategory and + AppWidgetProviderInfo.WIDGET_CATEGORY_NOT_KEYGUARD != 0 + else -> false + } + } + .onEach { widget -> communalInteractor.deleteWidget(id = widget.appWidgetId) } + } + /** * Ensure the existence of all associated users for widgets, and remove widgets belonging to * users who have been deleted. diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt index 9b181be93b61..f3316958f01d 100644 --- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt +++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt @@ -16,8 +16,13 @@ package com.android.systemui.display +import android.hardware.display.DisplayManager +import android.os.Handler +import com.android.app.displaylib.DisplayLibComponent +import com.android.app.displaylib.createDisplayLibComponent import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DeviceStateRepository import com.android.systemui.display.data.repository.DeviceStateRepositoryImpl import com.android.systemui.display.data.repository.DisplayRepository @@ -28,6 +33,8 @@ import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepos import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepositoryImpl import com.android.systemui.display.data.repository.FocusedDisplayRepository import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl +import com.android.systemui.display.data.repository.PerDisplayRepoDumpHelper +import com.android.systemui.display.data.repository.PerDisplayRepository import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractorModule @@ -40,9 +47,11 @@ import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope /** Module binding display related classes. */ -@Module(includes = [DisplayWindowPropertiesInteractorModule::class]) +@Module(includes = [DisplayWindowPropertiesInteractorModule::class, DisplayLibModule::class]) interface DisplayModule { @Binds fun bindConnectedDisplayInteractor( @@ -73,6 +82,9 @@ interface DisplayModule { impl: DisplayWindowPropertiesRepositoryImpl ): DisplayWindowPropertiesRepository + @Binds + fun dumpRegistrationLambda(helper: PerDisplayRepoDumpHelper): PerDisplayRepository.InitCallback + companion object { @Provides @SysUISingleton @@ -103,3 +115,31 @@ interface DisplayModule { } } } + +/** Module to bind the DisplayRepository from displaylib to the systemui dagger graph. */ +@Module +object DisplayLibModule { + @Provides + @SysUISingleton + fun displayLibComponent( + displayManager: DisplayManager, + @Background backgroundHandler: Handler, + @Background bgApplicationScope: CoroutineScope, + @Background backgroundCoroutineDispatcher: CoroutineDispatcher, + ): DisplayLibComponent { + return createDisplayLibComponent( + displayManager, + backgroundHandler, + bgApplicationScope, + backgroundCoroutineDispatcher, + ) + } + + @Provides + @SysUISingleton + fun providesDisplayRepositoryFromLib( + displayLibComponent: DisplayLibComponent + ): com.android.app.displaylib.DisplayRepository { + return displayLibComponent.displayRepository + } +} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt index 721d116004f3..051fe7e5517c 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt @@ -17,112 +17,30 @@ package com.android.systemui.display.data.repository import android.annotation.SuppressLint -import android.hardware.display.DisplayManager -import android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED -import android.hardware.display.DisplayManager.DisplayListener -import android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_ADDED -import android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_CHANGED -import android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_REMOVED -import android.os.Handler -import android.util.Log -import android.view.Display import android.view.IWindowManager -import com.android.app.tracing.FlowTracing.traceEach -import com.android.app.tracing.traceSection +import com.android.app.displaylib.DisplayRepository as DisplayRepositoryFromLib import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.display.data.DisplayEvent import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.util.Compile -import com.android.systemui.util.kotlin.pairwiseBy -import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.scan import kotlinx.coroutines.flow.stateIn /** Repository for providing access to display related information and events. */ -interface DisplayRepository { - /** Display change event indicating a change to the given displayId has occurred. */ - val displayChangeEvent: Flow<Int> - - /** Display addition event indicating a new display has been added. */ - val displayAdditionEvent: Flow<Display?> - - /** Display removal event indicating a display has been removed. */ - val displayRemovalEvent: Flow<Int> +interface DisplayRepository : DisplayRepositoryFromLib { /** A [StateFlow] that maintains a set of display IDs that should have system decorations. */ val displayIdsWithSystemDecorations: StateFlow<Set<Int>> - - /** - * Provides the current set of displays. - * - * Consider using [displayIds] if only the [Display.getDisplayId] is needed. - */ - val displays: StateFlow<Set<Display>> - - /** - * Provides the current set of display ids. - * - * Note that it is preferred to use this instead of [displays] if only the - * [Display.getDisplayId] is needed. - */ - val displayIds: StateFlow<Set<Int>> - - /** - * Pending display id that can be enabled/disabled. - * - * When `null`, it means there is no pending display waiting to be enabled. - */ - val pendingDisplay: Flow<PendingDisplay?> - - /** Whether the default display is currently off. */ - val defaultDisplayOff: Flow<Boolean> - - /** - * Given a display ID int, return the corresponding Display object, or null if none exist. - * - * This method is guaranteed to not result in any binder call. - */ - fun getDisplay(displayId: Int): Display? = - displays.value.firstOrNull { it.displayId == displayId } - - /** Represents a connected display that has not been enabled yet. */ - interface PendingDisplay { - /** Id of the pending display. */ - val id: Int - - /** Enables the display, making it available to the system. */ - suspend fun enable() - - /** - * Ignores the pending display. When called, this specific display id doesn't appear as - * pending anymore until the display is disconnected and reconnected again. - */ - suspend fun ignore() - - /** Disables the display, making it unavailable to the system. */ - suspend fun disable() - } } @SysUISingleton @@ -130,310 +48,11 @@ interface DisplayRepository { class DisplayRepositoryImpl @Inject constructor( - private val displayManager: DisplayManager, private val commandQueue: CommandQueue, private val windowManager: IWindowManager, - @Background backgroundHandler: Handler, @Background bgApplicationScope: CoroutineScope, - @Background backgroundCoroutineDispatcher: CoroutineDispatcher, -) : DisplayRepository { - private val allDisplayEvents: Flow<DisplayEvent> = - conflatedCallbackFlow { - val callback = - object : DisplayListener { - override fun onDisplayAdded(displayId: Int) { - trySend(DisplayEvent.Added(displayId)) - } - - override fun onDisplayRemoved(displayId: Int) { - trySend(DisplayEvent.Removed(displayId)) - } - - override fun onDisplayChanged(displayId: Int) { - trySend(DisplayEvent.Changed(displayId)) - } - } - displayManager.registerDisplayListener( - callback, - backgroundHandler, - EVENT_TYPE_DISPLAY_ADDED or - EVENT_TYPE_DISPLAY_CHANGED or - EVENT_TYPE_DISPLAY_REMOVED, - ) - awaitClose { displayManager.unregisterDisplayListener(callback) } - } - .onStart { emit(DisplayEvent.Changed(Display.DEFAULT_DISPLAY)) } - .debugLog("allDisplayEvents") - .flowOn(backgroundCoroutineDispatcher) - - override val displayChangeEvent: Flow<Int> = - allDisplayEvents.filterIsInstance<DisplayEvent.Changed>().map { event -> event.displayId } - - override val displayRemovalEvent: Flow<Int> = - allDisplayEvents.filterIsInstance<DisplayEvent.Removed>().map { it.displayId } - - // This is necessary because there might be multiple displays, and we could - // have missed events for those added before this process or flow started. - // Note it causes a binder call from the main thread (it's traced). - private val initialDisplays: Set<Display> = - traceSection("$TAG#initialDisplays") { displayManager.displays?.toSet() ?: emptySet() } - private val initialDisplayIds = initialDisplays.map { display -> display.displayId }.toSet() - - /** Propagate to the listeners only enabled displays */ - private val enabledDisplayIds: StateFlow<Set<Int>> = - allDisplayEvents - .scan(initial = initialDisplayIds) { previousIds: Set<Int>, event: DisplayEvent -> - val id = event.displayId - when (event) { - is DisplayEvent.Removed -> previousIds - id - is DisplayEvent.Added, - is DisplayEvent.Changed -> previousIds + id - } - } - .distinctUntilChanged() - .debugLog("enabledDisplayIds") - .stateIn(bgApplicationScope, SharingStarted.WhileSubscribed(), initialDisplayIds) - - private val defaultDisplay by lazy { - getDisplayFromDisplayManager(Display.DEFAULT_DISPLAY) - ?: error("Unable to get default display.") - } - - /** - * Represents displays that went though the [DisplayListener.onDisplayAdded] callback. - * - * Those are commonly the ones provided by [DisplayManager.getDisplays] by default. - */ - private val enabledDisplays: StateFlow<Set<Display>> = - enabledDisplayIds - .mapElementsLazily { displayId -> getDisplayFromDisplayManager(displayId) } - .onEach { - if (it.isEmpty()) Log.wtf(TAG, "No enabled displays. This should never happen.") - } - .flowOn(backgroundCoroutineDispatcher) - .debugLog("enabledDisplays") - .stateIn( - bgApplicationScope, - started = SharingStarted.WhileSubscribed(), - // This triggers a single binder call on the UI thread per process. The - // alternative would be to use sharedFlows, but they are prohibited due to - // performance concerns. - // Ultimately, this is a trade-off between a one-time UI thread binder call and - // the constant overhead of sharedFlows. - initialValue = initialDisplays, - ) - - /** - * Represents displays that went though the [DisplayListener.onDisplayAdded] callback. - * - * Those are commonly the ones provided by [DisplayManager.getDisplays] by default. - */ - override val displays: StateFlow<Set<Display>> = enabledDisplays - - override val displayIds: StateFlow<Set<Int>> = enabledDisplayIds - - /** - * Implementation that maps from [displays], instead of [allDisplayEvents] for 2 reasons: - * 1. Guarantee that it emits __after__ [displays] emitted. This way it is guaranteed that - * calling [getDisplay] for the newly added display will be non-null. - * 2. Reuse the existing instance of [Display] without a new call to [DisplayManager]. - */ - override val displayAdditionEvent: Flow<Display?> = - displays - .pairwiseBy { previousDisplays, currentDisplays -> currentDisplays - previousDisplays } - .flatMapLatest { it.asFlow() } - - val _ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet()) - private val ignoredDisplayIds: Flow<Set<Int>> = _ignoredDisplayIds.debugLog("ignoredDisplayIds") - - private fun getInitialConnectedDisplays(): Set<Int> = - traceSection("$TAG#getInitialConnectedDisplays") { - displayManager - .getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED) - .map { it.displayId } - .toSet() - .also { - if (DEBUG) { - Log.d(TAG, "getInitialConnectedDisplays: $it") - } - } - } - - /* keeps connected displays until they are disconnected. */ - private val connectedDisplayIds: StateFlow<Set<Int>> = - conflatedCallbackFlow { - val connectedIds = getInitialConnectedDisplays().toMutableSet() - val callback = - object : DisplayConnectionListener { - override fun onDisplayConnected(id: Int) { - if (DEBUG) { - Log.d(TAG, "display with id=$id connected.") - } - connectedIds += id - _ignoredDisplayIds.value -= id - trySend(connectedIds.toSet()) - } - - override fun onDisplayDisconnected(id: Int) { - connectedIds -= id - if (DEBUG) { - Log.d(TAG, "display with id=$id disconnected.") - } - _ignoredDisplayIds.value -= id - trySend(connectedIds.toSet()) - } - } - trySend(connectedIds.toSet()) - displayManager.registerDisplayListener( - callback, - backgroundHandler, - /* eventFlags */ 0, - DisplayManager.PRIVATE_EVENT_TYPE_DISPLAY_CONNECTION_CHANGED, - ) - awaitClose { displayManager.unregisterDisplayListener(callback) } - } - .distinctUntilChanged() - .debugLog("connectedDisplayIds") - .stateIn( - bgApplicationScope, - started = SharingStarted.WhileSubscribed(), - // The initial value is set to empty, but connected displays are gathered as soon as - // the flow starts being collected. This is to ensure the call to get displays (an - // IPC) happens in the background instead of when this object - // is instantiated. - initialValue = emptySet(), - ) - - private val connectedExternalDisplayIds: Flow<Set<Int>> = - connectedDisplayIds - .map { connectedDisplayIds -> - traceSection("$TAG#filteringExternalDisplays") { - connectedDisplayIds - .filter { id -> getDisplayType(id) == Display.TYPE_EXTERNAL } - .toSet() - } - } - .flowOn(backgroundCoroutineDispatcher) - .debugLog("connectedExternalDisplayIds") - - private fun getDisplayType(displayId: Int): Int? = - traceSection("$TAG#getDisplayType") { displayManager.getDisplay(displayId)?.type } - - private fun getDisplayFromDisplayManager(displayId: Int): Display? = - traceSection("$TAG#getDisplay") { displayManager.getDisplay(displayId) } - - /** - * Pending displays are the ones connected, but not enabled and not ignored. - * - * A connected display is ignored after the user makes the decision to use it or not. For now, - * the initial decision from the user is final and not reversible. - */ - private val pendingDisplayIds: Flow<Set<Int>> = - combine(enabledDisplayIds, connectedExternalDisplayIds, ignoredDisplayIds) { - enabledDisplaysIds, - connectedExternalDisplayIds, - ignoredDisplayIds -> - if (DEBUG) { - Log.d( - TAG, - "combining enabled=$enabledDisplaysIds, " + - "connectedExternalDisplayIds=$connectedExternalDisplayIds, " + - "ignored=$ignoredDisplayIds", - ) - } - connectedExternalDisplayIds - enabledDisplaysIds - ignoredDisplayIds - } - .debugLog("allPendingDisplayIds") - - /** Which display id should be enabled among the pending ones. */ - private val pendingDisplayId: Flow<Int?> = - pendingDisplayIds.map { it.maxOrNull() }.distinctUntilChanged().debugLog("pendingDisplayId") - - override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?> = - pendingDisplayId - .map { displayId -> - val id = displayId ?: return@map null - object : DisplayRepository.PendingDisplay { - override val id = id - - override suspend fun enable() { - traceSection("DisplayRepository#enable($id)") { - if (DEBUG) { - Log.d(TAG, "Enabling display with id=$id") - } - displayManager.enableConnectedDisplay(id) - } - // After the display has been enabled, it is automatically ignored. - ignore() - } - - override suspend fun ignore() { - traceSection("DisplayRepository#ignore($id)") { - _ignoredDisplayIds.value += id - } - } - - override suspend fun disable() { - ignore() - traceSection("DisplayRepository#disable($id)") { - if (DEBUG) { - Log.d(TAG, "Disabling display with id=$id") - } - displayManager.disableConnectedDisplay(id) - } - } - } - } - .debugLog("pendingDisplay") - - override val defaultDisplayOff: Flow<Boolean> = - displayChangeEvent - .filter { it == Display.DEFAULT_DISPLAY } - .map { defaultDisplay.state == Display.STATE_OFF } - .distinctUntilChanged() - - private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> { - return if (DEBUG) { - traceEach(flowName, logcat = true, traceEmissionCount = true) - } else { - this - } - } - - /** - * Maps a set of T to a set of V, minimizing the number of `createValue` calls taking into - * account the diff between each root flow emission. - * - * This is needed to minimize the number of [getDisplayFromDisplayManager] in this class. Note - * that if the [createValue] returns a null element, it will not be added in the output set. - */ - private fun <T, V> Flow<Set<T>>.mapElementsLazily(createValue: (T) -> V?): Flow<Set<V>> { - data class State<T, V>( - val previousSet: Set<T>, - // Caches T values from the previousSet that were already converted to V - val valueMap: Map<T, V>, - val resultSet: Set<V>, - ) - - val emptyInitialState = State(emptySet<T>(), emptyMap(), emptySet<V>()) - return this.scan(emptyInitialState) { state, currentSet -> - if (currentSet == state.previousSet) { - state - } else { - val removed = state.previousSet - currentSet - val added = currentSet - state.previousSet - val newMap = state.valueMap.toMutableMap() - - added.forEach { key -> createValue(key)?.let { newMap[key] = it } } - removed.forEach { key -> newMap.remove(key) } - - val resultSet = newMap.values.toSet() - State(currentSet, newMap, resultSet) - } - } - .filter { it != emptyInitialState } - .map { it.resultSet } - } + private val displayRepositoryFromLib: com.android.app.displaylib.DisplayRepository, +) : DisplayRepositoryFromLib by displayRepositoryFromLib, DisplayRepository { private val decorationEvents: Flow<Event> = callbackFlow { val callback = @@ -487,20 +106,5 @@ constructor( private companion object { const val TAG = "DisplayRepository" - val DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG } } - -/** Used to provide default implementations for all methods. */ -private interface DisplayConnectionListener : DisplayListener { - - override fun onDisplayConnected(id: Int) {} - - override fun onDisplayDisconnected(id: Int) {} - - override fun onDisplayAdded(id: Int) {} - - override fun onDisplayRemoved(id: Int) {} - - override fun onDisplayChanged(id: Int) {} -} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt new file mode 100644 index 000000000000..212d55612935 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt @@ -0,0 +1,39 @@ +/* + * 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.display.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.dump.DumpableFromToString +import javax.inject.Inject + +/** Helper class to register PerDisplayRepository in the dump manager in SystemUI. */ +@SysUISingleton +class PerDisplayRepoDumpHelper @Inject constructor(private val dumpManager: DumpManager) : + PerDisplayRepository.InitCallback { + /** + * Registers PerDisplayRepository in the dump manager. + * + * The repository will be identified by the given debug name. + */ + override fun onInit(debugName: String, instance: Any) { + dumpManager.registerNormalDumpable( + "PerDisplayRepository-$debugName", + DumpableFromToString(instance), + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt index d1d013542fbf..7e00c60dc43a 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt @@ -20,13 +20,10 @@ import android.util.Log import android.view.Display import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.traceSection -import com.android.systemui.Dumpable import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dump.DumpManager import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import java.io.PrintWriter import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collectLatest @@ -88,6 +85,20 @@ interface PerDisplayRepository<T> { /** Debug name for this repository, mainly for tracing and logging. */ val debugName: String + + /** + * Callback to run when a given repository is initialized. + * + * This allows the caller to perform custom logic when the repository is ready to be used, e.g. + * register to dumpManager. + * + * Note that the instance is *leaked* outside of this class, so it should only be done when + * repository is meant to live as long as the caller. In systemUI this is ok because the + * repository lives as long as the process itself. + */ + interface InitCallback { + fun onInit(debugName: String, instance: Any) + } } /** @@ -110,8 +121,8 @@ constructor( @Assisted private val instanceProvider: PerDisplayInstanceProvider<T>, @Background private val backgroundApplicationScope: CoroutineScope, private val displayRepository: DisplayRepository, - private val dumpManager: DumpManager, -) : PerDisplayRepository<T>, Dumpable { + private val initCallback: PerDisplayRepository.InitCallback, +) : PerDisplayRepository<T> { private val perDisplayInstances = ConcurrentHashMap<Int, T?>() @@ -120,7 +131,7 @@ constructor( } private suspend fun start() { - dumpManager.registerNormalDumpable("PerDisplayRepository-${debugName}", this) + initCallback.onInit(debugName, this) displayRepository.displayIds.collectLatest { displayIds -> val toRemove = perDisplayInstances.keys - displayIds toRemove.forEach { displayId -> @@ -169,8 +180,9 @@ constructor( private const val TAG = "PerDisplayInstanceRepo" } - override fun dump(pw: PrintWriter, args: Array<out String>) { - pw.println(perDisplayInstances) + override fun toString(): String { + return "PerDisplayInstanceRepositoryImpl(" + + "debugName='$debugName', instances=$perDisplayInstances)" } } @@ -193,6 +205,7 @@ class DefaultDisplayOnlyInstanceRepositoryImpl<T>( private val lazyDefaultDisplayInstance by lazy { instanceProvider.createInstance(Display.DEFAULT_DISPLAY) } + override fun get(displayId: Int): T? = lazyDefaultDisplayInstance } diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt index 15a3cbdb8072..84f103e8f730 100644 --- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.display.domain.interactor import android.companion.virtual.VirtualDeviceManager import android.view.Display +import com.android.app.displaylib.DisplayRepository as DisplayRepositoryFromLib import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DeviceStateRepository @@ -138,7 +139,8 @@ constructor( .distinctUntilChanged() .flowOn(backgroundCoroutineDispatcher) - private fun DisplayRepository.PendingDisplay.toInteractorPendingDisplay(): PendingDisplay = + private fun DisplayRepositoryFromLib.PendingDisplay.toInteractorPendingDisplay(): + PendingDisplay = object : PendingDisplay { override suspend fun enable() = this@toInteractorPendingDisplay.enable() diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt new file mode 100644 index 000000000000..438931a1962d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt @@ -0,0 +1,27 @@ +/* + * 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.dump + +import com.android.systemui.Dumpable +import java.io.PrintWriter + +/** Dumpable implementation that just calls toString() on the instance. */ +class DumpableFromToString<T>(private val instance: T) : Dumpable { + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("$instance") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index cf5c3402792e..f2a10cc43fd9 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -147,14 +147,14 @@ import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; +import dagger.Lazy; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import javax.inject.Inject; -import dagger.Lazy; - /** * Helper to show the global actions dialog. Each item is an {@link Action} that may show depending * on whether the keyguard is showing, and whether the device is provisioned. @@ -270,6 +270,16 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final UserLogoutInteractor mLogoutInteractor; private final GlobalActionsInteractor mInteractor; private final Lazy<DisplayWindowPropertiesRepository> mDisplayWindowPropertiesRepositoryLazy; + private final Handler mHandler; + + private final UserTracker.Callback mOnUserSwitched = new UserTracker.Callback() { + @Override + public void onBeforeUserSwitching(int newUser) { + // Dismiss the dialog as soon as we start switching. This will schedule a message + // in a handler so it will be pretty quick. + dismissDialog(); + } + }; @VisibleForTesting public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { @@ -425,6 +435,29 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mInteractor = interactor; mDisplayWindowPropertiesRepositoryLazy = displayWindowPropertiesRepository; + mHandler = new Handler(mMainHandler.getLooper()) { + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_DISMISS: + if (mDialog != null) { + if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) { + // Hide instantly. + mDialog.hide(); + mDialog.dismiss(); + } else { + mDialog.dismiss(); + } + mDialog = null; + } + break; + case MESSAGE_REFRESH: + refreshSilentMode(); + mAdapter.notifyDataSetChanged(); + break; + } + } + }; + // receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); @@ -537,6 +570,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene expandable != null ? expandable.dialogTransitionController( new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)) : null; + mUserTracker.addCallback(mOnUserSwitched, mBackgroundExecutor); if (controller != null) { mDialogTransitionAnimator.show(mDialog, controller); } else { @@ -1404,6 +1438,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mWindowManagerFuncs.onGlobalActionsHidden(); mLifecycle.setCurrentState(Lifecycle.State.CREATED); mInteractor.onDismissed(); + mUserTracker.removeCallback(mOnUserSwitched); } /** @@ -2228,29 +2263,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mDialogPressDelay = 0; // ms } - private Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_DISMISS: - if (mDialog != null) { - if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) { - // Hide instantly. - mDialog.hide(); - mDialog.dismiss(); - } else { - mDialog.dismiss(); - } - mDialog = null; - } - break; - case MESSAGE_REFRESH: - refreshSilentMode(); - mAdapter.notifyDataSetChanged(); - break; - } - } - }; - private void onAirplaneModeChanged() { // Let the service state callbacks handle the state. if (mHasTelephony || mAirplaneModeOn == null) return; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 757464976261..79685088fed7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -673,7 +673,8 @@ public class KeyguardService extends Service { if (SceneContainerFlag.isEnabled()) { mDeviceEntryInteractorLazy.get().lockNow("doKeyguardTimeout"); } else if (KeyguardWmStateRefactor.isEnabled()) { - mKeyguardServiceShowLockscreenInteractor.onKeyguardServiceDoKeyguardTimeout(); + mKeyguardServiceShowLockscreenInteractor + .onKeyguardServiceDoKeyguardTimeout(options); } mKeyguardViewMediator.doKeyguardTimeout(options); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index d8fc21af9724..4755e2845587 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -134,7 +134,6 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.CoreStartable; import com.android.systemui.DejankUtils; -import com.android.systemui.Dumpable; import com.android.systemui.EventLogTags; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.TransitionAnimator; @@ -194,7 +193,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -4129,14 +4127,14 @@ public class KeyguardViewMediator implements CoreStartable, private void notifyLockNowCallback() { List<LockNowCallback> callbacks; + synchronized (mLockNowCallbacks) { - callbacks = new ArrayList<LockNowCallback>(mLockNowCallbacks); + callbacks = new ArrayList<>(mLockNowCallbacks); mLockNowCallbacks.clear(); } - Iterator<LockNowCallback> iter = callbacks.listIterator(); - while (iter.hasNext()) { - LockNowCallback callback = iter.next(); - iter.remove(); + + for (int i = 0; i < callbacks.size(); i++) { + final LockNowCallback callback = callbacks.get(i); if (callback.mUserId != mSelectedUserInteractor.getSelectedUserId()) { Log.i(TAG, "Not notifying lockNowCallback due to user mismatch"); continue; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt index 58692746d1e0..51b953ef290c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt @@ -22,11 +22,14 @@ import android.util.Log import android.view.IRemoteAnimationFinishedCallback import android.view.RemoteAnimationTarget import android.view.WindowManager +import com.android.internal.widget.LockPatternUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardShowWhileAwakeInteractor import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.window.flags.Flags import com.android.wm.shell.keyguard.KeyguardTransitions import java.util.concurrent.Executor @@ -46,6 +49,9 @@ constructor( private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier, private val keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor, private val keyguardTransitions: KeyguardTransitions, + private val selectedUserInteractor: SelectedUserInteractor, + private val lockPatternUtils: LockPatternUtils, + private val keyguardShowWhileAwakeInteractor: KeyguardShowWhileAwakeInteractor, ) { /** @@ -92,12 +98,23 @@ constructor( * second timeout). */ private var isKeyguardGoingAway = false - private set(value) { + private set(goingAway) { // TODO(b/278086361): Extricate the keyguard state controller. - keyguardStateController.notifyKeyguardGoingAway(value) - field = value + keyguardStateController.notifyKeyguardGoingAway(goingAway) + + if (goingAway) { + keyguardGoingAwayRequestedForUserId = selectedUserInteractor.getSelectedUserId() + } + + field = goingAway } + /** + * The current user ID when we asked WM to start the keyguard going away animation. This is used + * for validation when user switching occurs during unlock. + */ + private var keyguardGoingAwayRequestedForUserId: Int = -1 + /** Callback provided by WM to call once we're done with the going away animation. */ private var goingAwayRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null @@ -171,6 +188,14 @@ constructor( nonApps: Array<RemoteAnimationTarget>, finishedCallback: IRemoteAnimationFinishedCallback, ) { + goingAwayRemoteAnimationFinishedCallback = finishedCallback + + if (maybeStartTransitionIfUserSwitchedDuringGoingAway()) { + Log.d(TAG, "User switched during keyguard going away - ending remote animation.") + endKeyguardGoingAwayAnimation() + return + } + // If we weren't expecting the keyguard to be going away, WM triggered this transition. if (!isKeyguardGoingAway) { // Since WM triggered this, we're likely not transitioning to GONE yet. See if we can @@ -198,7 +223,6 @@ constructor( } if (apps.isNotEmpty()) { - goingAwayRemoteAnimationFinishedCallback = finishedCallback keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0]) } else { // Nothing to do here if we have no apps, end the animation, which will cancel it and WM @@ -211,6 +235,7 @@ constructor( // If WM cancelled the animation, we need to end immediately even if we're still using the // animation. endKeyguardGoingAwayAnimation() + maybeStartTransitionIfUserSwitchedDuringGoingAway() } /** @@ -301,6 +326,29 @@ constructor( } } + /** + * If necessary, start a transition to show/hide keyguard in response to a user switch during + * keyguard going away. + * + * Returns [true] if a transition was started, or false if a transition was not necessary. + */ + private fun maybeStartTransitionIfUserSwitchedDuringGoingAway(): Boolean { + val currentUser = selectedUserInteractor.getSelectedUserId() + if (currentUser != keyguardGoingAwayRequestedForUserId) { + if (lockPatternUtils.isSecure(currentUser)) { + keyguardShowWhileAwakeInteractor.onSwitchedToSecureUserWhileKeyguardGoingAway() + } else { + keyguardDismissTransitionInteractor.startDismissKeyguardTransition( + reason = "User switch during keyguard going away, and new user is insecure" + ) + } + + return true + } else { + return false + } + } + companion object { private val TAG = "WindowManagerLsVis" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt new file mode 100644 index 000000000000..16c2d14b78ae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt @@ -0,0 +1,46 @@ +/* + * 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.keyguard.data.repository + +import android.os.IRemoteCallback +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * Holds an IRemoteCallback along with the current user ID at the time the callback was provided. + */ +data class ShowLockscreenCallback(val userId: Int, val remoteCallback: IRemoteCallback) + +/** Maintains state related to KeyguardService requests to show the lockscreen. */ +@SysUISingleton +class KeyguardServiceShowLockscreenRepository @Inject constructor() { + val showLockscreenCallbacks = ArrayList<ShowLockscreenCallback>() + + /** + * Adds a callback that we'll notify when we show the lockscreen (or affirmatively decide not to + * show it). + */ + fun addShowLockscreenCallback(forUser: Int, callback: IRemoteCallback) { + synchronized(showLockscreenCallbacks) { + showLockscreenCallbacks.add(ShowLockscreenCallback(forUser, callback)) + } + } + + companion object { + private const val TAG = "ShowLockscreenRepository" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt index ab0efed2cb76..02e04aa279d8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt @@ -226,6 +226,10 @@ constructor( } fun handleFidgetTap(x: Float, y: Float) { + if (!com.android.systemui.Flags.clockFidgetAnimation()) { + return + } + if (selectedClockSize.value == ClockSizeSetting.DYNAMIC) { clockEventController.handleFidgetTap(x, y) } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt index b55bb383c308..6c084038f328 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt @@ -17,16 +17,27 @@ package com.android.systemui.keyguard.domain.interactor import android.annotation.SuppressLint +import android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK +import android.os.Bundle +import android.os.IRemoteCallback +import android.os.RemoteException +import android.util.Log +import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyguard.data.repository.KeyguardServiceShowLockscreenRepository +import com.android.systemui.keyguard.data.repository.ShowLockscreenCallback +import com.android.systemui.settings.UserTracker +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch /** - * Logic around requests by [KeyguardService] to show keyguard right now, even though the device is - * awake and not going to sleep. + * Logic around requests by [KeyguardService] and friends to show keyguard right now, even though + * the device is awake and not going to sleep. * * This can happen if WM#lockNow() is called, if KeyguardService#showDismissibleKeyguard is called * because we're folding with "continue using apps on fold" set to "swipe up to continue", or if the @@ -38,7 +49,28 @@ import kotlinx.coroutines.launch @SysUISingleton class KeyguardServiceShowLockscreenInteractor @Inject -constructor(@Background val backgroundScope: CoroutineScope) { +constructor( + @Background val backgroundScope: CoroutineScope, + private val selectedUserInteractor: SelectedUserInteractor, + private val repository: KeyguardServiceShowLockscreenRepository, + private val userTracker: UserTracker, + private val wmLockscreenVisibilityInteractor: Lazy<WindowManagerLockscreenVisibilityInteractor>, + private val keyguardEnabledInteractor: KeyguardEnabledInteractor, +) : CoreStartable { + + override fun start() { + backgroundScope.launch { + // Whenever we tell ATMS that lockscreen is visible, notify any showLockscreenCallbacks. + // This is not the only place we notify the lockNowCallbacks - there are cases where we + // decide not to show the lockscreen despite being asked to, and we need to notify the + // callback in those cases as well. + wmLockscreenVisibilityInteractor.get().lockscreenVisibility.collect { visible -> + if (visible) { + notifyShowLockscreenCallbacks() + } + } + } + } /** * Emits whenever [KeyguardService] receives a call that indicates we should show the lockscreen @@ -57,9 +89,38 @@ constructor(@Background val backgroundScope: CoroutineScope) { /** * Called by [KeyguardService] when it receives a doKeyguardTimeout() call. This indicates that * the device locked while the screen was on. + * + * We'll show keyguard, and if provided, save the lock on user switch callback, to notify it + * later when we successfully show. */ - fun onKeyguardServiceDoKeyguardTimeout() { + fun onKeyguardServiceDoKeyguardTimeout(options: Bundle? = null) { backgroundScope.launch { + if (options?.getBinder(LOCK_ON_USER_SWITCH_CALLBACK) != null) { + val userId = userTracker.userId + + // This callback needs to be invoked after we show the lockscreen (or decide not to + // show it) otherwise System UI will crash in 20 seconds, as a security measure. + repository.addShowLockscreenCallback( + userId, + IRemoteCallback.Stub.asInterface( + options.getBinder(LOCK_ON_USER_SWITCH_CALLBACK) + ), + ) + + Log.d( + TAG, + "Showing lockscreen now - setting required callback for user $userId. " + + "SysUI will crash if this callback is not invoked.", + ) + + // If the keyguard is disabled or suppressed, we'll never actually show the + // lockscreen. Notify the callback so we don't crash. + if (!keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed()) { + Log.d(TAG, "Keyguard is disabled or suppressed, notifying callbacks now.") + notifyShowLockscreenCallbacks() + } + } + showNowEvents.emit(ShowWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON) } } @@ -74,4 +135,31 @@ constructor(@Background val backgroundScope: CoroutineScope) { showNowEvents.emit(ShowWhileAwakeReason.FOLDED_WITH_SWIPE_UP_TO_CONTINUE) } } + + /** Notifies the callbacks that we've either locked, or decided not to lock. */ + private fun notifyShowLockscreenCallbacks() { + var callbacks: MutableList<ShowLockscreenCallback> + + synchronized(repository.showLockscreenCallbacks) { + callbacks = ArrayList(repository.showLockscreenCallbacks) + repository.showLockscreenCallbacks.clear() + } + + callbacks.forEach { callback -> + if (callback.userId != selectedUserInteractor.getSelectedUserId()) { + Log.i(TAG, "Not notifying lockNowCallback due to user mismatch") + return + } + Log.i(TAG, "Notifying lockNowCallback") + try { + callback.remoteCallback.sendResult(null) + } catch (e: RemoteException) { + Log.e(TAG, "Could not issue LockNowCallback sendResult", e) + } + } + } + + companion object { + private const val TAG = "ShowLockscreenInteractor" + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt index a8000a568a6a..c67939a0738a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt @@ -16,15 +16,20 @@ package com.android.systemui.keyguard.domain.interactor +import android.annotation.SuppressLint import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.util.kotlin.sample import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.launch /** The reason we're showing lockscreen while awake, used for logging. */ enum class ShowWhileAwakeReason(private val logReason: String) { @@ -38,6 +43,9 @@ enum class ShowWhileAwakeReason(private val logReason: String) { ), KEYGUARD_TIMEOUT_WHILE_SCREEN_ON( "Timed out while the screen was kept on, or WM#lockNow() was called." + ), + SWITCHED_TO_SECURE_USER_WHILE_GOING_AWAY( + "User switch to secure user occurred during keyguardGoingAway sequence, so we're locking." ); override fun toString(): String { @@ -68,6 +76,7 @@ enum class ShowWhileAwakeReason(private val logReason: String) { class KeyguardShowWhileAwakeInteractor @Inject constructor( + @Background val backgroundScope: CoroutineScope, biometricSettingsRepository: BiometricSettingsRepository, keyguardEnabledInteractor: KeyguardEnabledInteractor, keyguardServiceShowLockscreenInteractor: KeyguardServiceShowLockscreenInteractor, @@ -91,6 +100,15 @@ constructor( .filter { reshow -> reshow } .map { ShowWhileAwakeReason.KEYGUARD_REENABLED } + /** + * Emits whenever a user switch to a secure user occurs during keyguard going away. + * + * This is an event flow, hence the SharedFlow. + */ + @SuppressLint("SharedFlowCreation") + val switchedToSecureUserDuringGoingAway: MutableSharedFlow<ShowWhileAwakeReason> = + MutableSharedFlow() + /** Emits whenever we should show lockscreen while the screen is on, for any reason. */ val showWhileAwakeEvents: Flow<ShowWhileAwakeReason> = merge( @@ -108,5 +126,15 @@ constructor( keyguardServiceShowLockscreenInteractor.showNowEvents.filter { keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed() }, + switchedToSecureUserDuringGoingAway, ) + + /** A user switch to a secure user occurred while we were going away. We need to re-lock. */ + fun onSwitchedToSecureUserWhileKeyguardGoingAway() { + backgroundScope.launch { + switchedToSecureUserDuringGoingAway.emit( + ShowWhileAwakeReason.SWITCHED_TO_SECURE_USER_WHILE_GOING_AWAY + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index 2b4582a2a096..780b7fb06b5a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -29,6 +29,7 @@ constructor( private val auditLogger: KeyguardTransitionAuditLogger, private val statusBarDisableFlagsInteractor: StatusBarDisableFlagsInteractor, private val keyguardStateCallbackInteractor: KeyguardStateCallbackInteractor, + private val keyguardServiceShowLockscreenInteractor: KeyguardServiceShowLockscreenInteractor, ) : CoreStartable { override fun start() { @@ -54,6 +55,7 @@ constructor( auditLogger.start() statusBarDisableFlagsInteractor.start() keyguardStateCallbackInteractor.start() + keyguardServiceShowLockscreenInteractor.start() } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 6d8a943d3e28..830afeac7b96 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -31,10 +31,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.AOD -import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN -import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING @@ -165,43 +163,27 @@ constructor( .onStart { emit(false) } .distinctUntilChanged() - private val isOnLockscreen: Flow<Boolean> = + private val isOnOrGoingToLockscreen: Flow<Boolean> = combine( - keyguardTransitionInteractor.isFinishedIn(LOCKSCREEN).onStart { emit(false) }, - anyOf( - keyguardTransitionInteractor.isInTransition(Edge.create(to = LOCKSCREEN)), - keyguardTransitionInteractor.isInTransition(Edge.create(from = LOCKSCREEN)), - ), - ) { onLockscreen, transitioningToOrFromLockscreen -> - onLockscreen || transitioningToOrFromLockscreen + keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it == 1f }, + keyguardTransitionInteractor.isInTransition(Edge.create(to = LOCKSCREEN)), + ) { onLockscreen, transitioningToLockscreen -> + onLockscreen || transitioningToLockscreen } .distinctUntilChanged() private val alphaOnShadeExpansion: Flow<Float> = combineTransform( - anyOf( - keyguardTransitionInteractor.isInTransition( - edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone), - edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE), - ), - keyguardTransitionInteractor.isInTransition( - edge = Edge.create(from = Overlays.Bouncer, to = LOCKSCREEN), - edgeWithoutSceneContainer = - Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN), - ), - keyguardTransitionInteractor.isInTransition( - Edge.create(from = LOCKSCREEN, to = DREAMING) - ), - keyguardTransitionInteractor.isInTransition( - Edge.create(from = LOCKSCREEN, to = OCCLUDED) - ), + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = Overlays.Bouncer, to = LOCKSCREEN), + edgeWithoutSceneContainer = Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN), ), - isOnLockscreen, + isOnOrGoingToLockscreen, shadeInteractor.qsExpansion, shadeInteractor.shadeExpansion, - ) { disabledTransitionRunning, isOnLockscreen, qsExpansion, shadeExpansion -> + ) { disabledTransitionRunning, isOnOrGoingToLockscreen, qsExpansion, shadeExpansion -> // Fade out quickly as the shade expands - if (isOnLockscreen && !disabledTransitionRunning) { + if (isOnOrGoingToLockscreen && !disabledTransitionRunning) { val alpha = 1f - MathUtils.constrainedMap( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt index 3758afa61ed4..9312bca04994 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt @@ -66,6 +66,9 @@ constructor( transitionAnimation.sharedFlow( duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, onStep = alphaForAnimationStep, + // Rapid swipes to bouncer, and may end up skipping intermediate values that would've + // caused a complete fade out of lockscreen elements. Ensure it goes to 0f. + onFinish = { 0f }, ) val lockscreenAlpha: Flow<Float> = shortcutsAlpha diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt index 309b6751176c..c2efc7559487 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt @@ -1256,7 +1256,7 @@ class LegacyMediaDataManagerImpl( return MediaAction( Icon.createWithResource(context, iconId).setTint(themeText).loadDrawable(context), action, - context.getString(R.string.controls_media_resume), + context.getString(R.string.controls_media_button_play), if (Flags.mediaControlsUiUpdate()) { context.getDrawable(R.drawable.ic_media_play_button_container) } else { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt index dbd2250a75b0..a7c5a36b804a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt @@ -521,7 +521,7 @@ constructor( return MediaAction( Icon.createWithResource(context, iconId).setTint(themeText).loadDrawable(context), action, - context.getString(R.string.controls_media_resume), + context.getString(R.string.controls_media_button_play), if (Flags.mediaControlsUiUpdate()) { context.getDrawable(R.drawable.ic_media_play_button_container) } else { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt index 94df4b353c94..ca4a65953cba 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt @@ -1193,7 +1193,7 @@ class MediaDataProcessor( return MediaAction( Icon.createWithResource(context, iconId).setTint(themeText).loadDrawable(context), action, - context.getString(R.string.controls_media_resume), + context.getString(R.string.controls_media_button_play), if (Flags.mediaControlsUiUpdate()) { context.getDrawable(R.drawable.ic_media_play_button_container) } else { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt index cedf661d8ee3..5b65531cdd55 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt @@ -127,7 +127,6 @@ open class SeekBarObserver(private val holder: MediaViewHolder) : } holder.seekBar.setMax(data.duration) - val totalTimeDescription = data.durationDescription if (data.scrubbing) { holder.scrubbingTotalTimeView.text = formatTimeLabel(data.duration) } @@ -147,17 +146,9 @@ open class SeekBarObserver(private val holder: MediaViewHolder) : } } - val elapsedTimeDescription = data.elapsedTimeDescription if (data.scrubbing) { holder.scrubbingElapsedTimeView.text = formatTimeLabel(it) } - - holder.seekBar.contentDescription = - holder.seekBar.context.getString( - R.string.controls_media_seekbar_description, - elapsedTimeDescription, - totalTimeDescription, - ) } } @@ -166,6 +157,18 @@ open class SeekBarObserver(private val holder: MediaViewHolder) : return DateUtils.formatElapsedTime(milliseconds / DateUtils.SECOND_IN_MILLIS) } + fun updateContentDescription( + elapsedTimeDescription: CharSequence, + durationDescription: CharSequence, + ) { + holder.seekBar.contentDescription = + holder.seekBar.context.getString( + R.string.controls_media_seekbar_description, + elapsedTimeDescription, + durationDescription, + ) + } + @VisibleForTesting open fun buildResetAnimator(targetTime: Int): Animator { val animator = diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index ac6343c6bb64..71b3223b77be 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -159,8 +159,8 @@ constructor( /** Is the player currently visible (at the end of the transformation */ private var playersVisible: Boolean = false - /** Are we currently disabling pagination only allowing one media session to show */ - private var currentlyDisablePagination: Boolean = false + /** Are we currently disabling scolling, only allowing the first media session to show */ + private var currentlyDisableScrolling: Boolean = false /** * The desired location where we'll be at the end of the transformation. Usually this matches @@ -1130,21 +1130,22 @@ constructor( val endShowsActive = hostStates[currentEndLocation]?.showsOnlyActiveMedia ?: true val startShowsActive = hostStates[currentStartLocation]?.showsOnlyActiveMedia ?: endShowsActive - val startDisablePagination = hostStates[currentStartLocation]?.disablePagination ?: false - val endDisablePagination = hostStates[currentEndLocation]?.disablePagination ?: false + val startDisableScrolling = hostStates[currentStartLocation]?.disableScrolling ?: false + val endDisableScrolling = hostStates[currentEndLocation]?.disableScrolling ?: false if ( currentlyShowingOnlyActive != endShowsActive || - currentlyDisablePagination != endDisablePagination || + currentlyDisableScrolling != endDisableScrolling || ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) && (startShowsActive != endShowsActive || - startDisablePagination != endDisablePagination)) + startDisableScrolling != endDisableScrolling)) ) { // Whenever we're transitioning from between differing states or the endstate differs // we reset the translation currentlyShowingOnlyActive = endShowsActive - currentlyDisablePagination = endDisablePagination + currentlyDisableScrolling = endDisableScrolling mediaCarouselScrollHandler.resetTranslation(animate = true) + mediaCarouselScrollHandler.scrollingDisabled = currentlyDisableScrolling } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index 006eb203a669..f69985ee5364 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -224,6 +224,8 @@ public class MediaControlPanel { this::setIsScrubbing; private final SeekBarViewModel.EnabledChangeListener mEnabledChangeListener = this::setIsSeekBarEnabled; + private final SeekBarViewModel.ContentDescriptionListener mContentDescriptionListener = + this::setSeekbarContentDescription; private final BroadcastDialogController mBroadcastDialogController; private boolean mIsCurrentBroadcastedApp = false; @@ -327,6 +329,7 @@ public class MediaControlPanel { } mSeekBarViewModel.removeScrubbingChangeListener(mScrubbingChangeListener); mSeekBarViewModel.removeEnabledChangeListener(mEnabledChangeListener); + mSeekBarViewModel.removeContentDescriptionListener(mContentDescriptionListener); mSeekBarViewModel.onDestroy(); mMediaViewController.onDestroy(); } @@ -395,6 +398,10 @@ public class MediaControlPanel { }); } + private void setSeekbarContentDescription(CharSequence elapsedTime, CharSequence duration) { + mSeekBarObserver.updateContentDescription(elapsedTime, duration); + } + /** * Reloads animator duration scale. */ @@ -424,6 +431,7 @@ public class MediaControlPanel { mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar()); mSeekBarViewModel.setScrubbingChangeListener(mScrubbingChangeListener); mSeekBarViewModel.setEnabledChangeListener(mEnabledChangeListener); + mSeekBarViewModel.setContentDescriptionListener(mContentDescriptionListener); mMediaViewController.attach(player); vh.getPlayer().setOnLongClickListener(v -> { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index dba190022c8b..e87d5de56177 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -229,6 +229,20 @@ constructor( } } + private val seekbarDescriptionListener = + object : SeekBarViewModel.ContentDescriptionListener { + override fun onContentDescriptionChanged( + elapsedTimeDescription: CharSequence, + durationDescription: CharSequence, + ) { + if (!SceneContainerFlag.isEnabled) return + seekBarObserver.updateContentDescription( + elapsedTimeDescription, + durationDescription, + ) + } + } + /** * Sets the listening state of the player. * @@ -350,6 +364,7 @@ constructor( } seekBarViewModel.removeScrubbingChangeListener(scrubbingChangeListener) seekBarViewModel.removeEnabledChangeListener(enabledChangeListener) + seekBarViewModel.removeContentDescriptionListener(seekbarDescriptionListener) seekBarViewModel.onDestroy() } mediaHostStatesManager.removeController(this) @@ -653,6 +668,7 @@ constructor( seekBarViewModel.attachTouchHandlers(mediaViewHolder.seekBar) seekBarViewModel.setScrubbingChangeListener(scrubbingChangeListener) seekBarViewModel.setEnabledChangeListener(enabledChangeListener) + seekBarViewModel.setContentDescriptionListener(seekbarDescriptionListener) val mediaCard = mediaViewHolder.player attach(mediaViewHolder.player) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt index 9cf4a7b3a007..68865d65139c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt @@ -127,6 +127,9 @@ class MediaCarouselScrollHandler( scrollView.relativeScrollX = newRelativeScroll } + /** Is scrolling disabled for the carousel */ + var scrollingDisabled: Boolean = false + /** Does the dismiss currently show the setting cog? */ var showsSettingsButton: Boolean = false @@ -270,6 +273,10 @@ class MediaCarouselScrollHandler( } private fun onTouch(motionEvent: MotionEvent): Boolean { + if (scrollingDisabled) { + return false + } + val isUp = motionEvent.action == MotionEvent.ACTION_UP if (gestureDetector.onTouchEvent(motionEvent)) { if (isUp) { @@ -349,6 +356,10 @@ class MediaCarouselScrollHandler( } fun onScroll(down: MotionEvent, lastMotion: MotionEvent, distanceX: Float): Boolean { + if (scrollingDisabled) { + return false + } + val totalX = lastMotion.x - down.x val currentTranslation = scrollView.getContentTranslation() if (currentTranslation != 0.0f || !scrollView.canScrollHorizontally((-totalX).toInt())) { @@ -405,6 +416,10 @@ class MediaCarouselScrollHandler( } private fun onFling(vX: Float, vY: Float): Boolean { + if (scrollingDisabled) { + return false + } + if (vX * vX < 0.5 * vY * vY) { return false } @@ -575,6 +590,9 @@ class MediaCarouselScrollHandler( * @param destIndex destination index to indicate where the scroll should end. */ fun scrollToPlayer(sourceIndex: Int = -1, destIndex: Int) { + if (scrollingDisabled) { + return + } if (sourceIndex >= 0 && sourceIndex < mediaContent.childCount) { scrollView.relativeScrollX = sourceIndex * playerWidthPlusPadding } @@ -596,6 +614,9 @@ class MediaCarouselScrollHandler( * @param step A positive number means next, and negative means previous. */ fun scrollByStep(step: Int) { + if (scrollingDisabled) { + return + } val destIndex = visibleMediaIndex + step if (destIndex >= mediaContent.childCount || destIndex < 0) { if (!showsSettingsButton) return diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt index a518349ea424..37af7642b105 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt @@ -295,7 +295,7 @@ class MediaHost( changedListener?.invoke() } - override var disablePagination: Boolean = false + override var disableScrolling: Boolean = false set(value) { if (field == value) { return @@ -320,7 +320,7 @@ class MediaHost( mediaHostState.visible = visible mediaHostState.disappearParameters = disappearParameters.deepCopy() mediaHostState.falsingProtectionNeeded = falsingProtectionNeeded - mediaHostState.disablePagination = disablePagination + mediaHostState.disableScrolling = disableScrolling return mediaHostState } @@ -349,7 +349,7 @@ class MediaHost( if (!disappearParameters.equals(other.disappearParameters)) { return false } - if (disablePagination != other.disablePagination) { + if (disableScrolling != other.disableScrolling) { return false } return true @@ -363,7 +363,7 @@ class MediaHost( result = 31 * result + showsOnlyActiveMedia.hashCode() result = 31 * result + if (visible) 1 else 2 result = 31 * result + disappearParameters.hashCode() - result = 31 * result + disablePagination.hashCode() + result = 31 * result + disableScrolling.hashCode() return result } } @@ -423,10 +423,11 @@ interface MediaHostState { var disappearParameters: DisappearParameters /** - * Whether pagination should be disabled for this host, meaning that when there are multiple - * media sessions, only the first one will appear. + * Whether scrolling should be disabled for this host, meaning that when there are multiple + * media sessions, it will not be possible to scroll between media sessions or swipe away the + * entire media carousel. The first media session will always be shown. */ - var disablePagination: Boolean + var disableScrolling: Boolean /** Get a copy of this view state, deepcopying all appropriate members */ fun copy(): MediaHostState diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt index 8744c5c9a838..78a8cf8e9432 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt @@ -104,19 +104,20 @@ constructor( ) set(value) { val enabledChanged = value.enabled != field.enabled + field = value if (enabledChanged) { enabledChangeListener?.onEnabledChanged(value.enabled) } + _progress.postValue(value) + bgExecutor.execute { val durationDescription = formatTimeContentDescription(value.duration) val elapsedDescription = value.elapsedTime?.let { formatTimeContentDescription(it) } ?: "" - field = - value.copy( - durationDescription = durationDescription, - elapsedTimeDescription = elapsedDescription, - ) - _progress.postValue(field) + contentDescriptionListener?.onContentDescriptionChanged( + elapsedDescription, + durationDescription, + ) } } @@ -175,6 +176,7 @@ constructor( private var scrubbingChangeListener: ScrubbingChangeListener? = null private var enabledChangeListener: EnabledChangeListener? = null + private var contentDescriptionListener: ContentDescriptionListener? = null /** Set to true when the user is touching the seek bar to change the position. */ private var scrubbing = false @@ -394,6 +396,16 @@ constructor( } } + fun setContentDescriptionListener(listener: ContentDescriptionListener) { + contentDescriptionListener = listener + } + + fun removeContentDescriptionListener(listener: ContentDescriptionListener) { + if (listener == contentDescriptionListener) { + contentDescriptionListener = null + } + } + /** returns a pair of whether seekbar is enabled and the duration of media. */ private fun getEnabledStateAndDuration(metadata: MediaMetadata?): Pair<Boolean, Int> { val duration = metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0 @@ -468,6 +480,13 @@ constructor( fun onEnabledChanged(enabled: Boolean) } + interface ContentDescriptionListener { + fun onContentDescriptionChanged( + elapsedTimeDescription: CharSequence, + durationDescription: CharSequence, + ) + } + private class SeekBarChangeListener( val viewModel: SeekBarViewModel, val falsingManager: FalsingManager, @@ -639,7 +658,5 @@ constructor( val duration: Int, /** whether seekBar is listening to progress updates */ val listening: Boolean, - val elapsedTimeDescription: CharSequence = "", - val durationDescription: CharSequence = "", ) } 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 f79693138e24..d0c6a3e6a3ef 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java @@ -66,6 +66,7 @@ import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; import com.android.internal.annotations.GuardedBy; +import com.android.media.flags.Flags; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.BluetoothUtils; @@ -78,7 +79,6 @@ import com.android.settingslib.media.InputMediaDevice; import com.android.settingslib.media.InputRouteManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; -import com.android.settingslib.media.flags.Flags; import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DialogTransitionAnimator; @@ -226,7 +226,7 @@ public class MediaSwitchingController InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm, token); mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName); mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName); - mOutputMediaItemListProxy = new OutputMediaItemListProxy(); + mOutputMediaItemListProxy = new OutputMediaItemListProxy(context); mDialogTransitionAnimator = dialogTransitionAnimator; mNearbyMediaDevicesManager = nearbyMediaDevicesManager; mMediaOutputColorSchemeLegacy = MediaOutputColorSchemeLegacy.fromSystemColors(mContext); @@ -308,7 +308,8 @@ public class MediaSwitchingController } private MediaController getMediaController() { - if (mToken != null && Flags.usePlaybackInfoForRoutingControls()) { + if (mToken != null + && com.android.settingslib.media.flags.Flags.usePlaybackInfoForRoutingControls()) { return new MediaController(mContext, mToken); } else { for (NotificationEntry entry : mNotifCollection.getAllNotifs()) { @@ -577,19 +578,35 @@ public class MediaSwitchingController private void buildMediaItems(List<MediaDevice> devices) { synchronized (mMediaDevicesLock) { - List<MediaItem> updatedMediaItems = - buildMediaItems(mOutputMediaItemListProxy.getOutputMediaItemList(), devices); - mOutputMediaItemListProxy.clearAndAddAll(updatedMediaItems); + if (!mLocalMediaManager.isPreferenceRouteListingExist()) { + attachRangeInfo(devices); + Collections.sort(devices, Comparator.naturalOrder()); + } + if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { + // For the first time building list, to make sure the top device is the connected + // device. + boolean needToHandleMutingExpectedDevice = + hasMutingExpectedDevice() && !isCurrentConnectedDeviceRemote(); + final MediaDevice connectedMediaDevice = + needToHandleMutingExpectedDevice ? null : getCurrentConnectedMediaDevice(); + mOutputMediaItemListProxy.updateMediaDevices( + devices, + getSelectedMediaDevice(), + connectedMediaDevice, + needToHandleMutingExpectedDevice, + getConnectNewDeviceItem()); + } else { + List<MediaItem> updatedMediaItems = + buildMediaItems( + mOutputMediaItemListProxy.getOutputMediaItemList(), devices); + mOutputMediaItemListProxy.clearAndAddAll(updatedMediaItems); + } } } protected List<MediaItem> buildMediaItems( List<MediaItem> oldMediaItems, List<MediaDevice> devices) { synchronized (mMediaDevicesLock) { - if (!mLocalMediaManager.isPreferenceRouteListingExist()) { - attachRangeInfo(devices); - Collections.sort(devices, Comparator.naturalOrder()); - } // For the first time building list, to make sure the top device is the connected // device. boolean needToHandleMutingExpectedDevice = @@ -648,8 +665,7 @@ public class MediaSwitchingController .map(MediaItem::createDeviceMediaItem) .collect(Collectors.toList()); - boolean shouldAddFirstSeenSelectedDevice = - com.android.media.flags.Flags.enableOutputSwitcherDeviceGrouping(); + boolean shouldAddFirstSeenSelectedDevice = Flags.enableOutputSwitcherDeviceGrouping(); if (shouldAddFirstSeenSelectedDevice) { finalMediaItems.clear(); @@ -675,7 +691,7 @@ public class MediaSwitchingController } private boolean enableInputRouting() { - return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl(); + return Flags.enableAudioInputDeviceRoutingAndVolumeControl(); } private void buildInputMediaItems(List<MediaDevice> devices) { @@ -703,8 +719,7 @@ public class MediaSwitchingController if (connectedMediaDevice != null) { selectedDevicesIds.add(connectedMediaDevice.getId()); } - boolean groupSelectedDevices = - com.android.media.flags.Flags.enableOutputSwitcherDeviceGrouping(); + boolean groupSelectedDevices = Flags.enableOutputSwitcherDeviceGrouping(); int nextSelectedItemIndex = 0; boolean suggestedDeviceAdded = false; boolean displayGroupAdded = false; @@ -879,6 +894,11 @@ public class MediaSwitchingController return mLocalMediaManager.getCurrentConnectedDevice(); } + @VisibleForTesting + void clearMediaItemList() { + mOutputMediaItemListProxy.clear(); + } + boolean addDeviceToPlayMedia(MediaDevice device) { mMetricLogger.logInteractionExpansion(device); return mLocalMediaManager.addDeviceToPlayMedia(device); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java b/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java index 1c9c0b102cb7..45ca2c6ee8e5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -16,22 +16,175 @@ package com.android.systemui.media.dialog; +import android.content.Context; + +import androidx.annotation.Nullable; + +import com.android.media.flags.Flags; +import com.android.settingslib.media.MediaDevice; +import com.android.systemui.res.R; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; /** A proxy of holding the list of Output Switcher's output media items. */ public class OutputMediaItemListProxy { + private final Context mContext; private final List<MediaItem> mOutputMediaItemList; - public OutputMediaItemListProxy() { + // Use separated lists to hold different media items and create the list of output media items + // by using those separated lists and group dividers. + private final List<MediaItem> mSelectedMediaItems; + private final List<MediaItem> mSuggestedMediaItems; + private final List<MediaItem> mSpeakersAndDisplaysMediaItems; + @Nullable private MediaItem mConnectNewDeviceMediaItem; + + public OutputMediaItemListProxy(Context context) { + mContext = context; mOutputMediaItemList = new CopyOnWriteArrayList<>(); + mSelectedMediaItems = new CopyOnWriteArrayList<>(); + mSuggestedMediaItems = new CopyOnWriteArrayList<>(); + mSpeakersAndDisplaysMediaItems = new CopyOnWriteArrayList<>(); } /** Returns the list of output media items. */ public List<MediaItem> getOutputMediaItemList() { + if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { + if (isEmpty() && !mOutputMediaItemList.isEmpty()) { + // Ensures mOutputMediaItemList is empty when all individual media item lists are + // empty, preventing unexpected state issues. + mOutputMediaItemList.clear(); + } else if (!isEmpty() && mOutputMediaItemList.isEmpty()) { + // When any individual media item list is modified, the cached mOutputMediaItemList + // is emptied. On the next request for the output media item list, a fresh list is + // created and stored in the cache. + mOutputMediaItemList.addAll(createOutputMediaItemList()); + } + } return mOutputMediaItemList; } + private List<MediaItem> createOutputMediaItemList() { + List<MediaItem> finalMediaItems = new CopyOnWriteArrayList<>(); + finalMediaItems.addAll(mSelectedMediaItems); + if (!mSuggestedMediaItems.isEmpty()) { + finalMediaItems.add( + MediaItem.createGroupDividerMediaItem( + mContext.getString( + R.string.media_output_group_title_suggested_device))); + finalMediaItems.addAll(mSuggestedMediaItems); + } + if (!mSpeakersAndDisplaysMediaItems.isEmpty()) { + finalMediaItems.add( + MediaItem.createGroupDividerMediaItem( + mContext.getString( + R.string.media_output_group_title_speakers_and_displays))); + finalMediaItems.addAll(mSpeakersAndDisplaysMediaItems); + } + if (mConnectNewDeviceMediaItem != null) { + finalMediaItems.add(mConnectNewDeviceMediaItem); + } + return finalMediaItems; + } + + /** Updates the list of output media items with a given list of media devices. */ + public void updateMediaDevices( + List<MediaDevice> devices, + List<MediaDevice> selectedDevices, + @Nullable MediaDevice connectedMediaDevice, + boolean needToHandleMutingExpectedDevice, + @Nullable MediaItem connectNewDeviceMediaItem) { + Set<String> selectedOrConnectedMediaDeviceIds = + selectedDevices.stream().map(MediaDevice::getId).collect(Collectors.toSet()); + if (connectedMediaDevice != null) { + selectedOrConnectedMediaDeviceIds.add(connectedMediaDevice.getId()); + } + + List<MediaItem> selectedMediaItems = new ArrayList<>(); + List<MediaItem> suggestedMediaItems = new ArrayList<>(); + List<MediaItem> speakersAndDisplaysMediaItems = new ArrayList<>(); + Map<String, MediaItem> deviceIdToMediaItemMap = new HashMap<>(); + buildMediaItems( + devices, + selectedOrConnectedMediaDeviceIds, + needToHandleMutingExpectedDevice, + selectedMediaItems, + suggestedMediaItems, + speakersAndDisplaysMediaItems, + deviceIdToMediaItemMap); + + List<MediaItem> updatedSelectedMediaItems = new CopyOnWriteArrayList<>(); + List<MediaItem> updatedSuggestedMediaItems = new CopyOnWriteArrayList<>(); + List<MediaItem> updatedSpeakersAndDisplaysMediaItems = new CopyOnWriteArrayList<>(); + if (isEmpty()) { + updatedSelectedMediaItems.addAll(selectedMediaItems); + updatedSuggestedMediaItems.addAll(suggestedMediaItems); + updatedSpeakersAndDisplaysMediaItems.addAll(speakersAndDisplaysMediaItems); + } else { + Set<String> updatedDeviceIds = new HashSet<>(); + // Preserve the existing media item order while updating with the latest device + // information. Some items may retain their original group (suggested, speakers and + // displays) to maintain this order. + updateMediaItems( + mSelectedMediaItems, + updatedSelectedMediaItems, + deviceIdToMediaItemMap, + updatedDeviceIds); + updateMediaItems( + mSuggestedMediaItems, + updatedSuggestedMediaItems, + deviceIdToMediaItemMap, + updatedDeviceIds); + updateMediaItems( + mSpeakersAndDisplaysMediaItems, + updatedSpeakersAndDisplaysMediaItems, + deviceIdToMediaItemMap, + updatedDeviceIds); + + // Append new media items that are not already in the existing lists to the output list. + List<MediaItem> remainingMediaItems = new ArrayList<>(); + remainingMediaItems.addAll( + getRemainingMediaItems(selectedMediaItems, updatedDeviceIds)); + remainingMediaItems.addAll( + getRemainingMediaItems(suggestedMediaItems, updatedDeviceIds)); + remainingMediaItems.addAll( + getRemainingMediaItems(speakersAndDisplaysMediaItems, updatedDeviceIds)); + updatedSpeakersAndDisplaysMediaItems.addAll(remainingMediaItems); + } + + if (Flags.enableOutputSwitcherDeviceGrouping() && !updatedSelectedMediaItems.isEmpty()) { + MediaItem selectedMediaItem = updatedSelectedMediaItems.get(0); + Optional<MediaDevice> mediaDeviceOptional = selectedMediaItem.getMediaDevice(); + if (mediaDeviceOptional.isPresent()) { + MediaItem updatedMediaItem = + MediaItem.createDeviceMediaItem( + mediaDeviceOptional.get(), /* isFirstDeviceInGroup= */ true); + updatedSelectedMediaItems.remove(0); + updatedSelectedMediaItems.add(0, updatedMediaItem); + } + } + + mSelectedMediaItems.clear(); + mSelectedMediaItems.addAll(updatedSelectedMediaItems); + mSuggestedMediaItems.clear(); + mSuggestedMediaItems.addAll(updatedSuggestedMediaItems); + mSpeakersAndDisplaysMediaItems.clear(); + mSpeakersAndDisplaysMediaItems.addAll(updatedSpeakersAndDisplaysMediaItems); + mConnectNewDeviceMediaItem = connectNewDeviceMediaItem; + + // The cached mOutputMediaItemList is cleared upon any update to individual media item + // lists. This ensures getOutputMediaItemList() computes and caches a fresh list on the next + // invocation. + mOutputMediaItemList.clear(); + } + /** Updates the list of output media items with the given list. */ public void clearAndAddAll(List<MediaItem> updatedMediaItems) { mOutputMediaItemList.clear(); @@ -40,16 +193,112 @@ public class OutputMediaItemListProxy { /** Removes the media items with muting expected devices. */ public void removeMutingExpectedDevices() { + if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { + mSelectedMediaItems.removeIf((MediaItem::isMutingExpectedDevice)); + mSuggestedMediaItems.removeIf((MediaItem::isMutingExpectedDevice)); + mSpeakersAndDisplaysMediaItems.removeIf((MediaItem::isMutingExpectedDevice)); + if (mConnectNewDeviceMediaItem != null + && mConnectNewDeviceMediaItem.isMutingExpectedDevice()) { + mConnectNewDeviceMediaItem = null; + } + } mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice)); } /** Clears the output media item list. */ public void clear() { + if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { + mSelectedMediaItems.clear(); + mSuggestedMediaItems.clear(); + mSpeakersAndDisplaysMediaItems.clear(); + mConnectNewDeviceMediaItem = null; + } mOutputMediaItemList.clear(); } /** Returns whether the output media item list is empty. */ public boolean isEmpty() { - return mOutputMediaItemList.isEmpty(); + if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { + return mSelectedMediaItems.isEmpty() + && mSuggestedMediaItems.isEmpty() + && mSpeakersAndDisplaysMediaItems.isEmpty() + && (mConnectNewDeviceMediaItem == null); + } else { + return mOutputMediaItemList.isEmpty(); + } + } + + private void buildMediaItems( + List<MediaDevice> devices, + Set<String> selectedOrConnectedMediaDeviceIds, + boolean needToHandleMutingExpectedDevice, + List<MediaItem> selectedMediaItems, + List<MediaItem> suggestedMediaItems, + List<MediaItem> speakersAndDisplaysMediaItems, + Map<String, MediaItem> deviceIdToMediaItemMap) { + for (MediaDevice device : devices) { + String deviceId = device.getId(); + MediaItem mediaItem = MediaItem.createDeviceMediaItem(device); + if (needToHandleMutingExpectedDevice && device.isMutingExpectedDevice()) { + selectedMediaItems.add(0, mediaItem); + } else if (!needToHandleMutingExpectedDevice + && selectedOrConnectedMediaDeviceIds.contains(device.getId())) { + if (Flags.enableOutputSwitcherDeviceGrouping()) { + selectedMediaItems.add(mediaItem); + } else { + selectedMediaItems.add(0, mediaItem); + } + } else if (device.isSuggestedDevice()) { + suggestedMediaItems.add(mediaItem); + } else { + speakersAndDisplaysMediaItems.add(mediaItem); + } + deviceIdToMediaItemMap.put(deviceId, mediaItem); + } + } + + /** Returns a list of media items that remains the same order as the existing media items. */ + private void updateMediaItems( + List<MediaItem> existingMediaItems, + List<MediaItem> updatedMediaItems, + Map<String, MediaItem> deviceIdToMediaItemMap, + Set<String> updatedDeviceIds) { + List<String> existingDeviceIds = getDeviceIds(existingMediaItems); + for (String deviceId : existingDeviceIds) { + MediaItem mediaItem = deviceIdToMediaItemMap.get(deviceId); + if (mediaItem != null) { + updatedMediaItems.add(mediaItem); + updatedDeviceIds.add(deviceId); + } + } + } + + /** + * Returns media items from the input list that are not associated with the given device IDs. + */ + private List<MediaItem> getRemainingMediaItems( + List<MediaItem> mediaItems, Set<String> deviceIds) { + List<MediaItem> remainingMediaItems = new ArrayList<>(); + for (MediaItem item : mediaItems) { + Optional<MediaDevice> mediaDeviceOptional = item.getMediaDevice(); + if (mediaDeviceOptional.isPresent()) { + String deviceId = mediaDeviceOptional.get().getId(); + if (!deviceIds.contains(deviceId)) { + remainingMediaItems.add(item); + } + } + } + return remainingMediaItems; + } + + /** Returns a list of media device IDs for the given list of media items. */ + private List<String> getDeviceIds(List<MediaItem> mediaItems) { + List<String> deviceIds = new ArrayList<>(); + for (MediaItem item : mediaItems) { + if (item != null && item.getMediaDevice().isPresent()) { + deviceIds.add(item.getMediaDevice().get().getId()); + } + } + return deviceIds; } } diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt index 8751aa31f237..f07238895aa5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt @@ -73,6 +73,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.key +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -94,9 +95,12 @@ import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription @@ -106,6 +110,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEachIndexed +import androidx.compose.ui.util.fastRoundToInt import com.android.compose.PlatformButton import com.android.compose.PlatformIconButton import com.android.compose.PlatformOutlinedButton @@ -134,6 +139,7 @@ import com.android.systemui.media.remedia.ui.viewmodel.MediaPlayPauseActionViewM import com.android.systemui.media.remedia.ui.viewmodel.MediaSecondaryActionViewModel import com.android.systemui.media.remedia.ui.viewmodel.MediaViewModel import kotlin.math.max +import kotlin.math.min /** * Renders a media controls UI element. @@ -414,12 +420,56 @@ private fun ContentScope.CardForegroundContent( .clip(CircleShape), ) + var cardMaxWidth: Int by remember { mutableIntStateOf(0) } Row( horizontalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.align(Alignment.TopEnd), + modifier = + Modifier.align(Alignment.TopEnd) + // Output switcher chips must each be limited to at most 40% of the maximum + // width of the card. + // + // This saves the maximum possible width of the card so it can be referred + // to by child custom layout code below. + // + // The assumption is that the row can be as wide as the entire card. + .layout { measurable, constraints -> + cardMaxWidth = constraints.maxWidth + val placeable = measurable.measure(constraints) + + layout(placeable.measuredWidth, placeable.measuredHeight) { + placeable.place(0, 0) + } + }, ) { viewModel.outputSwitcherChips.fastForEach { chip -> - OutputSwitcherChip(viewModel = chip, colorScheme = colorScheme) + OutputSwitcherChip( + viewModel = chip, + colorScheme = colorScheme, + modifier = + Modifier + // Each chip must be limited to 40% of the width of the card at + // most. + // + // The underlying assumption is that there'll never be more than one + // chip with text and one more icon-only chip. Only the one with + // text can ever end up being too wide. + .layout { measurable, constraints -> + val placeable = + measurable.measure( + constraints.copy( + maxWidth = + min( + (cardMaxWidth * 0.4f).fastRoundToInt(), + constraints.maxWidth, + ) + ) + ) + + layout(placeable.measuredWidth, placeable.measuredHeight) { + placeable.place(0, 0) + } + }, + ) } } } @@ -663,11 +713,20 @@ private fun ContentScope.Navigation( if (isSeekBarVisible) { // To allow the seek bar slider to fade in and out, it's tagged as an element. Element(key = Media.Elements.SeekBarSlider, modifier = Modifier.weight(1f)) { + val sliderDragDelta = remember { + // Not a mutableStateOf - this is never accessed in composition and + // using an anonymous object avoids generics boxing of inline Offset. + object { + var value = Offset.Zero + } + } Slider( interactionSource = interactionSource, value = viewModel.progress, onValueChange = { progress -> viewModel.onScrubChange(progress) }, - onValueChangeFinished = { viewModel.onScrubFinished() }, + onValueChangeFinished = { + viewModel.onScrubFinished(sliderDragDelta.value) + }, colors = colors, thumb = { SeekBarThumb(interactionSource = interactionSource, colors = colors) @@ -681,9 +740,43 @@ private fun ContentScope.Navigation( ) }, modifier = - Modifier.fillMaxWidth().clearAndSetSemantics { - contentDescription = viewModel.contentDescription - }, + Modifier.fillMaxWidth() + .clearAndSetSemantics { + contentDescription = viewModel.contentDescription + } + .pointerInput(Unit) { + // Track and report the drag delta to the view-model so it + // can + // decide whether to accept the next onValueChangeFinished + // or + // reject it if the drag was overly vertical. + awaitPointerEventScope { + var down: PointerInputChange? = null + while (true) { + val event = + awaitPointerEvent(PointerEventPass.Initial) + when (event.type) { + PointerEventType.Press -> { + // A new gesture has begun. Record the + // initial + // down input change. + down = event.changes.last() + } + + PointerEventType.Move -> { + // The pointer has moved. If it's the same + // pointer as the latest down, calculate and + // report the drag delta. + val change = event.changes.last() + if (change.id == down?.id) { + sliderDragDelta.value = + change.position - down.position + } + } + } + } + } + }, ) } } @@ -979,6 +1072,8 @@ private fun OutputSwitcherChip( text = viewModel.text, style = MaterialTheme.typography.bodySmall, color = colorScheme.onPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt index ca7334341c87..a3689926e937 100644 --- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.media.remedia.ui.viewmodel import androidx.annotation.FloatRange +import androidx.compose.ui.geometry.Offset /** * Models UI state for the navigation component of the UI (potentially containing the seek bar and @@ -58,7 +59,7 @@ sealed interface MediaNavigationViewModel { * A callback to invoke once the user finishes "scrubbing" (e.g. stopped moving the thumb of * the seek bar). The position/progress should be committed. */ - val onScrubFinished: () -> Unit, + val onScrubFinished: (delta: Offset) -> Unit, /** Accessibility string to attach to the seekbar UI element. */ val contentDescription: String, ) : MediaNavigationViewModel diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt index 15951165a19e..19b08fa212db 100644 --- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt @@ -26,9 +26,9 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.ImageBitmap import com.android.systemui.classifier.Classifier -import com.android.systemui.classifier.domain.interactor.runIfNotFalseTap import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.lifecycle.ExclusiveActivatable @@ -42,6 +42,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.util.Locale +import kotlin.math.abs import kotlin.math.roundToLong import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.awaitCancellation @@ -114,8 +115,11 @@ constructor( isScrubbing = true seekProgress = progress }, - onScrubFinished = { - if (!falsingSystem.isFalseTouch(Classifier.MEDIA_SEEKBAR)) { + onScrubFinished = { dragDelta -> + if ( + dragDelta.isHorizontal() && + !falsingSystem.isFalseTouch(Classifier.MEDIA_SEEKBAR) + ) { interactor.seek( sessionKey = session.key, to = (seekProgress * session.durationMs).roundToLong(), @@ -346,6 +350,14 @@ constructor( .formatMeasures(*measures.toTypedArray()) } + /** + * Returns `true` if this [Offset] is the same or larger on the horizontal axis than the + * vertical axis. + */ + private fun Offset.isHorizontal(): Boolean { + return abs(x) >= abs(y) + } + interface FalsingSystem { fun runIfNotFalseTap(@FalsingManager.Penalty penalty: Int, block: () -> Unit) diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 49fa3ba2da61..88f679e73388 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -72,4 +72,8 @@ public interface NavigationBarController { /** @return {@link NavigationBar} on the default display. */ @Nullable NavigationBar getDefaultNavigationBar(); + + /** @return {@link NavigationBar} for a specific display, or null if not available. */ + @Nullable + NavigationBar getNavigationBar(int displayId); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt index 45ff7f4f87ef..f096510fa5dc 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt @@ -54,4 +54,6 @@ class NavigationBarControllerEmptyImpl @Inject constructor() : NavigationBarCont override fun isOverviewEnabled(displayId: Int) = false override fun getDefaultNavigationBar(): NavigationBar? = null + + override fun getNavigationBar(displayId: Int): NavigationBar? = null } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index 50d0a459da66..8fbf8b60af9a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -469,7 +469,8 @@ public class NavigationBarControllerImpl implements return (navBar == null) ? null : navBar.getView(); } - private @Nullable NavigationBar getNavigationBar(int displayId) { + @Override + public @Nullable NavigationBar getNavigationBar(int displayId) { return mNavigationBars.get(displayId); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java index 914e0f74e4a0..58ddbf60e8fb 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java @@ -27,10 +27,13 @@ import android.view.WindowManager; import com.android.app.viewcapture.ViewCapture; import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.systemui.dagger.qualifiers.DisplayId; +import com.android.systemui.display.data.repository.PerDisplayRepository; +import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope; import com.android.systemui.navigationbar.views.NavigationBarFrame; import com.android.systemui.navigationbar.views.NavigationBarView; import com.android.systemui.res.R; +import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; import dagger.Lazy; import dagger.Module; @@ -71,6 +74,20 @@ public interface NavigationBarModule { return context.getSystemService(WindowManager.class); } + /** A SysUiState for the navigation bar display. */ + @Provides + @NavigationBarScope + @DisplayId + static SysUiState provideSysUiState(@DisplayId Context context, + SysUiState defaultState, + PerDisplayRepository<SysUiState> repository) { + if (ShadeWindowGoesAround.isEnabled()) { + return repository.get(context.getDisplayId()); + } else { + return defaultState; + } + } + /** A ViewCaptureAwareWindowManager specific to the display's context. */ @Provides @NavigationBarScope diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java index f95f45906b23..8b5b3adeef1f 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java @@ -569,7 +569,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements NavigationModeController navigationModeController, StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, - SysUiState sysUiFlagsContainer, + @DisplayId SysUiState sysUiFlagsContainer, UserTracker userTracker, CommandQueue commandQueue, Optional<Pip> pipOptional, @@ -1694,7 +1694,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements (mNavbarFlags & NAVBAR_BACK_DISMISS_IME) != 0) .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, allowSystemGestureIgnoringBarVisibility()) - .commitUpdate(mDisplayId); + .commitUpdate(); } private void updateAssistantEntrypoints(boolean assistantAvailable, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java index 36cb8fa374b0..cbc4c26b2f94 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java @@ -740,15 +740,13 @@ public class NavigationBarView extends FrameLayout { /** */ public void updateDisabledSystemUiStateFlags(SysUiState sysUiState) { - int displayId = mContext.getDisplayId(); - sysUiState.setFlag(SYSUI_STATE_OVERVIEW_DISABLED, (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0) .setFlag(SYSUI_STATE_HOME_DISABLED, (mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0) .setFlag(SYSUI_STATE_SEARCH_DISABLED, (mDisabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0) - .commitUpdate(displayId); + .commitUpdate(); } public void setInScreenPinning(boolean active) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index a4386dee7264..05a60a6db31e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -18,7 +18,6 @@ package com.android.systemui.qs.composefragment import android.annotation.SuppressLint import android.content.Context -import android.content.res.Configuration import android.graphics.PointF import android.graphics.Rect import android.os.Bundle @@ -49,7 +48,6 @@ import androidx.compose.foundation.layout.requiredHeightIn import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -72,8 +70,6 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.layout.positionOnScreen import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.CustomAccessibilityAction @@ -255,7 +251,7 @@ constructor( @Composable private fun Content() { - PlatformTheme(isDarkTheme = true /* Delete AlwaysDarkMode when removing this */) { + PlatformTheme { ProvideShortcutHelperIndication(interactionsConfig = interactionsConfig()) { // TODO(b/389985793): Make sure that there is no coroutine work or recompositions // happening when alwaysCompose is true but isQsVisibleAndAnyShadeExpanded is false. @@ -747,25 +743,22 @@ constructor( ) val BrightnessSlider = @Composable { - AlwaysDarkMode { - Box( - Modifier.systemGestureExclusionInShade( - enabled = { - layoutState.transitionState is TransitionState.Idle - } - ) - ) { - BrightnessSliderContainer( - viewModel = - containerViewModel.brightnessSliderViewModel, - containerColors = - ContainerColors( - Color.Transparent, - ContainerColors.defaultContainerColor, - ), - modifier = Modifier.fillMaxWidth(), - ) - } + Box( + Modifier.systemGestureExclusionInShade( + enabled = { + layoutState.transitionState is TransitionState.Idle + } + ) + ) { + BrightnessSliderContainer( + viewModel = containerViewModel.brightnessSliderViewModel, + containerColors = + ContainerColors( + Color.Transparent, + ContainerColors.defaultContainerColor, + ), + modifier = Modifier.fillMaxWidth(), + ) } } val TileGrid = @@ -1243,28 +1236,3 @@ private fun interactionsConfig() = private inline val alwaysCompose get() = Flags.alwaysComposeQsUiFragment() - -/** - * Forces the configuration and themes to be dark theme. This is needed in order to have - * [colorResource] retrieve the dark mode colors. - * - * This should be removed when we remove the force dark mode in [PlatformTheme] at the root of the - * compose hierarchy. - */ -@Composable -private fun AlwaysDarkMode(content: @Composable () -> Unit) { - val currentConfig = LocalConfiguration.current - val darkConfig = - Configuration(currentConfig).apply { - uiMode = - (uiMode and (Configuration.UI_MODE_NIGHT_MASK.inv())) or - Configuration.UI_MODE_NIGHT_YES - } - val newContext = LocalContext.current.createConfigurationContext(darkConfig) - CompositionLocalProvider( - LocalConfiguration provides darkConfig, - LocalContext provides newContext, - ) { - content() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index 22971a9eb703..a7ebb2289814 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -88,7 +88,11 @@ constructor( LaunchedEffect(listening, pagerState) { snapshotFlow { listening() } .collect { - if (!listening()) { + // Whenever we go from not listening to listening, we should be in the first + // page. If we did this when going from listening to not listening, opening + // edit mode in second page will cause it to go to first page during the + // transition. + if (listening()) { pagerState.scrollToPage(0) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt index 69b967a68c3c..ccbd8fdbe00c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt @@ -63,6 +63,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -89,6 +90,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.graphics.Color @@ -113,7 +115,9 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastMap +import com.android.compose.gesture.effect.rememberOffsetOverscrollEffectFactory import com.android.compose.modifiers.height +import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.common.ui.compose.load import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.SizedTileImpl @@ -131,6 +135,7 @@ import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaul import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AUTO_SCROLL_SPEED import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AvailableTilesGridMinHeight import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.CurrentTilesGridPadding +import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.GridBackgroundCornerRadius import com.android.systemui.qs.panels.ui.compose.selection.InteractiveTileContainer import com.android.systemui.qs.panels.ui.compose.selection.MutableSelectionState import com.android.systemui.qs.panels.ui.compose.selection.ResizingState @@ -163,14 +168,27 @@ object TileType @OptIn(ExperimentalMaterial3Api::class) @Composable private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { - + val primaryContainerColor = MaterialTheme.colorScheme.primaryContainer TopAppBar( - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), - title = { Text(text = stringResource(id = R.string.qs_edit)) }, + colors = + TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent, + titleContentColor = MaterialTheme.colorScheme.onSurface, + ), + title = { + Text( + text = stringResource(id = R.string.qs_edit), + modifier = Modifier.padding(start = 24.dp), + ) + }, navigationIcon = { - IconButton(onClick = onStopEditing) { + IconButton( + onClick = onStopEditing, + modifier = Modifier.drawBehind { drawCircle(primaryContainerColor) }, + ) { Icon( Icons.AutoMirrored.Filled.ArrowBack, + tint = MaterialTheme.colorScheme.onSurface, contentDescription = stringResource(id = com.android.internal.R.string.action_bar_up_description), ) @@ -178,11 +196,19 @@ private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { }, actions = { if (onReset != null) { - TextButton(onClick = onReset) { + TextButton( + onClick = onReset, + colors = + ButtonDefaults.textButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ), + ) { Text(stringResource(id = com.android.internal.R.string.reset)) } } }, + modifier = Modifier.padding(vertical = 8.dp), ) } @@ -215,7 +241,9 @@ fun DefaultEditTileGrid( containerColor = Color.Transparent, topBar = { EditModeTopBar(onStopEditing = onStopEditing, onReset = reset) }, ) { innerPadding -> - CompositionLocalProvider(LocalOverscrollFactory provides null) { + CompositionLocalProvider( + LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory() + ) { val scrollState = rememberScrollState() AutoScrollGrid(listState, scrollState, innerPadding) @@ -244,7 +272,7 @@ fun DefaultEditTileGrid( targetState = listState.dragInProgress || selectionState.selected, label = "QSEditHeader", contentAlignment = Alignment.Center, - modifier = Modifier.fillMaxWidth().heightIn(min = 80.dp), + modifier = Modifier.fillMaxWidth().heightIn(min = 48.dp), ) { showRemoveTarget -> EditGridHeader { if (showRemoveTarget) { @@ -289,10 +317,6 @@ fun DefaultEditTileGrid( spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)), modifier = modifier.fillMaxSize(), ) { - EditGridHeader { - Text(text = stringResource(id = R.string.drag_to_add_tiles)) - } - val availableTiles = remember { mutableStateListOf<AvailableTileGridCell>().apply { addAll(toAvailableTiles(listState.tiles, otherTiles)) @@ -371,9 +395,7 @@ private fun EditGridHeader( modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit, ) { - CompositionLocalProvider( - LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f) - ) { + CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) { Box(contentAlignment = Alignment.Center, modifier = modifier.fillMaxWidth()) { content() } } } @@ -420,6 +442,7 @@ private fun CurrentTilesGrid( listState.tiles.fastMap { Pair(it, BounceableTileViewModel()) } } + val primaryColor = MaterialTheme.colorScheme.primary TileLazyGrid( state = gridState, columns = GridCells.Fixed(columns), @@ -428,9 +451,9 @@ private fun CurrentTilesGrid( Modifier.fillMaxWidth() .height { totalHeight.roundToPx() } .border( - width = 1.dp, - color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f), - shape = RoundedCornerShape((TileHeight / 2) + CurrentTilesGridPadding), + width = 2.dp, + color = primaryColor, + shape = RoundedCornerShape(GridBackgroundCornerRadius), ) .dragAndDropTileList(gridState, { gridContentOffset }, listState) { spec -> onSetTiles(currentListState.tileSpecs()) @@ -439,6 +462,13 @@ private fun CurrentTilesGrid( .onGloballyPositioned { coordinates -> gridContentOffset = coordinates.positionInRoot() } + .drawBehind { + drawRoundRect( + primaryColor, + cornerRadius = CornerRadius(GridBackgroundCornerRadius.toPx()), + alpha = .15f, + ) + } .testTag(CURRENT_TILES_GRID_TEST_TAG), ) { EditTiles(cells, listState, selectionState, coroutineScope, largeTilesSpan, onRemoveTile) { @@ -469,7 +499,6 @@ private fun AvailableTileGrid( remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) { groupAndSort(tiles) } - val labelColors = EditModeTileDefaults.editTileColors() // Available tiles Column( @@ -480,32 +509,45 @@ private fun AvailableTileGrid( ) { groupedTiles.forEach { (category, tiles) -> key(category) { - Text( - text = category.label.load() ?: "", - fontSize = 20.sp, - color = labelColors.label, + val surfaceColor = MaterialTheme.colorScheme.surface + Column( + verticalArrangement = spacedBy(16.dp), modifier = - Modifier.fillMaxWidth().padding(start = 16.dp, bottom = 8.dp, top = 8.dp), - ) - tiles.chunked(columns).forEach { row -> - Row( - horizontalArrangement = spacedBy(TileArrangementPadding), - modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max), - ) { - row.forEach { tileGridCell -> - key(tileGridCell.key) { - AvailableTileGridCell( - cell = tileGridCell, - dragAndDropState = dragAndDropState, - selectionState = selectionState, - onAddTile = onAddTile, - modifier = Modifier.weight(1f).fillMaxHeight(), + Modifier.drawBehind { + drawRoundRect( + surfaceColor, + cornerRadius = CornerRadius(GridBackgroundCornerRadius.toPx()), + alpha = .32f, ) } - } + .padding(16.dp), + ) { + Text( + text = category.label.load() ?: "", + fontSize = 20.sp, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.fillMaxWidth().padding(start = 8.dp, bottom = 16.dp), + ) + tiles.chunked(columns).forEach { row -> + Row( + horizontalArrangement = spacedBy(TileArrangementPadding), + modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max), + ) { + row.forEach { tileGridCell -> + key(tileGridCell.key) { + AvailableTileGridCell( + cell = tileGridCell, + dragAndDropState = dragAndDropState, + selectionState = selectionState, + onAddTile = onAddTile, + modifier = Modifier.weight(1f).fillMaxHeight(), + ) + } + } - // Spacers for incomplete rows - repeat(columns - row.size) { Spacer(modifier = Modifier.weight(1f)) } + // Spacers for incomplete rows + repeat(columns - row.size) { Spacer(modifier = Modifier.weight(1f)) } + } } } } @@ -761,7 +803,7 @@ private fun AvailableTileGridCell( color = colors.label, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, - modifier = Modifier.align(Alignment.Center), + modifier = Modifier.align(Alignment.TopCenter), ) } } @@ -861,15 +903,16 @@ private object EditModeTileDefaults { const val AUTO_SCROLL_SPEED = 2 // 2ms per pixel val CurrentTilesGridPadding = 10.dp val AvailableTilesGridMinHeight = 200.dp + val GridBackgroundCornerRadius = 42.dp @Composable fun editTileColors(): TileColors = TileColors( - background = MaterialTheme.colorScheme.surfaceVariant, - iconBackground = MaterialTheme.colorScheme.surfaceVariant, - label = MaterialTheme.colorScheme.onSurfaceVariant, - secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant, - icon = MaterialTheme.colorScheme.onSurfaceVariant, + background = LocalAndroidColorScheme.current.surfaceEffect2, + iconBackground = Color.Transparent, + label = MaterialTheme.colorScheme.onSurface, + secondaryLabel = MaterialTheme.colorScheme.onSurface, + icon = MaterialTheme.colorScheme.onSurface, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 6236ada46374..27e609232a4c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -78,8 +78,15 @@ constructor( } val columns = columnsWithMediaViewModel.columns + val largeTiles by iconTilesViewModel.largeTilesState val largeTilesSpan by iconTilesViewModel.largeTilesSpanState - val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width(largeTilesSpan)) } + // Tiles or largeTiles may be updated while this is composed, so listen to any changes + val sizedTiles = + remember(tiles, largeTiles, largeTilesSpan) { + tiles.map { + SizedTileImpl(it, if (largeTiles.contains(it.spec)) largeTilesSpan else 1) + } + } val bounceables = remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } } val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt index bf63c3858542..6bafd432669a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt @@ -25,6 +25,7 @@ import android.service.quicksettings.Tile.STATE_ACTIVE import android.service.quicksettings.Tile.STATE_INACTIVE import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable @@ -59,6 +60,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.semantics.Role @@ -74,7 +76,9 @@ import androidx.compose.ui.util.trace import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.Expandable import com.android.compose.animation.bounceable +import com.android.compose.animation.rememberExpandableController import com.android.compose.modifiers.thenIf +import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.Flags import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon @@ -165,6 +169,7 @@ fun Tile( // TODO(b/361789146): Draw the shapes instead of clipping val tileShape by TileDefaults.animateTileShapeAsState(uiState.state) val animatedColor by animateColorAsState(colors.background, label = "QSTileBackgroundColor") + val animatedAlpha by animateFloatAsState(colors.alpha, label = "QSTileAlpha") TileExpandable( color = { animatedColor }, @@ -181,7 +186,8 @@ fun Tile( nextBounceable = currentBounceableInfo.nextTile, orientation = Orientation.Horizontal, bounceEnd = currentBounceableInfo.bounceEnd, - ), + ) + .graphicsLayer { alpha = animatedAlpha }, ) { expandable -> val longClick: (() -> Unit)? = { @@ -260,8 +266,7 @@ private fun TileExpandable( content: @Composable (Expandable) -> Unit, ) { Expandable( - color = color(), - shape = shape, + controller = rememberExpandableController(color = color, shape = shape), modifier = modifier.clip(shape).verticalSquish(squishiness), useModifierBasedImplementation = true, ) { @@ -370,6 +375,7 @@ data class TileColors( val label: Color, val secondaryLabel: Color, val icon: Color, + val alpha: Float = 1f, ) private object TileDefaults { @@ -393,10 +399,10 @@ private object TileDefaults { @ReadOnlyComposable fun activeDualTargetTileColors(): TileColors = TileColors( - background = MaterialTheme.colorScheme.surfaceVariant, + background = LocalAndroidColorScheme.current.surfaceEffect2, iconBackground = MaterialTheme.colorScheme.primary, - label = MaterialTheme.colorScheme.onSurfaceVariant, - secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant, + label = MaterialTheme.colorScheme.onSurface, + secondaryLabel = MaterialTheme.colorScheme.onSurface, icon = MaterialTheme.colorScheme.onPrimary, ) @@ -404,34 +410,36 @@ private object TileDefaults { @ReadOnlyComposable fun inactiveDualTargetTileColors(): TileColors = TileColors( - background = MaterialTheme.colorScheme.surfaceVariant, - iconBackground = MaterialTheme.colorScheme.surfaceContainerHighest, - label = MaterialTheme.colorScheme.onSurfaceVariant, - secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant, - icon = MaterialTheme.colorScheme.onSurfaceVariant, + background = LocalAndroidColorScheme.current.surfaceEffect2, + iconBackground = LocalAndroidColorScheme.current.surfaceEffect3, + label = MaterialTheme.colorScheme.onSurface, + secondaryLabel = MaterialTheme.colorScheme.onSurface, + icon = MaterialTheme.colorScheme.onSurface, ) @Composable @ReadOnlyComposable fun inactiveTileColors(): TileColors = TileColors( - background = MaterialTheme.colorScheme.surfaceVariant, - iconBackground = MaterialTheme.colorScheme.surfaceVariant, - label = MaterialTheme.colorScheme.onSurfaceVariant, - secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant, - icon = MaterialTheme.colorScheme.onSurfaceVariant, + background = LocalAndroidColorScheme.current.surfaceEffect2, + iconBackground = Color.Transparent, + label = MaterialTheme.colorScheme.onSurface, + secondaryLabel = MaterialTheme.colorScheme.onSurface, + icon = MaterialTheme.colorScheme.onSurface, ) @Composable @ReadOnlyComposable - fun unavailableTileColors(): TileColors = - TileColors( - background = MaterialTheme.colorScheme.surface, - iconBackground = MaterialTheme.colorScheme.surface, + fun unavailableTileColors(): TileColors { + return TileColors( + background = LocalAndroidColorScheme.current.surfaceEffect2, + iconBackground = LocalAndroidColorScheme.current.surfaceEffect2, label = MaterialTheme.colorScheme.onSurface, secondaryLabel = MaterialTheme.colorScheme.onSurface, icon = MaterialTheme.colorScheme.onSurface, + alpha = .38f, ) + } @Composable @ReadOnlyComposable diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt index a66b51f6fe50..57f63c755b43 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt @@ -62,7 +62,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.toSize import androidx.compose.ui.zIndex -import com.android.compose.modifiers.size import com.android.compose.modifiers.thenIf import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BADGE_ANGLE_RAD @@ -155,6 +154,7 @@ fun InteractiveTileContainer( Icon( Icons.Default.Remove, contentDescription = null, + tint = MaterialTheme.colorScheme.onPrimaryContainer, modifier = Modifier.size(size).align(Alignment.Center).graphicsLayer { this.alpha = badgeIconAlpha @@ -218,14 +218,15 @@ fun StaticTileBadge( ) } ) { - val secondaryColor = MaterialTheme.colorScheme.secondary val size = with(LocalDensity.current) { BadgeIconSize.toDp() } + val primaryColor = MaterialTheme.colorScheme.primary Icon( icon, contentDescription = contentDescription, + tint = MaterialTheme.colorScheme.onPrimary, modifier = Modifier.size(size).align(Alignment.Center).drawBehind { - drawCircle(secondaryColor, radius = BadgeSize.toPx() / 2) + drawCircle(primaryColor, radius = BadgeSize.toPx() / 2) }, ) } @@ -291,7 +292,7 @@ private fun Transition<TileState>.animateColor(): State<Color> { return animateColor { state -> when (state) { None -> Color.Transparent - Removable -> MaterialTheme.colorScheme.secondary + Removable -> MaterialTheme.colorScheme.primaryContainer Selected -> MaterialTheme.colorScheme.primary } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt index a9d673aa7400..d6705a68d32c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt @@ -35,6 +35,9 @@ constructor( private val hydrator = Hydrator("DynamicIconTilesViewModel") private val interactor = interactorFactory.create() + val largeTilesState = + hydrator.hydratedStateOf(traceName = "largeTiles", source = iconTilesViewModel.largeTiles) + val largeTilesSpanState = hydrator.hydratedStateOf( traceName = "largeTilesSpan", diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt index bc15bbb5e57d..263ef09ea767 100644 --- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt @@ -20,6 +20,8 @@ import android.content.Context import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.feature.flags.Flags import androidx.annotation.VisibleForTesting +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -28,8 +30,11 @@ import com.android.systemui.statusbar.phone.SystemUIDialog import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch /** * Provides a {@link com.android.systemui.statusbar.phone.SystemUIDialog} to be shown on the inner @@ -46,6 +51,7 @@ internal constructor( private val rearDisplayStateInteractor: RearDisplayStateInteractor, private val rearDisplayInnerDialogDelegateFactory: RearDisplayInnerDialogDelegate.Factory, @Application private val scope: CoroutineScope, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : CoreStartable, AutoCloseable { companion object { @@ -53,6 +59,16 @@ internal constructor( } @VisibleForTesting var stateChangeListener: Job? = null + private val keyguardVisible = MutableStateFlow(false) + private val keyguardVisibleFlow = keyguardVisible.asStateFlow() + + @VisibleForTesting + val keyguardCallback = + object : KeyguardUpdateMonitorCallback() { + override fun onKeyguardVisibilityChanged(visible: Boolean) { + keyguardVisible.value = visible + } + } override fun close() { stateChangeListener?.cancel() @@ -62,28 +78,39 @@ internal constructor( if (Flags.deviceStateRdmV2()) { var dialog: SystemUIDialog? = null + keyguardUpdateMonitor.registerCallback(keyguardCallback) + stateChangeListener = - rearDisplayStateInteractor.state - .map { - when (it) { - is RearDisplayStateInteractor.State.Enabled -> { - val rearDisplayContext = - context.createDisplayContext(it.innerDisplay) - val delegate = - rearDisplayInnerDialogDelegateFactory.create( - rearDisplayContext, - deviceStateManager::cancelStateRequest, - ) - dialog = delegate.createDialog().apply { show() } - } + scope.launch { + combine(rearDisplayStateInteractor.state, keyguardVisibleFlow) { + rearDisplayState, + keyguardVisible -> + Pair(rearDisplayState, keyguardVisible) + } + .collectLatest { (rearDisplayState, keyguardVisible) -> + when (rearDisplayState) { + is RearDisplayStateInteractor.State.Enabled -> { + if (!keyguardVisible) { + val rearDisplayContext = + context.createDisplayContext( + rearDisplayState.innerDisplay + ) + val delegate = + rearDisplayInnerDialogDelegateFactory.create( + rearDisplayContext, + deviceStateManager::cancelStateRequest, + ) + dialog = delegate.createDialog().apply { show() } + } + } - is RearDisplayStateInteractor.State.Disabled -> { - dialog?.dismiss() - dialog = null + is RearDisplayStateInteractor.State.Disabled -> { + dialog?.dismiss() + dialog = null + } } } - } - .launchIn(scope) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java index 4be35f147c2f..d2639654d206 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java @@ -89,6 +89,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.contextualeducation.GestureType; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.display.data.repository.DisplayRepository; +import com.android.systemui.display.data.repository.PerDisplayRepository; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardWmStateRefactor; @@ -109,6 +111,7 @@ import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.domain.interactor.ShadeInteractor; +import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; import com.android.systemui.shared.recents.ILauncherProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.QuickStepContract; @@ -156,7 +159,9 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis private final Executor mMainExecutor; private final ShellInterface mShellInterface; private final Lazy<ShadeViewController> mShadeViewControllerLazy; - private SysUiState mSysUiState; + private final PerDisplayRepository<SysUiState> mPerDisplaySysUiStateRepository; + private final DisplayRepository mDisplayRepository; + private SysUiState mDefaultDisplaySysUIState; private final Handler mHandler; private final Lazy<NavigationBarController> mNavBarControllerLazy; private final ScreenPinningRequest mScreenPinningRequest; @@ -586,9 +591,12 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis // Force-update the systemui state flags updateSystemUiStateFlags(); - // TODO b/398011576 - send the state for all displays. - notifySystemUiStateFlags(mSysUiState.getFlags(), Display.DEFAULT_DISPLAY); - + if (ShadeWindowGoesAround.isEnabled()) { + notifySysUiStateFlagsForAllDisplays(); + } else { + notifySystemUiStateFlags(mDefaultDisplaySysUIState.getFlags(), + Display.DEFAULT_DISPLAY); + } notifyConnectionChanged(); } @@ -614,6 +622,18 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis } }; + /** Propagates the flags for all displays to be notified to Launcher. */ + @VisibleForTesting + public void notifySysUiStateFlagsForAllDisplays() { + var displays = mDisplayRepository.getDisplayIds().getValue(); + for (int displayId : displays) { + var state = mPerDisplaySysUiStateRepository.get(displayId); + if (state != null) { + notifySystemUiStateFlags(state.getFlags(), displayId); + } + } + } + private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged; // This is the death handler for the binder from the launcher service @@ -671,7 +691,7 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis ScreenPinningRequest screenPinningRequest, NavigationModeController navModeController, NotificationShadeWindowController statusBarWinController, - SysUiState sysUiState, + PerDisplayRepository<SysUiState> perDisplaySysUiStateRepository, Provider<SceneInteractor> sceneInteractor, Provider<ShadeInteractor> shadeInteractor, UserTracker userTracker, @@ -686,7 +706,8 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder, BroadcastDispatcher broadcastDispatcher, Optional<BackAnimation> backAnimation, - ProcessWrapper processWrapper + ProcessWrapper processWrapper, + DisplayRepository displayRepository ) { // b/241601880: This component should only be running for primary users or // secondaryUsers when visibleBackgroundUsers are supported. @@ -718,10 +739,10 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis com.android.internal.R.string.config_recentsComponentName)); mQuickStepIntent = new Intent(ACTION_QUICKSTEP) .setPackage(mRecentsComponentName.getPackageName()); - // TODO b/398011576 - Here we're still only handling the default display state. We should - // have a callback for any sysuiState change. - mSysUiState = sysUiState; - mSysUiState.addCallback(mSysUiStateCallback); + mPerDisplaySysUiStateRepository = perDisplaySysUiStateRepository; + mDisplayRepository = displayRepository; + mDefaultDisplaySysUIState = perDisplaySysUiStateRepository.get(Display.DEFAULT_DISPLAY); + mDefaultDisplaySysUIState.addCallback(mSysUiStateCallback); mUiEventLogger = uiEventLogger; mDisplayTracker = displayTracker; mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder; @@ -770,7 +791,7 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis if (mLauncherProxy != null) { try { if (DesktopModeStatus.canEnterDesktopMode(mContext) - && (sysUiState.getFlags() + && (mDefaultDisplaySysUIState.getFlags() & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0) { return; } @@ -795,7 +816,7 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis } public void onVoiceSessionWindowVisibilityChanged(boolean visible) { - mSysUiState.setFlag(SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING, visible) + mDefaultDisplaySysUIState.setFlag(SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING, visible) .commitUpdate(mContext.getDisplayId()); } @@ -804,23 +825,42 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis startConnectionToCurrentUser(); } - private void updateSystemUiStateFlags() { + private void updateSysUIStateForNavbars() { + if (ShadeWindowGoesAround.isEnabled()) { + var displays = mDisplayRepository.getDisplayIds().getValue(); + for (int displayId : displays) { + updateSysUIStateForNavbarWithDisplayId(displayId); + } + } else { + updateSysUIStateForNavbarWithDisplayId(Display.DEFAULT_DISPLAY); + } + } + + private void updateSysUIStateForNavbarWithDisplayId(int displayId) { final NavigationBar navBarFragment = - mNavBarControllerLazy.get().getDefaultNavigationBar(); + mNavBarControllerLazy.get().getNavigationBar(displayId); final NavigationBarView navBarView = - mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId()); + mNavBarControllerLazy.get().getNavigationBarView(displayId); if (SysUiState.DEBUG) { Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment + " navBarView=" + navBarView + " shadeViewController=" + mShadeViewControllerLazy.get()); } + final SysUiState displaySysuiState = mPerDisplaySysUiStateRepository.get(displayId); + if (displaySysuiState == null) return; + if (navBarFragment != null) { navBarFragment.updateSystemUiStateFlags(); } if (navBarView != null) { - navBarView.updateDisabledSystemUiStateFlags(mSysUiState); + navBarView.updateDisabledSystemUiStateFlags(displaySysuiState); } + } + + /** Force updates SystemUI state flags prior to sending them to Launcher. */ + public void updateSystemUiStateFlags() { + updateSysUIStateForNavbars(); mShadeViewControllerLazy.get().updateSystemUiStateFlags(); if (mStatusBarWinController != null) { mStatusBarWinController.notifyStateChangedCallbacks(); @@ -845,7 +885,7 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean keyguardGoingAway, boolean bouncerShowing, boolean isDozing, boolean panelExpanded, boolean isDreaming, boolean communalShowing) { - mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING, + mDefaultDisplaySysUIState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING, keyguardShowing && !keyguardOccluded) .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED, keyguardShowing && keyguardOccluded) @@ -1122,7 +1162,7 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis new WakefulnessLifecycle.Observer() { @Override public void onStartedWakingUp() { - mSysUiState + mDefaultDisplaySysUIState .setFlag(SYSUI_STATE_AWAKE, true) .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, true) .commitUpdate(mContext.getDisplayId()); @@ -1130,7 +1170,7 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis @Override public void onFinishedWakingUp() { - mSysUiState + mDefaultDisplaySysUIState .setFlag(SYSUI_STATE_AWAKE, true) .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, false) .commitUpdate(mContext.getDisplayId()); @@ -1138,7 +1178,7 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis @Override public void onStartedGoingToSleep() { - mSysUiState + mDefaultDisplaySysUIState .setFlag(SYSUI_STATE_AWAKE, false) .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, true) .commitUpdate(mContext.getDisplayId()); @@ -1146,7 +1186,7 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis @Override public void onFinishedGoingToSleep() { - mSysUiState + mDefaultDisplaySysUIState .setFlag(SYSUI_STATE_AWAKE, false) .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, false) .commitUpdate(mContext.getDisplayId()); @@ -1247,7 +1287,7 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion); pw.print(" mNavBarMode="); pw.println(mNavBarMode); pw.print(" mIsPrevServiceCleanedUp="); pw.println(mIsPrevServiceCleanedUp); - mSysUiState.dump(pw, args); + mDefaultDisplaySysUIState.dump(pw, args); } public interface LauncherProxyListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt index f45971b57b65..2bacee12db8a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt @@ -86,7 +86,7 @@ open class BlurUtils @Inject constructor( */ fun prepareBlur(viewRootImpl: ViewRootImpl?, radius: Int) { if (viewRootImpl == null || !viewRootImpl.surfaceControl.isValid || - !supportsBlursOnWindows() || earlyWakeupEnabled + !shouldBlur(radius) || earlyWakeupEnabled ) { return } @@ -113,7 +113,7 @@ open class BlurUtils @Inject constructor( return } createTransaction().use { - if (supportsBlursOnWindows()) { + if (shouldBlur(radius)) { it.setBackgroundBlurRadius(viewRootImpl.surfaceControl, radius) if (!earlyWakeupEnabled && lastAppliedBlur == 0 && radius != 0) { Trace.asyncTraceForTrackBegin( @@ -142,6 +142,14 @@ open class BlurUtils @Inject constructor( return SurfaceControl.Transaction() } + private fun shouldBlur(radius: Int): Boolean { + return supportsBlursOnWindows() || + ((Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()) && + supportsBlursOnWindowsBase() && + lastAppliedBlur > 0 && + radius == 0) + } + /** * If this device can render blurs. * @@ -149,8 +157,11 @@ open class BlurUtils @Inject constructor( * @return {@code true} when supported. */ open fun supportsBlursOnWindows(): Boolean { + return supportsBlursOnWindowsBase() && crossWindowBlurListeners.isCrossWindowBlurEnabled + } + + private fun supportsBlursOnWindowsBase(): Boolean { return CROSS_WINDOW_BLUR_SUPPORTED && ActivityManager.isHighEndGfx() && - crossWindowBlurListeners.isCrossWindowBlurEnabled() && !SystemProperties.getBoolean("persist.sysui.disableBlur", false) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 05ef1645c1d6..d2f424a46620 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -394,7 +394,7 @@ constructor( // Only drag down on sensitive views, otherwise the ExpandHelper will take this return if (NotificationBundleUi.isEnabled) view.entryAdapter?.isSensitive?.value == true - else view.entry.isSensitive.value + else view.entryLegacy.isSensitive.value } } return false @@ -569,7 +569,7 @@ constructor( if (NotificationBundleUi.isEnabled) { userId = expandView.entryAdapter?.sbn?.userId!! } else { - userId = expandView.entry.sbn.userId + userId = expandView.entryLegacy.sbn.userId } } var fullShadeNeedsBouncer = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 339f898be251..9bf3d5dfe4cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -47,7 +47,6 @@ import android.database.ExecutorContentObserver; import android.net.Uri; import android.os.Looper; import android.os.Process; -import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -78,6 +77,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction; @@ -920,7 +920,9 @@ public class NotificationLockscreenUserManagerImpl implements // notification's "when" time, or the notification entry creation time private long getEarliestNotificationTime(NotificationEntry notif) { long notifWhenWallClock = notif.getSbn().getNotification().getWhen(); - long creationTimeDelta = SystemClock.uptimeMillis() - notif.getCreationTime(); + long creationTimeDelta = UseElapsedRealtimeForCreationTime.getCurrentTime() + - notif.getCreationTime(); + long creationTimeWallClock = System.currentTimeMillis() - creationTimeDelta; return Math.min(notifWhenWallClock, creationTimeWallClock); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 041ed6504634..485d5b2ab555 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -169,7 +169,7 @@ public class NotificationRemoteInputManager implements CoreStartable { if (NotificationBundleUi.isEnabled()) { releaseNotificationIfKeptForRemoteInputHistory(row.getEntryAdapter()); } else { - releaseNotificationIfKeptForRemoteInputHistory(row.getEntry()); + releaseNotificationIfKeptForRemoteInputHistory(row.getEntryLegacy()); } } return started; @@ -189,8 +189,8 @@ public class NotificationRemoteInputManager implements CoreStartable { statusBarNotification = row.getEntryAdapter().getSbn(); } } else { - if (row.getEntry() != null) { - statusBarNotification = row.getEntry().getSbn(); + if (row.getEntryLegacy() != null) { + statusBarNotification = row.getEntryLegacy().getSbn(); } } if (statusBarNotification == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 2e83910a2a93..472dc823423e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -19,8 +19,6 @@ package com.android.systemui.statusbar import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator -import android.content.Context -import android.content.res.Configuration import android.os.SystemClock import android.util.IndentingPrintWriter import android.util.Log @@ -42,16 +40,14 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.ShadeExpansionChangeEvent import com.android.systemui.shade.ShadeExpansionListener +import com.android.systemui.shade.domain.interactor.ShadeModeInteractor import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScrimController -import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.WallpaperController import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor @@ -82,13 +78,11 @@ constructor( private val wallpaperInteractor: WallpaperInteractor, private val notificationShadeWindowController: NotificationShadeWindowController, private val dozeParameters: DozeParameters, - @ShadeDisplayAware private val context: Context, - private val splitShadeStateController: SplitShadeStateController, + private val shadeModeInteractor: ShadeModeInteractor, private val windowRootViewBlurInteractor: WindowRootViewBlurInteractor, private val appZoomOutOptional: Optional<AppZoomOut>, @Application private val applicationScope: CoroutineScope, dumpManager: DumpManager, - configurationController: ConfigurationController, ) : ShadeExpansionListener, Dumpable { companion object { private const val WAKE_UP_ANIMATION_ENABLED = true @@ -110,7 +104,6 @@ constructor( private var isOpen: Boolean = false private var isBlurred: Boolean = false private var listeners = mutableListOf<DepthListener>() - private var inSplitShade: Boolean = false private var prevTracking: Boolean = false private var prevTimestamp: Long = -1 @@ -294,7 +287,7 @@ constructor( private fun blurRadiusToZoomOut(blurRadius: Float): Float { var zoomOut = MathUtils.saturate(blurUtils.ratioOfBlurRadius(blurRadius)) - if (inSplitShade) { + if (shadeModeInteractor.isSplitShade) { zoomOut = 0f } @@ -432,14 +425,6 @@ constructor( } shadeAnimation.setStiffness(SpringForce.STIFFNESS_LOW) shadeAnimation.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY) - updateResources() - configurationController.addCallback( - object : ConfigurationController.ConfigurationListener { - override fun onConfigChanged(newConfig: Configuration?) { - updateResources() - } - } - ) applicationScope.launch { wallpaperInteractor.wallpaperSupportsAmbientMode.collect { supported -> wallpaperSupportsAmbientMode = supported @@ -469,10 +454,6 @@ constructor( } } - private fun updateResources() { - inSplitShade = splitShadeStateController.shouldUseSplitNotificationShade(context.resources) - } - fun addListener(listener: DepthListener) { listeners.add(listener) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index f88c618a9acc..c2a87cddee55 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -675,7 +675,7 @@ public class NotificationShelf extends ActivatableNotificationView { } StatusBarIconView icon = NotificationBundleUi.isEnabled() ? row.getEntryAdapter().getIcons().getShelfIcon() - : row.getEntry().getIcons().getShelfIcon(); + : row.getEntryLegacy().getIcons().getShelfIcon(); float shelfIconPosition = getTranslationY() + icon.getTop() + icon.getTranslationY(); if (shelfIconPosition < maxTop && !mAmbientState.isFullyHidden()) { int top = (int) (maxTop - shelfIconPosition); @@ -689,7 +689,7 @@ public class NotificationShelf extends ActivatableNotificationView { private void updateContinuousClipping(final ExpandableNotificationRow row) { StatusBarIconView icon = NotificationBundleUi.isEnabled() ? row.getEntryAdapter().getIcons().getShelfIcon() - : row.getEntry().getIcons().getShelfIcon(); + : row.getEntryLegacy().getIcons().getShelfIcon(); boolean needsContinuousClipping = ViewState.isAnimatingY(icon) && !mAmbientState.isDozing(); boolean isContinuousClipping = icon.getTag(TAG_CONTINUOUS_CLIPPING) != null; if (needsContinuousClipping && !isContinuousClipping) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS index 6cebcd98a0ba..6d3c12d139db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS @@ -18,10 +18,10 @@ per-file *Keyguard* = file:../keyguard/OWNERS per-file *Notification* = file:notification/OWNERS # Files that control blur effects on shade per-file *NotificationShadeDepth* = set noparent -per-file *NotificationShadeDepth* = shanh@google.com, rahulbanerjee@google.com +per-file *NotificationShadeDepth* = shanh@google.com, rahulbanerjee@google.com, tracyzhou@google.com per-file *NotificationShadeDepth* = file:../keyguard/OWNERS per-file *Blur* = set noparent -per-file *Blur* = shanh@google.com, rahulbanerjee@google.com +per-file *Blur* = shanh@google.com, rahulbanerjee@google.com, tracyzhou@google.com # Not setting noparent here, since *Mode* matches many other classes (e.g., *ViewModel*) per-file *Mode* = file:notification/OWNERS per-file *SmartReply* = set noparent diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt index 7e7031200988..03108deb0ecc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt @@ -17,10 +17,13 @@ package com.android.systemui.statusbar.chips.call.ui.viewmodel import android.app.PendingIntent +import android.content.ComponentName import android.content.Context import android.view.View import com.android.internal.jank.Cuj import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.animation.ComposableControllerFactory +import com.android.systemui.animation.DelegateTransitionAnimatorController import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton @@ -48,7 +51,10 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.stateIn /** View model for the ongoing phone call chip shown in the status bar. */ @@ -63,14 +69,62 @@ constructor( private val activityStarter: ActivityStarter, @StatusBarChipsLog private val logger: LogBuffer, ) : OngoingActivityChipViewModel { + /** The transition cookie used to register and unregister launch and return animations. */ + private val cookie = + ActivityTransitionAnimator.TransitionCookie("${CallChipViewModel::class.java}") + + /** + * Used internally to determine when a launch or return animation is in progress, as these + * require special handling. + */ + private val transitionState: MutableStateFlow<TransitionState> = + MutableStateFlow(TransitionState.NoTransition) + + // Since we're combining the chip state and the transition state flows, getting the old value by + // using [pairwise()] would confuse things. This is because if the calculation is triggered by + // a change in transition state, the chip state will still show the previous and current values, + // making it difficult to figure out what actually changed. Instead we cache the old value here, + // so that at each update we can keep track of what actually changed. + private var latestState: OngoingCallModel = OngoingCallModel.NoCall + private var latestTransitionState: TransitionState = TransitionState.NoTransition + private val chipWithReturnAnimation: StateFlow<OngoingActivityChipModel> = if (StatusBarChipsReturnAnimations.isEnabled) { - interactor.ongoingCallState - .map { state -> - when (state) { - is OngoingCallModel.NoCall -> OngoingActivityChipModel.Inactive() + combine(interactor.ongoingCallState, transitionState) { newState, newTransitionState -> + val oldState = latestState + latestState = newState + val oldTransitionState = latestTransitionState + latestTransitionState = newTransitionState + + logger.log( + TAG, + LogLevel.DEBUG, + {}, + { + "Call chip state updated: oldState=$oldState newState=$newState " + + "oldTransitionState=$oldTransitionState " + + "newTransitionState=$newTransitionState" + }, + ) + + when (newState) { + is OngoingCallModel.NoCall -> + OngoingActivityChipModel.Inactive( + transitionManager = getTransitionManager(newState) + ) + is OngoingCallModel.InCall -> - prepareChip(state, systemClock, isHidden = state.isAppVisible) + prepareChip( + newState, + systemClock, + isHidden = + shouldChipBeHidden( + oldState = oldState, + newState = newState, + oldTransitionState = oldTransitionState, + newTransitionState = newTransitionState, + ), + ) } } .stateIn( @@ -112,6 +166,12 @@ constructor( chipLegacy } + /** + * The controller factory that the call chip uses to register and unregister its transition + * animations. + */ + private var transitionControllerFactory: ComposableControllerFactory? = null + /** Builds an [OngoingActivityChipModel.Active] from all the relevant information. */ private fun prepareChip( state: OngoingCallModel.InCall, @@ -149,6 +209,7 @@ constructor( onClickListenerLegacy = getOnClickListener(state.intent), clickBehavior = getClickBehavior(state.intent), isHidden = isHidden, + transitionManager = getTransitionManager(state), ) } else { val startTimeInElapsedRealtime = @@ -161,6 +222,7 @@ constructor( onClickListenerLegacy = getOnClickListener(state.intent), clickBehavior = getClickBehavior(state.intent), isHidden = isHidden, + transitionManager = getTransitionManager(state), ) } } @@ -191,9 +253,21 @@ constructor( onClick = { expandable -> StatusBarChipsModernization.unsafeAssertInNewMode() val animationController = - expandable.activityTransitionController( - Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP - ) + if ( + !StatusBarChipsReturnAnimations.isEnabled || + transitionControllerFactory == null + ) { + expandable.activityTransitionController( + Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP + ) + } else { + // When return animations are enabled, we use a long-lived registration + // with controllers created on-demand by the animation library instead + // of explicitly creating one at the time of the click. By not passing + // a controller here, we let the framework do its work. Otherwise, the + // explicit controller would take precedence and override the other one. + null + } activityStarter.postStartActivityDismissingKeyguard(intent, animationController) } ) @@ -210,6 +284,120 @@ constructor( ) } + private fun getTransitionManager( + state: OngoingCallModel + ): OngoingActivityChipModel.TransitionManager? { + if (!StatusBarChipsReturnAnimations.isEnabled) return null + return if (state is OngoingCallModel.NoCall) { + OngoingActivityChipModel.TransitionManager( + unregisterTransition = { activityStarter.unregisterTransition(cookie) } + ) + } else { + val component = (state as OngoingCallModel.InCall).intent?.intent?.component + if (component != null) { + val factory = getTransitionControllerFactory(component) + OngoingActivityChipModel.TransitionManager( + factory, + registerTransition = { + activityStarter.registerTransition(cookie, factory, scope) + }, + ) + } else { + // Without a component we can't instantiate a controller factory, and without a + // factory registering an animation is impossible. In this case, the transition + // manager is empty and inert. + OngoingActivityChipModel.TransitionManager() + } + } + } + + private fun getTransitionControllerFactory( + component: ComponentName + ): ComposableControllerFactory { + var factory = transitionControllerFactory + if (factory?.component == component) return factory + + factory = + object : + ComposableControllerFactory( + cookie, + component, + launchCujType = Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, + ) { + override suspend fun createController( + forLaunch: Boolean + ): ActivityTransitionAnimator.Controller { + transitionState.value = + if (forLaunch) { + TransitionState.LaunchRequested + } else { + TransitionState.ReturnRequested + } + + val controller = + expandable + .mapNotNull { + it?.activityTransitionController( + launchCujType, + cookie, + component, + returnCujType, + isEphemeral = false, + ) + } + .first() + + return object : DelegateTransitionAnimatorController(controller) { + override val isLaunching: Boolean + get() = forLaunch + + override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { + delegate.onTransitionAnimationStart(isExpandingFullyAbove) + transitionState.value = + if (isLaunching) { + TransitionState.Launching + } else { + TransitionState.Returning + } + } + + override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { + delegate.onTransitionAnimationEnd(isExpandingFullyAbove) + transitionState.value = TransitionState.NoTransition + } + + override fun onTransitionAnimationCancelled( + newKeyguardOccludedState: Boolean? + ) { + delegate.onTransitionAnimationCancelled(newKeyguardOccludedState) + transitionState.value = TransitionState.NoTransition + } + } + } + } + + transitionControllerFactory = factory + return factory + } + + /** Define the current state of this chip's transition animation. */ + private sealed interface TransitionState { + /** Idle. */ + data object NoTransition : TransitionState + + /** Launch animation has been requested but hasn't started yet. */ + data object LaunchRequested : TransitionState + + /** Launch animation in progress. */ + data object Launching : TransitionState + + /** Return animation has been requested but hasn't started yet. */ + data object ReturnRequested : TransitionState + + /** Return animation in progress. */ + data object Returning : TransitionState + } + companion object { private val phoneIcon = Icon.Resource( @@ -217,5 +405,42 @@ constructor( ContentDescription.Resource(R.string.ongoing_call_content_description), ) private val TAG = "CallVM".pad() + + /** Determines whether or not an active call chip should be hidden. */ + private fun shouldChipBeHidden( + oldState: OngoingCallModel, + newState: OngoingCallModel.InCall, + oldTransitionState: TransitionState, + newTransitionState: TransitionState, + ): Boolean { + // The app is in the background and no transitions are ongoing (during transitions, + // [isAppVisible] must always be true). Show the chip. + if (!newState.isAppVisible) return false + + // The call has just started and is visible. Hide the chip. + if (oldState is OngoingCallModel.NoCall) return true + + // The state went from the app not being visible to visible. This happens when the chip + // is tapped and a launch animation is about to start. Keep the chip showing. + if (!(oldState as OngoingCallModel.InCall).isAppVisible) return false + + // The app was and remains visible, but the transition state has changed. A launch or + // return animation has been requested or is ongoing. Keep the chip showing. + if ( + newTransitionState is TransitionState.LaunchRequested || + newTransitionState is TransitionState.Launching || + newTransitionState is TransitionState.ReturnRequested || + newTransitionState is TransitionState.Returning + ) { + return false + } + + // The app was and remains visible, so we generally want to hide the chip. The only + // exception is if a return transition has just ended. In this case, the transition + // state changes shortly before the app visibility does. If we hide the chip between + // these two updates, this results in a flicker. We bridge the gap by keeping the chip + // showing. + return oldTransitionState != TransitionState.Returning + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt index 2fe627020ebf..1a802d634894 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -141,7 +141,7 @@ constructor( // When we're promoting notifications automatically, the `when` time set on the // notification will likely just be set to the current time, which would cause the chip // to always show "now". We don't want early testers to get that experience since it's - // not what will happen at launch, so just don't show any time. + // not what will happen at launch, so just don't show any time.onometerstate return OngoingActivityChipModel.Active.IconOnly( this.key, icon, @@ -194,14 +194,14 @@ constructor( } } is PromotedNotificationContentModel.When.Chronometer -> { - // TODO(b/364653005): Check isCountDown and support CountDown. return OngoingActivityChipModel.Active.Timer( this.key, icon, colors, startTimeMs = this.promotedContent.time.elapsedRealtimeMillis, - onClickListenerLegacy, - clickBehavior, + isEventInFuture = this.promotedContent.time.isCountDown, + onClickListenerLegacy = onClickListenerLegacy, + clickBehavior = clickBehavior, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt index 2032ec8af78c..1eb46d8cc3d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.chips.ui.binder +import android.annotation.ElapsedRealtimeLong import com.android.systemui.statusbar.chips.ui.view.ChipChronometer object ChipChronometerBinder { @@ -25,9 +26,11 @@ object ChipChronometerBinder { * @param startTimeMs the time this event started, relative to * [com.android.systemui.util.time.SystemClock.elapsedRealtime]. See * [android.widget.Chronometer.setBase]. + * @param isCountDown see [android.widget.Chronometer.setCountDown]. */ - fun bind(startTimeMs: Long, view: ChipChronometer) { + fun bind(@ElapsedRealtimeLong startTimeMs: Long, isCountDown: Boolean, view: ChipChronometer) { view.base = startTimeMs + view.isCountDown = isCountDown view.start() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt index 6f8552738d33..77e0dde3dec5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt @@ -315,7 +315,11 @@ object OngoingActivityChipBinder { chipShortTimeDeltaView.visibility = View.GONE } is OngoingActivityChipModel.Active.Timer -> { - ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView) + ChipChronometerBinder.bind( + chipModel.startTimeMs, + chipModel.isEventInFuture, + chipTimeView, + ) chipTimeView.visibility = View.VISIBLE chipTextView.visibility = View.GONE diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt index 55d753662a65..fa8d25623d67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt @@ -74,25 +74,31 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier = val textMeasurer = rememberTextMeasurer() when (viewModel) { is OngoingActivityChipModel.Active.Timer -> { - val timerState = rememberChronometerState(startTimeMillis = viewModel.startTimeMs) - val text = timerState.currentTimeText - Text( - text = text, - style = textStyle, - color = textColor, - softWrap = false, - modifier = - modifier - .hideTextIfDoesNotFit( - text = text, - textStyle = textStyle, - textMeasurer = textMeasurer, - maxTextWidth = maxTextWidth, - startPadding = startPadding, - endPadding = endPadding, - ) - .neverDecreaseWidth(density), - ) + val timerState = + rememberChronometerState( + eventTimeMillis = viewModel.startTimeMs, + isCountDown = viewModel.isEventInFuture, + timeSource = viewModel.timeSource, + ) + timerState.currentTimeText?.let { text -> + Text( + text = text, + style = textStyle, + color = textColor, + softWrap = false, + modifier = + modifier + .hideTextIfDoesNotFit( + text = text, + textStyle = textStyle, + textMeasurer = textMeasurer, + maxTextWidth = maxTextWidth, + startPadding = startPadding, + endPadding = endPadding, + ) + .neverDecreaseWidth(density), + ) + } } is OngoingActivityChipModel.Active.Countdown -> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt index 4edb23dc9f0e..58d38903f7cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt @@ -42,6 +42,7 @@ import com.android.systemui.common.ui.compose.Icon import com.android.systemui.common.ui.compose.load import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.chips.StatusBarChipsReturnAnimations import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.core.StatusBarConnectedDisplays @@ -90,6 +91,8 @@ fun OngoingActivityChip( }, borderStroke = borderStroke, onClick = onClick, + useModifierBasedImplementation = StatusBarChipsReturnAnimations.isEnabled, + transitionControllerFactory = model.transitionManager?.controllerFactory, ) { ChipBody(model, iconViewStore, isClickable = onClick != null) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt index 7080c3402b08..700e6d93c628 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt @@ -21,12 +21,14 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.res.R +import com.android.systemui.statusbar.chips.StatusBarChipsReturnAnimations import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder @@ -36,18 +38,30 @@ fun OngoingActivityChips( iconViewStore: NotificationIconContainerViewBinder.IconViewStore?, modifier: Modifier = Modifier, ) { - Row( - modifier = - modifier - .fillMaxHeight() - .padding(start = dimensionResource(R.dimen.ongoing_activity_chip_margin_start)), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = - Arrangement.spacedBy(dimensionResource(R.dimen.ongoing_activity_chip_margin_start)), - ) { - chips.active - .filter { !it.isHidden } - .forEach { + if (StatusBarChipsReturnAnimations.isEnabled) { + SideEffect { + // Active chips must always be capable of animating to/from activities, even when they + // are hidden. Therefore we always register their transitions. + for (chip in chips.active) chip.transitionManager?.registerTransition?.invoke() + // Inactive chips and chips in the overflow are never shown, so they must not have any + // registered transition. + for (chip in chips.overflow) chip.transitionManager?.unregisterTransition?.invoke() + for (chip in chips.inactive) chip.transitionManager?.unregisterTransition?.invoke() + } + } + + val shownChips = chips.active.filter { !it.isHidden } + if (shownChips.isNotEmpty()) { + Row( + modifier = + modifier + .fillMaxHeight() + .padding(start = dimensionResource(R.dimen.ongoing_activity_chip_margin_start)), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = + Arrangement.spacedBy(dimensionResource(R.dimen.ongoing_activity_chip_margin_start)), + ) { + shownChips.forEach { key(it.key) { OngoingActivityChip( model = it, @@ -56,5 +70,6 @@ fun OngoingActivityChips( ) } } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index 8e470742f174..3876d9fa77a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -18,12 +18,15 @@ package com.android.systemui.statusbar.chips.ui.model import android.annotation.CurrentTimeMillisLong import android.annotation.ElapsedRealtimeLong +import android.os.SystemClock import android.view.View +import com.android.systemui.animation.ComposableControllerFactory import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips +import com.android.systemui.statusbar.chips.ui.viewmodel.TimeSource import com.android.systemui.statusbar.core.StatusBarConnectedDisplays /** Model representing the display of an ongoing activity as a chip in the status bar. */ @@ -31,6 +34,9 @@ sealed class OngoingActivityChipModel { /** Condensed name representing the model, used for logs. */ abstract val logName: String + /** Object used to manage the behavior of this chip during activity launch and returns. */ + abstract val transitionManager: TransitionManager? + /** * This chip shouldn't be shown. * @@ -38,7 +44,10 @@ sealed class OngoingActivityChipModel { * animated, and false if that transition should *not* be animated (i.e. the chip view should * immediately disappear). */ - data class Inactive(val shouldAnimate: Boolean = true) : OngoingActivityChipModel() { + data class Inactive( + val shouldAnimate: Boolean = true, + override val transitionManager: TransitionManager? = null, + ) : OngoingActivityChipModel() { override val logName = "Inactive(anim=$shouldAnimate)" } @@ -59,6 +68,7 @@ sealed class OngoingActivityChipModel { open val onClickListenerLegacy: View.OnClickListener?, /** Data class that determines how clicks on the chip should be handled. */ open val clickBehavior: ClickBehavior, + override val transitionManager: TransitionManager?, /** * Whether this chip should be hidden. This can be the case depending on system states (like * which apps are in the foreground and whether there is an ongoing transition. @@ -75,6 +85,7 @@ sealed class OngoingActivityChipModel { override val colors: ColorsModel, override val onClickListenerLegacy: View.OnClickListener?, override val clickBehavior: ClickBehavior, + override val transitionManager: TransitionManager? = null, override val isHidden: Boolean = false, override val shouldAnimate: Boolean = true, ) : @@ -84,6 +95,7 @@ sealed class OngoingActivityChipModel { colors, onClickListenerLegacy, clickBehavior, + transitionManager, isHidden, shouldAnimate, ) { @@ -105,8 +117,22 @@ sealed class OngoingActivityChipModel { * [android.widget.Chronometer.setBase]. */ @ElapsedRealtimeLong val startTimeMs: Long, + + /** + * The [TimeSource] that should be used to track the current time for this timer. Should + * be compatible with [startTimeMs]. + */ + val timeSource: TimeSource = TimeSource { SystemClock.elapsedRealtime() }, + + /** + * True if this chip represents an event starting in the future and false if this chip + * represents an event that has already started. If true, [startTimeMs] should be in the + * future. Otherwise, [startTimeMs] should be in the past. + */ + val isEventInFuture: Boolean = false, override val onClickListenerLegacy: View.OnClickListener?, override val clickBehavior: ClickBehavior, + override val transitionManager: TransitionManager? = null, override val isHidden: Boolean = false, override val shouldAnimate: Boolean = true, ) : @@ -116,6 +142,7 @@ sealed class OngoingActivityChipModel { colors, onClickListenerLegacy, clickBehavior, + transitionManager, isHidden, shouldAnimate, ) { @@ -142,6 +169,7 @@ sealed class OngoingActivityChipModel { @CurrentTimeMillisLong val time: Long, override val onClickListenerLegacy: View.OnClickListener?, override val clickBehavior: ClickBehavior, + override val transitionManager: TransitionManager? = null, override val isHidden: Boolean = false, override val shouldAnimate: Boolean = true, ) : @@ -151,6 +179,7 @@ sealed class OngoingActivityChipModel { colors, onClickListenerLegacy, clickBehavior, + transitionManager, isHidden, shouldAnimate, ) { @@ -170,6 +199,7 @@ sealed class OngoingActivityChipModel { override val colors: ColorsModel, /** The number of seconds until an event is started. */ val secondsUntilStarted: Long, + override val transitionManager: TransitionManager? = null, override val isHidden: Boolean = false, override val shouldAnimate: Boolean = true, ) : @@ -179,6 +209,7 @@ sealed class OngoingActivityChipModel { colors, onClickListenerLegacy = null, clickBehavior = ClickBehavior.None, + transitionManager, isHidden, shouldAnimate, ) { @@ -194,6 +225,7 @@ sealed class OngoingActivityChipModel { val text: String, override val onClickListenerLegacy: View.OnClickListener? = null, override val clickBehavior: ClickBehavior, + override val transitionManager: TransitionManager? = null, override val isHidden: Boolean = false, override val shouldAnimate: Boolean = true, ) : @@ -203,6 +235,7 @@ sealed class OngoingActivityChipModel { colors, onClickListenerLegacy, clickBehavior, + transitionManager, isHidden, shouldAnimate, ) { @@ -256,4 +289,17 @@ sealed class OngoingActivityChipModel { /** Clicking the chip will show the heads up notification associated with the chip. */ data class ShowHeadsUpNotification(val onClick: () -> Unit) : ClickBehavior } + + /** Defines the behavior of the chip with respect to activity launch and return transitions. */ + data class TransitionManager( + /** The factory used to create the controllers that animate the chip. */ + val controllerFactory: ComposableControllerFactory? = null, + /** + * Used to create a registration for this chip using [controllerFactory]. Must be + * idempotent. + */ + val registerTransition: () -> Unit = {}, + /** Used to remove the existing registration for this chip, if any. */ + val unregisterTransition: () -> Unit = {}, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt index 62789782d0a9..0fc7f82f785a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt @@ -15,7 +15,7 @@ */ package com.android.systemui.statusbar.chips.ui.viewmodel -import android.os.SystemClock +import android.annotation.ElapsedRealtimeLong import android.text.format.DateUtils.formatElapsedTime import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -27,6 +27,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle +import kotlin.math.absoluteValue import kotlinx.coroutines.delay /** Platform-optimized interface for getting current time */ @@ -34,18 +35,50 @@ fun interface TimeSource { fun getCurrentTime(): Long } -/** Holds and manages the state for a Chronometer */ -class ChronometerState(private val timeSource: TimeSource, private val startTimeMillis: Long) { - private var currentTimeMillis by mutableLongStateOf(0L) +/** + * Holds and manages the state for a Chronometer, which shows a timer in a format like "MM:SS" or + * "H:MM:SS". + * + * If [isEventInFuture] is false, then this Chronometer is counting up from an event that started in + * the past, like a phone call that was answered. [eventTimeMillis] represents the time the event + * started and the timer will tick up: 04:00, 04:01, ... No timer is shown if [eventTimeMillis] is + * in the future and [isEventInFuture] is false. + * + * If [isEventInFuture] is true, then this Chronometer is counting down to an event that will occur + * in the future, like a future meeting. [eventTimeMillis] represents the time the event will occur + * and the timer will tick down: 04:00, 03:59, ... No timer is shown if [eventTimeMillis] is in the + * past and [isEventInFuture] is true. + */ +class ChronometerState( + private val timeSource: TimeSource, + @ElapsedRealtimeLong private val eventTimeMillis: Long, + private val isEventInFuture: Boolean, +) { + private var currentTimeMillis by mutableLongStateOf(timeSource.getCurrentTime()) private val elapsedTimeMillis: Long - get() = maxOf(0L, currentTimeMillis - startTimeMillis) + get() = + if (isEventInFuture) { + eventTimeMillis - currentTimeMillis + } else { + currentTimeMillis - eventTimeMillis + } - val currentTimeText: String by derivedStateOf { formatElapsedTime(elapsedTimeMillis / 1000) } + /** + * The current timer string in a format like "MM:SS" or "H:MM:SS", or null if we shouldn't show + * the timer string. + */ + val currentTimeText: String? by derivedStateOf { + if (elapsedTimeMillis < 0) { + null + } else { + formatElapsedTime(elapsedTimeMillis / 1000) + } + } suspend fun run() { while (true) { currentTimeMillis = timeSource.getCurrentTime() - val delaySkewMillis = (currentTimeMillis - startTimeMillis) % 1000L + val delaySkewMillis = (eventTimeMillis - currentTimeMillis).absoluteValue % 1000L delay(1000L - delaySkewMillis) } } @@ -54,13 +87,16 @@ class ChronometerState(private val timeSource: TimeSource, private val startTime /** Remember and manage the ChronometerState */ @Composable fun rememberChronometerState( - startTimeMillis: Long, - timeSource: TimeSource = remember { TimeSource { SystemClock.elapsedRealtime() } }, + eventTimeMillis: Long, + isCountDown: Boolean, + timeSource: TimeSource, ): ChronometerState { val state = - remember(timeSource, startTimeMillis) { ChronometerState(timeSource, startTimeMillis) } + remember(timeSource, eventTimeMillis, isCountDown) { + ChronometerState(timeSource, eventTimeMillis, isCountDown) + } val lifecycleOwner = LocalLifecycleOwner.current - LaunchedEffect(lifecycleOwner, timeSource, startTimeMillis) { + LaunchedEffect(lifecycleOwner, timeSource, eventTimeMillis) { lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { state.run() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index 8be9e410f8f6..fcdcc3f698de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -105,7 +105,7 @@ public final class NotificationClicker implements View.OnClickListener { mBubblesOptional.get().collapseStack(); } } else { - if (!row.getEntry().isBubble() && mBubblesOptional.isPresent()) { + if (!row.getEntryLegacy().isBubble() && mBubblesOptional.isPresent()) { mBubblesOptional.get().collapseStack(); } } @@ -130,7 +130,7 @@ public final class NotificationClicker implements View.OnClickListener { } else { row.setBubbleClickListener(v -> mNotificationActivityStarter.onNotificationBubbleIconClicked( - row.getEntry())); + row.getEntryLegacy())); } row.setOnClickListener(this); row.setOnDragSuccessListener(mOnDragSuccessListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt index 874a059d2323..8163128f762a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt @@ -74,7 +74,6 @@ class NotificationTransitionAnimatorController( const val ANIMATION_DURATION_TOP_ROUNDING = 100L } - private val notificationEntry = notification.entry private val notificationKey = notification.key override val isLaunching: Boolean = true @@ -160,7 +159,7 @@ class NotificationTransitionAnimatorController( private val headsUpNotificationRow: ExpandableNotificationRow? get() { val pipelineParent = if (NotificationBundleUi.isEnabled) - notification.entryAdapter?.parent else notificationEntry.parent + notification.entryAdapter?.parent else notification.entryLegacy.parent val summaryEntry = (pipelineParent as? GroupEntry)?.summary return when { headsUpManager.isHeadsUpEntry(notificationKey) -> notification 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 26c302bf6409..b1a26af336d8 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 @@ -113,6 +113,10 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { return false } + override fun isPromotedOngoing(): Boolean { + return false + } + override fun isFullScreenCapable(): 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 3118ce56ac69..4299825bd5e3 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 @@ -132,6 +132,11 @@ public interface EntryAdapter { boolean isAmbient(); + /** + * Returns whether this row represents promoted ongoing notification. + */ + boolean isPromotedOngoing(); + default boolean isFullScreenCapable() { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index caa7abb1aa7a..8f7f61f6be65 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -34,7 +34,7 @@ public abstract class ListEntry extends PipelineEntry { } /** - * The SystemClock.uptimeMillis() when this object was created. In general, this means the + * The SystemClock.elapsedRealtime() when this object was created. In general, this means the * moment when NotificationManager notifies our listener about the existence of this entry. * * This value will not change if the notification is updated, although it will change if the @@ -65,13 +65,4 @@ public abstract class ListEntry extends PipelineEntry { @Nullable public PipelineEntry getPreviousParent() { return mPreviousAttachState.getParent(); } - - /** - * Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a - * fresh attach state (all entries will be null/default-initialized). - */ - void beginNewAttachState() { - mPreviousAttachState.clone(mAttachState); - mAttachState.reset(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 9795edf3313c..b7fe39e9c757 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -522,7 +522,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { } private void onNotificationsInitialized() { - mInitializedTimestamp = mClock.uptimeMillis(); + mInitializedTimestamp = UseElapsedRealtimeForCreationTime.getCurrentTime(mClock); } private void postNotification( @@ -532,7 +532,8 @@ public class NotifCollection implements Dumpable, PipelineDumpable { if (entry == null) { // A new notification! - entry = new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); + entry = new NotificationEntry(sbn, ranking, + UseElapsedRealtimeForCreationTime.getCurrentTime(mClock)); mEventQueue.add(new InitEntryEvent(entry)); mEventQueue.add(new BindEntryEvent(entry, sbn)); mNotificationSet.put(sbn.getKey(), entry); @@ -861,7 +862,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { // messages from system server. private void crashIfNotInitializing(RuntimeException exception) { final boolean isRecentlyInitialized = mInitializedTimestamp == 0 - || mClock.uptimeMillis() - mInitializedTimestamp + || UseElapsedRealtimeForCreationTime.getCurrentTime(mClock) - mInitializedTimestamp < INITIALIZATION_FORGIVENESS_WINDOW; if (isRecentlyInitialized) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt index 1f8d365cfdad..698fed33a408 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt @@ -89,9 +89,9 @@ class NotifCollectionCache<V>( return true } - // Using uptimeMillis since it's guaranteed to be monotonic, as we don't want a + // Using elapsedRealtime since it's guaranteed to be monotonic, as we don't want a // timezone/clock change to break us - val now = systemClock.uptimeMillis() + val now = UseElapsedRealtimeForCreationTime.getCurrentTime(systemClock) // Cannot purge the same entry from two threads simultaneously synchronized(key) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index d031d831bf5a..4558017a98c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -251,7 +251,7 @@ public final class NotificationEntry extends ListEntry { /** * @param sbn the StatusBarNotification from system server * @param ranking also from system server - * @param creationTime SystemClock.uptimeMillis of when we were created + * @param creationTime SystemClock.elapsedRealtime of when we were created */ public NotificationEntry( @NonNull StatusBarNotification sbn, @@ -508,7 +508,7 @@ public final class NotificationEntry extends ListEntry { ArrayList<NotificationEntry> children = new ArrayList<>(); for (ExpandableNotificationRow child : rowChildren) { - children.add(child.getEntry()); + children.add(child.getEntryLegacy()); } return children; 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 1168c647c26a..345b6aae9673 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 @@ -137,6 +137,10 @@ class NotificationEntryAdapter( return entry.ranking.isAmbient } + override fun isPromotedOngoing(): Boolean { + return entry.isPromotedOngoing + } + override fun isFullScreenCapable(): Boolean { return entry.sbn.notification.fullScreenIntent != null } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java index 872cd68e1b21..e9c4efc4de64 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java @@ -101,4 +101,13 @@ public abstract class PipelineEntry { public void setBucket(@PriorityBucket int bucket) { mBucket = bucket; } + + /** + * Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a + * fresh attach state (all entries will be null/default-initialized). + */ + void beginNewAttachState() { + mPreviousAttachState.clone(mAttachState); + mAttachState.reset(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 238ba8d9f490..5cea82140692 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -289,6 +289,9 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { mIdToBundleEntry.clear(); for (String id: mNotifBundler.getBundleIds()) { + if (BundleCoordinator.debugBundleUi) { + Log.i(TAG, "create BundleEntry with id: " + id); + } mIdToBundleEntry.put(id, new BundleEntry(id)); } } @@ -562,6 +565,11 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { entry.beginNewAttachState(); } + for (BundleEntry be : mIdToBundleEntry.values()) { + be.beginNewAttachState(); + // TODO(b/399736937) Clear bundle children + // BundleEntry has not representative summary so we do not need to clear it here. + } mNotifList.clear(); } @@ -570,7 +578,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { List<PipelineEntry> out, List<NotifFilter> filters) { Trace.beginSection("ShadeListBuilder.filterNotifs"); - final long now = mSystemClock.uptimeMillis(); + final long now = UseElapsedRealtimeForCreationTime.getCurrentTime(mSystemClock); for (PipelineEntry entry : entries) { if (entry instanceof GroupEntry) { final GroupEntry groupEntry = (GroupEntry) entry; @@ -614,7 +622,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { GroupEntry group = mGroups.get(topLevelKey); if (group == null) { - group = new GroupEntry(topLevelKey, mSystemClock.uptimeMillis()); + group = new GroupEntry(topLevelKey, + UseElapsedRealtimeForCreationTime.getCurrentTime(mSystemClock)); mGroups.put(topLevelKey, group); } if (group.getParent() == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/UseElapsedRealtimeForCreationTime.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/UseElapsedRealtimeForCreationTime.kt new file mode 100644 index 000000000000..23f90f3694a8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/UseElapsedRealtimeForCreationTime.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection + +import android.app.Flags +import com.android.systemui.util.time.SystemClock + +/** A helper class for replacing uptimeMillis with elapsedRealtime for entry creation times */ +public object UseElapsedRealtimeForCreationTime { + @JvmStatic + fun getCurrentTime(clock: SystemClock): Long { + if (Flags.notifEntryCreationTimeUseElapsedRealtime()) { + return clock.elapsedRealtime() + } + return clock.uptimeMillis() + } + + @JvmStatic + fun getCurrentTime(): Long { + if (Flags.notifEntryCreationTimeUseElapsedRealtime()) { + return android.os.SystemClock.elapsedRealtime() + } + return android.os.SystemClock.uptimeMillis() + + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java index 2eec68b26347..fb7772e26240 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java @@ -25,7 +25,7 @@ import java.util.List; * Represents a set of notification post events for a particular notification group. */ public class EventBatch { - /** SystemClock.uptimeMillis() */ + /** SystemClock.elapsedRealtime() */ final long mCreatedTimestamp; /** SBN.getGroupKey -- same for all members */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java index 96b35428b3ce..944e313d795a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java @@ -34,6 +34,7 @@ import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; import com.android.systemui.statusbar.notification.collection.PipelineDumpable; import com.android.systemui.statusbar.notification.collection.PipelineDumper; +import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.time.SystemClock; @@ -182,11 +183,12 @@ public class GroupCoalescer implements Dumpable, PipelineDumpable { private void maybeEmitBatch(StatusBarNotification sbn) { final CoalescedEvent event = mCoalescedEvents.get(sbn.getKey()); final EventBatch batch = mBatches.get(sbn.getGroupKey()); + long now = UseElapsedRealtimeForCreationTime.getCurrentTime(mClock); if (event != null) { mLogger.logEarlyEmit(sbn.getKey(), requireNonNull(event.getBatch()).mGroupKey); emitBatch(requireNonNull(event.getBatch())); } else if (batch != null - && mClock.uptimeMillis() - batch.mCreatedTimestamp >= mMaxGroupLingerDuration) { + && now - batch.mCreatedTimestamp >= mMaxGroupLingerDuration) { mLogger.logMaxBatchTimeout(sbn.getKey(), batch.mGroupKey); emitBatch(batch); } @@ -228,7 +230,8 @@ public class GroupCoalescer implements Dumpable, PipelineDumpable { private EventBatch getOrBuildBatch(final String groupKey) { EventBatch batch = mBatches.get(groupKey); if (batch == null) { - batch = new EventBatch(mClock.uptimeMillis(), groupKey); + batch = new EventBatch(UseElapsedRealtimeForCreationTime.getCurrentTime(mClock), + groupKey); mBatches.put(groupKey, batch); } return batch; @@ -268,7 +271,8 @@ public class GroupCoalescer implements Dumpable, PipelineDumpable { } events.sort(mEventComparator); - long batchAge = mClock.uptimeMillis() - batch.mCreatedTimestamp; + long batchAge = UseElapsedRealtimeForCreationTime.getCurrentTime(mClock) + - batch.mCreatedTimestamp; mLogger.logEmitBatch(batch.mGroupKey, batch.mMembers.size(), batchAge); mHandler.onNotificationBatchPosted(events); @@ -298,7 +302,7 @@ public class GroupCoalescer implements Dumpable, PipelineDumpable { @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - long now = mClock.uptimeMillis(); + long now = UseElapsedRealtimeForCreationTime.getCurrentTime(mClock); int eventCount = 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt index 8833ff1ce20c..4478d0e97920 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt @@ -98,9 +98,14 @@ class BundleCoordinator @Inject constructor( object : NotifBundler("NotifBundler") { // Use list instead of set to keep fixed order - override val bundleIds: List<String> = SYSTEM_RESERVED_IDS + override val bundleIds: List<String> = + if (debugBundleUi) SYSTEM_RESERVED_IDS + "notify" + else SYSTEM_RESERVED_IDS override fun getBundleIdOrNull(entry: NotificationEntry?): String? { + if (debugBundleUi && entry?.key?.contains("notify") == true) { + return "notify" + } return entry?.representativeEntry?.channel?.id?.takeIf { it in this.bundleIds } } } @@ -110,4 +115,9 @@ class BundleCoordinator @Inject constructor( pipeline.setNotifBundler(bundler) } } + + companion object { + @kotlin.jvm.JvmField + var debugBundleUi: Boolean = true + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index b54f21b23bba..1be415d7bf47 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -241,8 +241,7 @@ public class PreparationCoordinator implements Coordinator { isMemberOfDelayedGroup = shouldWaitForGroupToInflate(parent, now); mIsDelayedGroupCache.put(parent, isMemberOfDelayedGroup); } - - return !isInflated(entry) || isMemberOfDelayedGroup; + return !isInflated(entry) || (isMemberOfDelayedGroup != null && isMemberOfDelayedGroup); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt index a0a86710b4ba..f43767d3effb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt @@ -22,7 +22,6 @@ import com.android.internal.widget.MessagingGroup import com.android.internal.widget.MessagingMessage import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.systemui.Flags import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener @@ -147,9 +146,7 @@ internal constructor( traceSection("updateNotifOnUiModeChanged") { mPipeline?.allNotifs?.forEach { entry -> entry.row?.onUiModeChanged() - if (Flags.notificationUndoGutsOnConfigChanged()) { - mGutsManager.closeAndUndoGuts() - } + mGutsManager.closeAndUndoGuts() } } } @@ -158,16 +155,7 @@ internal constructor( colorUpdateLogger.logEvent("VCC.updateNotificationsOnDensityOrFontScaleChanged()") mPipeline?.allNotifs?.forEach { entry -> entry.onDensityOrFontScaleChanged() - if (Flags.notificationUndoGutsOnConfigChanged()) { - mGutsManager.closeAndUndoGuts() - } else { - // This property actually gets reset when the guts are re-inflated, so we're never - // actually calling onDensityOrFontScaleChanged below. - val exposedGuts = entry.areGutsExposed() - if (exposedGuts) { - mGutsManager.onDensityOrFontScaleChanged(entry) - } - } + mGutsManager.closeAndUndoGuts() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index bdbdc53c4b1c..0466c0359710 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -39,9 +39,9 @@ import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; import com.android.systemui.statusbar.notification.collection.GroupEntry; -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.listbuilder.OnBeforeRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; @@ -113,6 +113,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { @VisibleForTesting protected static final long ALLOW_SECTION_CHANGE_TIMEOUT = 500; + private final boolean mCheckLockScreenTransitionEnabled = Flags.checkLockscreenGoneTransition(); + @Inject public VisualStabilityCoordinator( @Background DelayableExecutor delayableExecutor, @@ -182,7 +184,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { this::onTrackingHeadsUpModeChanged); } - if (Flags.checkLockscreenGoneTransition()) { + if (mCheckLockScreenTransitionEnabled) { if (SceneContainerFlag.isEnabled()) { mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition( Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone), null), @@ -437,7 +439,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { boolean wasReorderingAllowed = mReorderingAllowed; // No need to run notification pipeline when the lockscreen is in fading animation. mPipelineRunAllowed = !(isPanelCollapsingOrLaunchingActivity() - || (Flags.checkLockscreenGoneTransition() && mLockscreenInGoneTransition)); + || (mCheckLockScreenTransitionEnabled && mLockscreenInGoneTransition)); mReorderingAllowed = isReorderingAllowed(); if (wasPipelineRunAllowed != mPipelineRunAllowed || wasReorderingAllowed != mReorderingAllowed) { @@ -499,7 +501,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { * notification and we are reordering based on the user's change. * * @param entry notification entry that can change sections even if isReorderingAllowed is false - * @param now current time SystemClock.uptimeMillis + * @param now current time SystemClock.elapsedRealtime */ public void temporarilyAllowSectionChanges(@NonNull NotificationEntry entry, long now) { final String entryKey = entry.getKey(); @@ -566,7 +568,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { pw.println("pipelineRunAllowed: " + mPipelineRunAllowed); pw.println(" notifPanelCollapsing: " + mNotifPanelCollapsing); pw.println(" launchingNotifActivity: " + mNotifPanelLaunchingActivity); - if (Flags.checkLockscreenGoneTransition()) { + if (mCheckLockScreenTransitionEnabled) { pw.println(" lockscreenInGoneTransition: " + mLockscreenInGoneTransition); } pw.println("reorderingAllowed: " + mReorderingAllowed); @@ -627,7 +629,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { } private void onLockscreenInGoneTransitionChanged(boolean inGoneTransition) { - if (!Flags.checkLockscreenGoneTransition()) { + if (!mCheckLockScreenTransitionEnabled) { return; } if (inGoneTransition == mLockscreenInGoneTransition) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java index 07fa6aeb7900..03b4076ba6fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java @@ -20,7 +20,6 @@ import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_N import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; -import android.os.SystemClock; import android.service.notification.NotificationStats; import androidx.annotation.NonNull; @@ -30,6 +29,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime; import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; @@ -85,7 +85,7 @@ public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback public void onImportanceChanged(NotificationEntry entry) { mVisualStabilityCoordinator.temporarilyAllowSectionChanges( entry, - SystemClock.uptimeMillis()); + UseElapsedRealtimeForCreationTime.getCurrentTime()); } @NonNull diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java index 776c7d5eb7f6..389bb3129c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java @@ -41,8 +41,8 @@ public abstract class NotifFilter extends Pluggable<NotifFilter> { * this entry will not have any grouping nor sorting information. * If this filter is registered via {@link NotifPipeline#addFinalizeFilter}, * this entry will have grouping and sorting information. - * @param now A timestamp in SystemClock.uptimeMillis that represents "now" for the purposes of - * pipeline execution. This value will be the same for all pluggable calls made + * @param now A timestamp in SystemClock.elapsedRealtime that represents "now" for the purposes + * of pipeline execution. This value will be the same for all pluggable calls made * during this pipeline run, giving pluggables a stable concept of "now" to compare * various entries against. * @return True if the notif should be removed from the list diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt index 8021d8f58ccc..a552ca554fd5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt @@ -234,21 +234,21 @@ private class ShadeNode(val controller: NodeController) { fun getChildCount(): Int = controller.getChildCount() fun addChildAt(child: ShadeNode, index: Int) { - traceSection("ShadeNode#addChildAt") { + traceSection({ "ShadeNode#${controller::class.simpleName}#addChildAt" }) { controller.addChildAt(child.controller, index) child.controller.onViewAdded() } } fun moveChildTo(child: ShadeNode, index: Int) { - traceSection("ShadeNode#moveChildTo") { + traceSection({ "ShadeNode#${controller::class.simpleName}#moveChildTo" }) { controller.moveChildTo(child.controller, index) child.controller.onViewMoved() } } fun removeChild(child: ShadeNode, isTransfer: Boolean) { - traceSection("ShadeNode#removeChild") { + traceSection({ "ShadeNode#${controller::class.simpleName}#removeChild" }) { controller.removeChild(child.controller, isTransfer) child.controller.onViewRemoved() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt index cdbe0fd23a9a..8d1e61123fdd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt @@ -81,7 +81,7 @@ constructor( if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { flowOf(emptySet()) } else { - activeHeadsUpRows.map { it.map { (repo, _) -> repo }.toSet() } + activeHeadsUpRows.map { it.map { (repo, _) -> repo }.toSet() }.distinctUntilChanged() } } @@ -90,9 +90,9 @@ constructor( if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { flowOf(emptySet()) } else { - activeHeadsUpRows.map { - it.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet() - } + activeHeadsUpRows + .map { it.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet() } + .distinctUntilChanged() // TODO(b/402428276) stop sending duplicate updates instead } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index ec8fbc08de7a..5bdd769dfa03 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -19,7 +19,6 @@ import android.content.Context; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemClock; import android.service.notification.NotificationListenerService; import android.util.ArrayMap; import android.util.ArraySet; @@ -44,6 +43,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.UpdateSource; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; @@ -112,7 +112,7 @@ public class NotificationLogger implements StateListener, CoreStartable, @Override public void run() { - mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis(); + mLastVisibilityReportUptimeMs = UseElapsedRealtimeForCreationTime.getCurrentTime(); // 1. Loop over active entries: // A. Keep list of visible notifications. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt index 2a01a14f56aa..777392df67cc 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 @@ -19,19 +19,26 @@ package com.android.systemui.statusbar.notification.promoted import android.app.Flags import android.app.Flags.notificationsRedesignTemplates import android.app.Notification +import android.content.Context import android.graphics.PorterDuff import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.View.GONE +import android.view.View.MeasureSpec.AT_MOST +import android.view.View.MeasureSpec.EXACTLY +import android.view.View.MeasureSpec.UNSPECIFIED +import android.view.View.MeasureSpec.makeMeasureSpec import android.view.View.VISIBLE import android.view.ViewGroup.MarginLayoutParams import android.view.ViewStub import android.widget.Chronometer import android.widget.DateTimeView +import android.widget.FrameLayout import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView +import androidx.annotation.DimenRes import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box @@ -42,7 +49,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.isVisible @@ -88,22 +97,12 @@ fun AODPromotedNotification( } key(content.identity) { - val sidePaddings = dimensionResource(systemuiR.dimen.notification_side_paddings) - val sidePaddingValues = PaddingValues(horizontal = sidePaddings, vertical = 0.dp) - - val borderStroke = BorderStroke(1.dp, SecondaryText.brush) - - val borderRadius = dimensionResource(systemuiR.dimen.notification_corner_radius) - val borderShape = RoundedCornerShape(borderRadius) - - Box(modifier = modifier.padding(sidePaddingValues)) { - AODPromotedNotificationView( - layoutResource = layoutResource, - content = content, - audiblyAlertedIconVisible = audiblyAlertedIconVisible, - modifier = Modifier.border(borderStroke, borderShape), - ) - } + AODPromotedNotificationView( + layoutResource = layoutResource, + content = content, + audiblyAlertedIconVisible = audiblyAlertedIconVisible, + modifier = modifier, + ) } } @@ -114,27 +113,91 @@ fun AODPromotedNotificationView( audiblyAlertedIconVisible: Boolean, modifier: Modifier = Modifier, ) { - AndroidView( - factory = { context -> - val view = - traceSection("$TAG.inflate") { - LayoutInflater.from(context).inflate(layoutResource, /* root= */ null) - } - - val updater = - traceSection("$TAG.findViews") { AODPromotedNotificationViewUpdater(view) } - - view.setTag(viewUpdaterTagId, updater) - - view - }, - update = { view -> - val updater = view.getTag(viewUpdaterTagId) as AODPromotedNotificationViewUpdater - - traceSection("$TAG.update") { updater.update(content, audiblyAlertedIconVisible) } - }, - modifier = modifier, - ) + val sidePaddings = dimensionResource(systemuiR.dimen.notification_side_paddings) + val sidePaddingValues = PaddingValues(horizontal = sidePaddings, vertical = 0.dp) + + val boxModifier = modifier.padding(sidePaddingValues) + + val borderStroke = BorderStroke(1.dp, SecondaryText.brush) + + val borderRadius = dimensionResource(systemuiR.dimen.notification_corner_radius) + val borderShape = RoundedCornerShape(borderRadius) + + val maxHeight = + with(LocalDensity.current) { + scaledFontHeight(systemuiR.dimen.notification_max_height_for_promoted_ongoing) + .toPx() + } + .toInt() + + val viewModifier = Modifier.border(borderStroke, borderShape) + + Box(modifier = boxModifier) { + AndroidView( + factory = { context -> + val notif = + traceSection("$TAG.inflate") { + LayoutInflater.from(context).inflate(layoutResource, /* root= */ null) + } + val updater = + traceSection("$TAG.findViews") { AODPromotedNotificationViewUpdater(notif) } + + val frame = FrameLayoutWithMaxHeight(maxHeight, context) + frame.addView(notif) + frame.setTag(viewUpdaterTagId, updater) + + frame + }, + update = { frame -> + val updater = frame.getTag(viewUpdaterTagId) as AODPromotedNotificationViewUpdater + + traceSection("$TAG.update") { updater.update(content, audiblyAlertedIconVisible) } + frame.maxHeight = maxHeight + }, + modifier = viewModifier, + ) + } +} + +private class FrameLayoutWithMaxHeight(maxHeight: Int, context: Context) : FrameLayout(context) { + var maxHeight = maxHeight + set(value) { + if (field != value) { + field = value + requestLayout() + } + } + + // This mirrors the logic in NotificationContentView.onMeasure. + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + if (childCount < 1) { + return + } + + val child = getChildAt(0) + val childLayoutHeight = child.layoutParams.height + val childHeightSpec = + if (childLayoutHeight >= 0) { + makeMeasureSpec(maxHeight.coerceAtMost(childLayoutHeight), EXACTLY) + } else { + makeMeasureSpec(maxHeight, AT_MOST) + } + measureChildWithMargins(child, widthMeasureSpec, 0, childHeightSpec, 0) + val childMeasuredHeight = child.measuredHeight + + val ownHeightMode = MeasureSpec.getMode(heightMeasureSpec) + val ownHeightSize = MeasureSpec.getSize(heightMeasureSpec) + + val ownMeasuredWidth = MeasureSpec.getSize(widthMeasureSpec) + val ownMeasuredHeight = + if (ownHeightMode != UNSPECIFIED) { + childMeasuredHeight.coerceAtMost(ownHeightSize) + } else { + childMeasuredHeight + } + + setMeasuredDimension(ownMeasuredWidth, ownMeasuredHeight) + } } private val PromotedNotificationContentModel.layoutResource: Int? @@ -521,6 +584,11 @@ private enum class AodPromotedNotificationColor(val colorInt: Int) { val brush = SolidColor(androidx.compose.ui.graphics.Color(colorInt)) } +@Composable +private fun scaledFontHeight(@DimenRes dimenId: Int): Dp { + return dimensionResource(dimenId) * LocalDensity.current.fontScale.coerceAtLeast(1f) +} + private val viewUpdaterTagId = systemuiR.id.aod_promoted_notification_view_updater_tag private const val TAG = "AODPromotedNotification" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt index 2aafe8c81381..d35c3b617246 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt @@ -20,6 +20,7 @@ import android.app.Notification import android.app.Notification.BigPictureStyle import android.app.Notification.BigTextStyle import android.app.Notification.CallStyle +import android.app.Notification.EXTRA_BIG_TEXT import android.app.Notification.EXTRA_CHRONOMETER_COUNT_DOWN import android.app.Notification.EXTRA_PROGRESS import android.app.Notification.EXTRA_PROGRESS_INDETERMINATE @@ -27,8 +28,10 @@ import android.app.Notification.EXTRA_PROGRESS_MAX import android.app.Notification.EXTRA_SUB_TEXT import android.app.Notification.EXTRA_TEXT import android.app.Notification.EXTRA_TITLE +import android.app.Notification.EXTRA_TITLE_BIG import android.app.Notification.EXTRA_VERIFICATION_ICON import android.app.Notification.EXTRA_VERIFICATION_TEXT +import android.app.Notification.InboxStyle import android.app.Notification.ProgressStyle import android.content.Context import android.graphics.drawable.Icon @@ -105,8 +108,8 @@ constructor( contentBuilder.shortCriticalText = notification.shortCriticalText() contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs contentBuilder.profileBadgeResId = null // TODO - contentBuilder.title = notification.title() - contentBuilder.text = notification.text() + contentBuilder.title = notification.resolveTitle(recoveredBuilder.style) + contentBuilder.text = notification.resolveText(recoveredBuilder.style) contentBuilder.skeletonLargeIcon = notification.skeletonLargeIcon(imageModelProvider) contentBuilder.oldProgress = notification.oldProgress() @@ -127,8 +130,39 @@ constructor( private fun Notification.title(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE) + private fun Notification.bigTitle(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE_BIG) + + private fun Notification.Style.bigTitleOverridesTitle(): Boolean { + return when (this) { + is BigTextStyle, + is BigPictureStyle, + is InboxStyle -> true + else -> false + } + } + + private fun Notification.resolveTitle(style: Notification.Style?): CharSequence? { + return if (style?.bigTitleOverridesTitle() == true) { + bigTitle() + } else { + null + } ?: title() + } + private fun Notification.text(): CharSequence? = extras?.getCharSequence(EXTRA_TEXT) + private fun Notification.bigText(): CharSequence? = extras?.getCharSequence(EXTRA_BIG_TEXT) + + private fun Notification.Style.bigTextOverridesText(): Boolean = this is BigTextStyle + + private fun Notification.resolveText(style: Notification.Style?): CharSequence? { + return if (style?.bigTextOverridesText() == true) { + bigText() + } else { + null + } ?: text() + } + private fun Notification.subText(): String? = extras?.getString(EXTRA_SUB_TEXT) private fun Notification.shortCriticalText(): String? { @@ -233,13 +267,13 @@ constructor( private fun BigPictureStyle.extractContent( contentBuilder: PromotedNotificationContentModel.Builder ) { - // TODO? + // Big title is handled in resolveTitle, and big picture is unsupported. } private fun BigTextStyle.extractContent( contentBuilder: PromotedNotificationContentModel.Builder ) { - // TODO? + // Big title and big text are handled in resolveTitle and resolveText. } private fun CallStyle.extractContent( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractor.kt new file mode 100644 index 000000000000..14295357c54f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractor.kt @@ -0,0 +1,39 @@ +/* + * 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.promoted.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** A class which can receive both a demotion signal and a single handler of that signal */ +@SysUISingleton +class PackageDemotionInteractor @Inject constructor() { + private var demotionSignalHandler: ((packageName: String, uid: Int) -> Unit)? = null + + /** + * called after sending a the demotion signal to + * [android.app.INotificationManager.setCanBePromoted] + */ + fun onPackageDemoted(packageName: String, uid: Int) { + demotionSignalHandler?.invoke(packageName, uid) + } + + /** sets the [handler] that will be called when [onPackageDemoted] is called. */ + fun setOnPackageDemotionHandler(handler: (packageName: String, uid: Int) -> Unit) { + demotionSignalHandler = handler + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 689222608abe..e76867373139 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -40,6 +40,7 @@ import android.view.animation.Interpolator; import com.android.app.animation.Interpolators; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor.Configuration; +import com.android.systemui.Flags; import com.android.systemui.Gefingerpoken; import com.android.systemui.common.shared.colors.SurfaceEffectColors; import com.android.systemui.res.R; @@ -101,7 +102,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private ValueAnimator mBackgroundColorAnimator; private float mAppearAnimationFraction = -1.0f; private float mAppearAnimationTranslation; - private int mNormalColor; + protected int mNormalColor; + protected int mOpaqueColor; private boolean mIsBelowSpeedBump; private long mLastActionUpTime; @@ -130,17 +132,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView protected void updateColors() { if (notificationRowTransparency()) { - if (mIsBlurSupported) { - mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext()); - } else { - mNormalColor = mContext.getColor( - com.android.internal.R.color.materialColorSurfaceContainer); - } + mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext()); + mOpaqueColor = mContext.getColor( + com.android.internal.R.color.materialColorSurfaceContainer); } else { mNormalColor = mContext.getColor( com.android.internal.R.color.materialColorSurfaceContainerHigh); } - setBackgroundToNormalColor(); mTintedRippleColor = mContext.getColor( R.color.notification_ripple_tinted_color); mNormalRippleColor = mContext.getColor( @@ -151,12 +149,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mOverrideAmount = 0.0f; } - private void setBackgroundToNormalColor() { - if (mBackgroundNormal != null) { - mBackgroundNormal.setNormalColor(mNormalColor); - } - } - /** * Reload background colors from resources and invalidate views. */ @@ -186,7 +178,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundNormal = findViewById(R.id.backgroundNormal); mFakeShadow = findViewById(R.id.fake_shadow); mShadowHidden = mFakeShadow.getVisibility() != VISIBLE; - setBackgroundToNormalColor(); initBackground(); updateBackgroundTint(); updateOutlineAlpha(); @@ -352,7 +343,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } protected boolean usesTransparentBackground() { - return mIsBlurSupported && notificationRowTransparency(); + return false; } @Override @@ -709,7 +700,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (withTint && mBgTint != NO_COLOR) { return mBgTint; } else { - return mNormalColor; + if (Flags.notificationRowTransparency()) { + return usesTransparentBackground() ? mNormalColor : mOpaqueColor; + } else { + return mNormalColor; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 8da2f768bf71..2c3676ae16ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -82,6 +82,7 @@ import androidx.dynamicanimation.animation.SpringAnimation; import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -421,9 +422,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } onExpansionChanged(true /* userAction */, wasExpanded); } else { - final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry); - boolean nowExpanded = mGroupExpansionManager.toggleGroupExpansion(mEntry); - mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded); + final boolean wasExpanded = + mGroupExpansionManager.isGroupExpanded(getEntryLegacy()); + boolean nowExpanded = mGroupExpansionManager.toggleGroupExpansion(getEntryLegacy()); + mOnExpandClickListener.onExpandClicked(getEntryLegacy(), v, nowExpanded); if (shouldLogExpandClickMetric) { mMetricsLogger.action( MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER, nowExpanded); @@ -453,7 +455,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (NotificationBundleUi.isEnabled()) { mOnExpandClickListener.onExpandClicked(this, mEntryAdapter, nowExpanded); } else { - mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded); + mOnExpandClickListener.onExpandClicked(getEntryLegacy(), v, nowExpanded); } if (shouldLogExpandClickMetric) { mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_EXPANDER, nowExpanded); @@ -558,7 +560,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (NotificationBundleUi.isEnabled()) { return mKey; } else { - return mEntry.getKey(); + return getEntryLegacy().getKey(); } } @@ -661,14 +663,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ public boolean getIsNonblockable() { NotificationBundleUi.assertInLegacyMode(); - if (mEntry == null) { + if (getEntryLegacy() == null) { return true; } - return !mEntry.isBlockable(); + return !getEntryLegacy().isBlockable(); } private boolean isConversation() { - return mPeopleNotificationIdentifier.getPeopleNotificationType(mEntry) + return mPeopleNotificationIdentifier.getPeopleNotificationType(getEntry()) != PeopleNotificationIdentifier.TYPE_NON_PERSON; } @@ -679,7 +681,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView Trace.beginSection("ExpNotRow#onNotifUpdated (leaf)"); } for (NotificationContentView l : mLayouts) { - l.onNotificationUpdated(mEntry); + l.onNotificationUpdated(getEntry()); } mShowingPublicInitialized = false; if (mMenuRow != null) { @@ -746,7 +748,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ public void updateBubbleButton() { for (NotificationContentView l : mLayouts) { - l.updateBubbleButton(mEntry); + l.updateBubbleButton(getEntry()); } } @@ -777,7 +779,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mEntryAdapter.getContrastedColor(mContext, mIsMinimized && !isExpanded(), getBackgroundColorWithoutTint()); } else { - return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(), + return getEntryLegacy().getContrastedColor(mContext, mIsMinimized && !isExpanded(), getBackgroundColorWithoutTint()); } } @@ -885,7 +887,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (NotificationBundleUi.isEnabled()) { targetSdk = mEntryAdapter.getTargetSdk(); } else { - targetSdk = mEntry.targetSdk; + targetSdk = getEntryLegacy().targetSdk; } boolean beforeN = targetSdk < Build.VERSION_CODES.N; @@ -901,7 +903,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (NotificationBundleUi.isEnabled()) { summarization = mEntryAdapter.getSummarization(); } else { - summarization = mEntry.getRanking().getSummarization(); + summarization = getEntryLegacy().getRanking().getSummarization(); } if (customView && beforeS && !mIsSummaryWithChildren) { @@ -945,7 +947,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView layout.setHeights(smallHeight, headsUpHeight, maxExpandedHeight); } + /** + * Check {@link NotificationBundleUi#isEnabled()} + * and use {@link #getEntryAdapter()} when true + * and {@link #getEntryLegacy()} when false. + */ + @NonNull + @Deprecated + public NotificationEntry getEntryLegacy() { + NotificationBundleUi.assertInLegacyMode(); + return mEntry; + } + + /** + * Check {@link NotificationBundleUi#isEnabled()} + * and use {@link #getEntryAdapter()} when true + * and {@link #getEntryLegacy()} when false. + */ @NonNull + @Deprecated public NotificationEntry getEntry() { return mEntry; } @@ -979,7 +999,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } else if (isAboveShelf() != wasAboveShelf) { mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); } - updateColors(); + updateBackgroundTint(); } /** @@ -1481,8 +1501,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void setBubbleClickListener(@Nullable OnClickListener l) { mBubbleClickListener = l; // ensure listener is passed to the content views - mPrivateLayout.updateBubbleButton(mEntry); - mPublicLayout.updateBubbleButton(mEntry); + mPrivateLayout.updateBubbleButton(getEntry()); + mPublicLayout.updateBubbleButton(getEntry()); } /** @@ -1554,7 +1574,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return initializationTime != -1 && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY; } else { - return getEntry().hasFinishedInitialization(); + return getEntryLegacy().hasFinishedInitialization(); } } @@ -1633,14 +1653,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (NotificationBundleUi.isEnabled()) { mEntryAdapter.prepareForInflation(); } else { - mEntry.getSbn().clearPackageContext(); + getEntryLegacy().getSbn().clearPackageContext(); } // TODO: Move content inflation logic out of this call RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); params.setNeedsReinflation(true); var rebindEndCallback = mRebindingTracker.trackRebinding(NotificationBundleUi.isEnabled() - ? mEntryAdapter.getKey() : mEntry.getKey()); + ? mEntryAdapter.getKey() : getEntryLegacy().getKey()); mRowContentBindStage.requestRebind(mEntry, (e) -> rebindEndCallback.onFinished()); Trace.endSection(); } @@ -1678,21 +1698,36 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected void setBackgroundTintColor(int color) { - super.setBackgroundTintColor(color); - NotificationContentView view = getShowingLayout(); - if (view != null) { - view.setBackgroundTintColor(color); - } - if (notificationRowTransparency() && mBackgroundNormal != null) { - if (NotificationBundleUi.isEnabled() && mEntryAdapter != null) { - mBackgroundNormal.setBgIsColorized(mEntryAdapter.isColorized()); + if (notificationRowTransparency()) { + boolean isColorized = false; + if (NotificationBundleUi.isEnabled()) { + if (mEntryAdapter != null) { + isColorized = mEntryAdapter.isColorized(); + } } else { if (mEntry != null) { - mBackgroundNormal.setBgIsColorized( - mEntry.getSbn().getNotification().isColorized()); + isColorized = mEntry.getSbn().getNotification().isColorized(); + } + } + boolean isTransparent = usesTransparentBackground(); + if (isColorized) { + // For colorized notifications, use a color that matches the tint color at 90% alpha + // when the row is transparent. + color = ColorUtils.setAlphaComponent( + color, (int) (0xFF * (isTransparent ? 0.9f : 1))); + } else { + // For non-colorized notifications, use the semi-transparent normal color token + // when the row is transparent, and the opaque color token otherwise. + if (!isTransparent && mBgTint == NO_COLOR) { + color = mOpaqueColor; } } } + super.setBackgroundTintColor(color); + NotificationContentView view = getShowingLayout(); + if (view != null) { + view.setBackgroundTintColor(color); + } } public void closeRemoteInput() { @@ -2310,7 +2345,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @VisibleForTesting - protected void setEntry(NotificationEntry entry) { + @Deprecated + protected void setEntryLegacy(NotificationEntry entry) { + NotificationBundleUi.assertInLegacyMode(); mEntry = entry; } @@ -2403,7 +2440,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (NotificationBundleUi.isEnabled()) { return traceTag + "(" + getEntryAdapter().getStyle() + ")"; } else { - return traceTag + "(" + getEntry().getNotificationStyle() + ")"; + return traceTag + "(" + getEntryLegacy().getNotificationStyle() + ")"; } } @@ -2908,7 +2945,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (NotificationBundleUi.isEnabled()) { return getEntryAdapter().getIcons().getShelfIcon(); } else { - return mEntry.getIcons().getShelfIcon(); + return getEntryLegacy().getIcons().getShelfIcon(); } } @@ -3016,7 +3053,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mGroupExpansionManager.setGroupExpanded(mEntryAdapter, userExpanded); } } else { - mGroupExpansionManager.setGroupExpanded(mEntry, userExpanded); + mGroupExpansionManager.setGroupExpanded(getEntryLegacy(), userExpanded); } onExpansionChanged(true /* userAction */, wasExpanded); return; @@ -3113,7 +3150,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.setOnKeyguard(onKeyguard); } } - updateColors(); + updateBackgroundTint(); } } @@ -3159,7 +3196,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public boolean canShowHeadsUp() { boolean canEntryHun = NotificationBundleUi.isEnabled() ? mEntryAdapter.canPeek() - : mEntry.isStickyAndNotDemoted(); + : getEntryLegacy().isStickyAndNotDemoted(); if (mOnKeyguard && !isDozing() && !isBypassEnabled() && (!canEntryHun || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) { @@ -3181,13 +3218,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (NotificationBundleUi.isEnabled()) { return mGroupExpansionManager.isGroupExpanded(mEntryAdapter); } - return mGroupExpansionManager.isGroupExpanded(mEntry); + return mGroupExpansionManager.isGroupExpanded(getEntryLegacy()); } private boolean isGroupRoot() { return NotificationBundleUi.isEnabled() ? mGroupMembershipManager.isGroupRoot(mEntryAdapter) - : mGroupMembershipManager.isGroupSummary(mEntry); + : mGroupMembershipManager.isGroupSummary(getEntryLegacy()); } private void onAttachedChildrenCountChanged() { @@ -3209,7 +3246,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (NotificationBundleUi.isEnabled()) { mPublicLayout.setNotificationWhen(mEntryAdapter.getWhen()); } else { - mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen()); + mPublicLayout.setNotificationWhen( + getEntryLegacy().getSbn().getNotification().getWhen()); } } getShowingLayout().updateBackgroundColor(false /* animate */); @@ -3243,12 +3281,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return false; } - final NotificationEntry entry = mEntry; - if (entry == null) { - return false; + if (NotificationBundleUi.isEnabled()) { + final EntryAdapter entryAdapter = mEntryAdapter; + if (entryAdapter == null) { + return false; + } + return entryAdapter.isPromotedOngoing(); + } else { + final NotificationEntry entry = mEntry; + if (entry == null) { + return false; + } + return entry.isPromotedOngoing(); } - - return entry.isPromotedOngoing(); } private boolean isPromotedNotificationExpanded(boolean allowOnKeyguard) { @@ -3537,7 +3582,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mEntryAdapter.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); } else { - return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); + return getEntryLegacy().isClearable() + && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); } } @@ -3551,7 +3597,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (!NotificationBundleUi.isEnabled()) { // this is only called if row.getParent() instanceof NotificationStackScrollLayout, // so there is never a group to expand - mGroupExpansionManager.setGroupExpanded(mEntry, true); + mGroupExpansionManager.setGroupExpanded(getEntryLegacy(), true); } } notifyHeightChanged(/* needsAnimation= */ false); @@ -3784,7 +3830,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return true; } } else { - if (getEntry().getSbn().getNotification().isColorized()) { + if (getEntryLegacy().getSbn().getNotification().isColorized()) { return true; } } @@ -4255,7 +4301,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public boolean isMediaRow() { NotificationBundleUi.assertInLegacyMode(); - return mEntry.getSbn().getNotification().isMediaNotification(); + return getEntryLegacy().getSbn().getNotification().isMediaNotification(); } public void setAboveShelf(boolean aboveShelf) { @@ -4377,11 +4423,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void dump(PrintWriter pwOriginal, String[] args) { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); // Skip super call; dump viewState ourselves - if (NotificationBundleUi.isEnabled()) { - pw.println("Notification: " + mEntryAdapter.getKey()); - } else { - pw.println("Notification: " + mEntry.getKey()); - } + pw.println("Notification: " + getKey()); DumpUtilsKt.withIncreasedIndent(pw, () -> { pw.println(this); pw.print("visibility: " + getVisibility()); @@ -4618,7 +4660,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (NotificationBundleUi.isEnabled()) { mLaunchAnimationRunning = launchAnimationRunning; } else { - getEntry().setExpandAnimationRunning(launchAnimationRunning); + getEntryLegacy().setExpandAnimationRunning(launchAnimationRunning); } } @@ -4627,12 +4669,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (NotificationBundleUi.isEnabled()) { return mLaunchAnimationRunning; } else { - return getEntry().isExpandAnimationRunning(); + return getEntryLegacy().isExpandAnimationRunning(); } } @Override protected boolean usesTransparentBackground() { - return super.usesTransparentBackground() && !mIsHeadsUp && !mOnKeyguard; + // Row background should be opaque when it's displayed as a heads-up notification or + // displayed on keyguard. + // TODO(b/388891313): Account for isBlurSupported when it is initialized and updated + // correctly. + return notificationRowTransparency() && !mIsHeadsUp && !mOnKeyguard; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index ac55930f5c11..7c0ee6685e6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -139,7 +139,7 @@ public class ExpandableNotificationRowController implements NotifViewController } final int viewUserId = NotificationBundleUi.isEnabled() ? mView.getEntryAdapter().getSbn().getUserId() - : mView.getEntry().getSbn().getUserId(); + : mView.getEntryLegacy().getSbn().getUserId(); if (viewUserId == UserHandle.USER_ALL || viewUserId == userId) { mView.getPrivateLayout().setBubblesEnabledForUser( BUBBLES_SETTING_ENABLED_VALUE.equals(value)); @@ -395,7 +395,7 @@ public class ExpandableNotificationRowController implements NotifViewController mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener); } } else { - mView.getEntry().setInitializationTime(mClock.elapsedRealtime()); + mView.getEntryLegacy().setInitializationTime(mClock.elapsedRealtime()); mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener); } mPluginManager.addPluginListener(mView, @@ -429,7 +429,9 @@ public class ExpandableNotificationRowController implements NotifViewController @Override @NonNull public String getNodeLabel() { - return NotificationBundleUi.isEnabled() ? mView.getLoggingKey() : logKey(mView.getEntry()); + return NotificationBundleUi.isEnabled() + ? mView.getLoggingKey() + : logKey(mView.getEntryLegacy()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java index 9ae2eb1b9328..20b826a3ca92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java @@ -109,7 +109,7 @@ public class ExpandableNotificationRowDragController { StatusBarNotification sn = NotificationBundleUi.isEnabled() ? enr.getEntryAdapter().getSbn() - : enr.getEntry().getSbn(); + : enr.getEntryLegacy().getSbn(); Notification notification = sn.getNotification(); final PendingIntent contentIntent = notification.contentIntent != null ? notification.contentIntent diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt index d02f636728fc..fe3a856e711e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt @@ -18,7 +18,9 @@ package com.android.systemui.statusbar.notification.row import android.animation.ValueAnimator import android.content.Context +import android.content.res.ColorStateList import android.graphics.Canvas +import android.graphics.Color import android.graphics.ColorFilter import android.graphics.Paint import android.graphics.Path @@ -31,9 +33,27 @@ import android.graphics.RectF import android.graphics.Shader import com.android.systemui.res.R import com.android.wm.shell.shared.animation.Interpolators +import android.graphics.drawable.RippleDrawable +import androidx.core.content.ContextCompat class MagicActionBackgroundDrawable( context: Context, +) : RippleDrawable( + ContextCompat.getColorStateList( + context, + R.color.notification_ripple_untinted_color + ) ?: ColorStateList.valueOf(Color.TRANSPARENT), + createBaseDrawable(context), null +) { + companion object { + private fun createBaseDrawable(context: Context): Drawable { + return BaseBackgroundDrawable(context) + } + } +} + +class BaseBackgroundDrawable( + context: Context, ) : Drawable() { private val cornerRadius = context.resources.getDimension(R.dimen.magic_action_button_corner_radius) @@ -42,6 +62,14 @@ class MagicActionBackgroundDrawable( private val buttonShape = Path() // Color and style + private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + val bgColor = + context.getColor( + com.android.internal.R.color.materialColorSurfaceContainerHigh + ) + color = bgColor + style = Paint.Style.FILL + } private val outlinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { val outlineColor = context.getColor( @@ -91,6 +119,7 @@ class MagicActionBackgroundDrawable( canvas.save() // Draw background canvas.clipPath(buttonShape) + canvas.drawPath(buttonShape, bgPaint) // Apply gradient to outline canvas.drawPath(buttonShape, outlinePaint) updateGradient(boundsF) @@ -119,11 +148,13 @@ class MagicActionBackgroundDrawable( } override fun setAlpha(alpha: Int) { + bgPaint.alpha = alpha outlinePaint.alpha = alpha invalidateSelf() } override fun setColorFilter(colorFilter: ColorFilter?) { + bgPaint.colorFilter = colorFilter outlinePaint.colorFilter = colorFilter invalidateSelf() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index 4914e1073059..e4997e4f53ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -36,9 +36,9 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.graphics.ColorUtils; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.Dumpable; +import com.android.systemui.common.shared.colors.SurfaceEffectColors; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss; import com.android.systemui.util.DrawableDumpKt; @@ -52,7 +52,6 @@ import java.util.Arrays; public class NotificationBackgroundView extends View implements Dumpable, ExpandableNotificationRow.DismissButtonTargetVisibilityListener { - private static final int MAX_ALPHA = 0xFF; private final boolean mDontModifyCorners; private Drawable mBackground; private int mClipTopAmount; @@ -73,8 +72,6 @@ public class NotificationBackgroundView extends View implements Dumpable, private final ColorStateList mLightColoredStatefulColors; private final ColorStateList mDarkColoredStatefulColors; private int mNormalColor; - private boolean mBgIsColorized = false; - private boolean mForceOpaque = false; private final int convexR = 9; private final int concaveR = 22; @@ -88,13 +85,15 @@ public class NotificationBackgroundView extends View implements Dumpable, R.color.notification_state_color_light); mDarkColoredStatefulColors = getResources().getColorStateList( R.color.notification_state_color_dark); + if (notificationRowTransparency()) { + mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext()); + } else { + mNormalColor = mContext.getColor( + com.android.internal.R.color.materialColorSurfaceContainerHigh); + } mFocusOverlayStroke = getResources().getDimension(R.dimen.notification_focus_stroke_width); } - public void setNormalColor(int color) { - mNormalColor = color; - } - @Override public void onTargetVisibilityChanged(boolean targetVisible) { if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) { @@ -140,21 +139,6 @@ public class NotificationBackgroundView extends View implements Dumpable, } } - /** - * A way to tell whether the background has been colorized. - */ - public boolean isColorized() { - return mBgIsColorized; - } - - /** - * A way to inform this class whether the background has been colorized. - * We need to know this, in order to *not* override that color. - */ - public void setBgIsColorized(boolean b) { - mBgIsColorized = b; - } - private Path calculateDismissButtonCutoutPath(Rect backgroundBounds) { // TODO(b/365585705): Adapt to RTL after the UX design is finalized. @@ -311,28 +295,21 @@ public class NotificationBackgroundView extends View implements Dumpable, return ((LayerDrawable) mBackground).getDrawable(1); } - private void updateBaseLayerColor() { - // BG base layer being a drawable, there isn't a method like setColor() to color it. - // Instead, we set a color filter that essentially replaces every pixel of the drawable. - // For non-colorized notifications, this function specifies a new color token. - // For colorized notifications, this uses a color that matches the tint color at 90% alpha. - int color = isColorized() - ? ColorUtils.setAlphaComponent(mTintColor, (int) (MAX_ALPHA * 0.9f)) - : mNormalColor; - getBaseBackgroundLayer().setColorFilter( - new PorterDuffColorFilter( - color, - PorterDuff.Mode.SRC)); // SRC operator discards the drawable's color+alpha - } - public void setTint(int tintColor) { Drawable baseLayer = getBaseBackgroundLayer(); - baseLayer.mutate().setTintMode(PorterDuff.Mode.SRC_ATOP); - baseLayer.setTint(tintColor); - mTintColor = tintColor; if (notificationRowTransparency()) { - updateBaseLayerColor(); + // BG base layer being a drawable, there isn't a method like setColor() to color it. + // Instead, we set a color filter that essentially replaces every pixel of the drawable. + baseLayer.setColorFilter( + new PorterDuffColorFilter( + tintColor, + // SRC operator discards the drawable's color+alpha + PorterDuff.Mode.SRC)); + } else { + baseLayer.mutate().setTintMode(PorterDuff.Mode.SRC_ATOP); + baseLayer.setTint(tintColor); } + mTintColor = tintColor; setStatefulColors(); invalidate(); } 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 ff4b835eb3c0..d97e25fdfa22 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 @@ -473,7 +473,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder result.newPublicView = createSensitiveContentMessageNotification( NotificationBundleUi.isEnabled() ? row.getEntryAdapter().getSbn().getNotification() - : row.getEntry().getSbn().getNotification(), + : row.getEntryLegacy().getSbn().getNotification(), builder.getStyle(), systemUiContext, packageContext).createContentView(); } else { @@ -814,7 +814,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder existingWrapper.onReinflated(); } } catch (Exception e) { - handleInflationError(runningInflations, e, row, callback, logger, + handleInflationError(runningInflations, e, row, entry, callback, logger, "applying view synchronously"); // Add a running inflation to make sure we don't trigger callbacks. // Safe to do because only happens in tests. @@ -836,7 +836,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder String invalidReason = isValidView(v, entry, row.getResources()); if (invalidReason != null) { handleInflationError(runningInflations, new InflationException(invalidReason), - row, callback, logger, "applied invalid view"); + row, entry, callback, logger, "applied invalid view"); runningInflations.remove(inflationId); return; } @@ -873,7 +873,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder onViewApplied(newView); } catch (Exception anotherException) { runningInflations.remove(inflationId); - handleInflationError(runningInflations, e, row, + handleInflationError(runningInflations, e, row, entry, callback, logger, "applying view"); } } @@ -969,13 +969,14 @@ public class NotificationContentInflater implements NotificationRowContentBinder private static void handleInflationError( HashMap<Integer, CancellationSignal> runningInflations, Exception e, - ExpandableNotificationRow row, @Nullable InflationCallback callback, + ExpandableNotificationRow row, NotificationEntry entry, + @Nullable InflationCallback callback, NotificationRowContentBinderLogger logger, String logContext) { Assert.isMainThread(); logger.logAsyncTaskException(row.getLoggingKey(), logContext, e); runningInflations.values().forEach(CancellationSignal::cancel); if (callback != null) { - callback.handleInflationException(row.getEntry(), e); + callback.handleInflationException(entry, e); } } @@ -1443,7 +1444,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder + Integer.toHexString(sbn.getId()); Log.e(CentralSurfaces.TAG, "couldn't inflate view for notification " + ident, e); if (mCallback != null) { - mCallback.handleInflationException(mRow.getEntry(), + mCallback.handleInflationException(mEntry, new InflationException("Couldn't inflate contentViews" + e)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index cd6e4979ce88..26d318bea5cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -269,6 +269,7 @@ public class NotificationContentView extends FrameLayout implements Notification mNotificationMaxHeight = maxHeight; } + // This logic is mirrored in FrameLayoutWithMaxHeight.onMeasure in AODPromotedNotification.kt. @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightMode = MeasureSpec.getMode(heightMeasureSpec); @@ -600,7 +601,7 @@ public class NotificationContentView extends FrameLayout implements Notification if (NotificationBundleUi.isEnabled()) { return mContainingNotification.getEntryAdapter().getSbn(); } else { - return mContainingNotification.getEntry().getSbn(); + return mContainingNotification.getEntryLegacy().getSbn(); } } @@ -1247,7 +1248,7 @@ public class NotificationContentView extends FrameLayout implements Notification final boolean isSingleLineViewPresent = mSingleLineView != null; if (shouldShowSingleLineView && !isSingleLineViewPresent) { - Log.wtf(TAG, "calculateVisibleType: SingleLineView is not available!"); + Log.e(TAG, "calculateVisibleType: SingleLineView is not available!"); } final int collapsedVisualType = shouldShowSingleLineView && isSingleLineViewPresent @@ -1274,7 +1275,7 @@ public class NotificationContentView extends FrameLayout implements Notification final boolean shouldShowSingleLineView = mIsChildInGroup && !isGroupExpanded(); final boolean isSingleLinePresent = mSingleLineView != null; if (shouldShowSingleLineView && !isSingleLinePresent) { - Log.wtf(TAG, "getVisualTypeForHeight: singleLineView is not available."); + Log.e(TAG, "getVisualTypeForHeight: singleLineView is not available."); } if (!mUserExpanding && shouldShowSingleLineView && isSingleLinePresent) { @@ -1592,10 +1593,13 @@ public class NotificationContentView extends FrameLayout implements Notification return; } ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button); - View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container); - ViewGroup actionListMarginTarget = layout.findViewById( - com.android.internal.R.id.notification_action_list_margin_target); - if (bubbleButton == null || actionContainer == null) { + // With the new design, the actions_container should always be visible to act as padding + // when there are no actions. We're making its child visible/invisible instead. + View actionsContainerForVisibilityChange = layout.findViewById( + notificationsRedesignTemplates() + ? com.android.internal.R.id.actions_container_layout + : com.android.internal.R.id.actions_container); + if (bubbleButton == null || actionsContainerForVisibilityChange == null) { return; } @@ -1613,17 +1617,14 @@ public class NotificationContentView extends FrameLayout implements Notification bubbleButton.setImageDrawable(d); bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener()); bubbleButton.setVisibility(VISIBLE); - actionContainer.setVisibility(VISIBLE); - // Set notification_action_list_margin_target's bottom margin to 0 when showing bubble - if (actionListMarginTarget != null) { - removeBottomMargin(actionListMarginTarget); - } - if (notificationsRedesignTemplates()) { - // Similar treatment for smart reply margin - LinearLayout smartReplyContainer = layout.findViewById( - com.android.internal.R.id.smart_reply_container); - if (smartReplyContainer != null) { - removeBottomMargin(smartReplyContainer); + actionsContainerForVisibilityChange.setVisibility(VISIBLE); + if (!notificationsRedesignTemplates()) { + // Set notification_action_list_margin_target's bottom margin to 0 when showing + // bubble + ViewGroup actionListMarginTarget = layout.findViewById( + com.android.internal.R.id.notification_action_list_margin_target); + if (actionListMarginTarget != null) { + removeBottomMargin(actionListMarginTarget); } } } else { @@ -1664,8 +1665,13 @@ public class NotificationContentView extends FrameLayout implements Notification return; } ImageView snoozeButton = layout.findViewById(com.android.internal.R.id.snooze_button); - View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container); - if (snoozeButton == null || actionContainer == null) { + // With the new design, the actions_container should always be visible to act as padding + // when there are no actions. We're making its child visible/invisible instead. + View actionsContainerForVisibilityChange = layout.findViewById( + notificationsRedesignTemplates() + ? com.android.internal.R.id.actions_container_layout + : com.android.internal.R.id.actions_container); + if (snoozeButton == null || actionsContainerForVisibilityChange == null) { return; } // Notification.Builder can 'disable' the snooze button to prevent it from being shown here @@ -1691,7 +1697,7 @@ public class NotificationContentView extends FrameLayout implements Notification snoozeButton.setOnClickListener( mContainingNotification.getSnoozeClickListener(snoozeMenuItem)); snoozeButton.setVisibility(VISIBLE); - actionContainer.setVisibility(VISIBLE); + actionsContainerForVisibilityChange.setVisibility(VISIBLE); } private void applySmartReplyView() { 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 9a75253295d5..cdb78d99538b 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 @@ -47,7 +47,6 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.internal.statusbar.IStatusBarService; import com.android.settingslib.notification.ConversationIconFactory; import com.android.systemui.CoreStartable; -import com.android.systemui.Flags; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -71,6 +70,7 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener; import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; +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; @@ -100,6 +100,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta private final AccessibilityManager mAccessibilityManager; private final HighPriorityProvider mHighPriorityProvider; private final ChannelEditorDialogController mChannelEditorDialogController; + private final PackageDemotionInteractor mPackageDemotionInteractor; private final OnUserInteractionCallback mOnUserInteractionCallback; // Dependencies: @@ -155,6 +156,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta LauncherApps launcherApps, ShortcutManager shortcutManager, ChannelEditorDialogController channelEditorDialogController, + PackageDemotionInteractor packageDemotionInteractor, UserContextProvider contextTracker, AssistantFeedbackController assistantFeedbackController, Optional<BubblesManager> bubblesManagerOptional, @@ -184,6 +186,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta mShortcutManager = shortcutManager; mContextTracker = contextTracker; mChannelEditorDialogController = channelEditorDialogController; + mPackageDemotionInteractor = packageDemotionInteractor; mAssistantFeedbackController = assistantFeedbackController; mBubblesManagerOptional = bubblesManagerOptional; mUiEventLogger = uiEventLogger; @@ -231,15 +234,6 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta } } - public void onDensityOrFontScaleChanged(NotificationEntry entry) { - if (!Flags.notificationUndoGutsOnConfigChanged()) { - Log.wtf(TAG, "onDensityOrFontScaleChanged should not be called if" - + " notificationUndoGutsOnConfigChanged is off"); - } - setExposedGuts(entry.getGuts()); - bindGuts(entry.getRow()); - } - /** * Sends an intent to open the notification settings for a particular package and optional * channel. @@ -291,11 +285,6 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row); } - private boolean bindGuts(final ExpandableNotificationRow row) { - row.ensureGutsInflated(); - return bindGuts(row, mGutsMenuItem); - } - @VisibleForTesting protected boolean bindGuts(final ExpandableNotificationRow row, NotificationMenuRowPlugin.MenuItem item) { @@ -429,6 +418,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta mIconStyleProvider, mOnUserInteractionCallback, mChannelEditorDialogController, + mPackageDemotionInteractor, packageName, row.getEntry().getChannel(), row.getEntry(), @@ -440,6 +430,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta NotificationBundleUi.isEnabled() ? !row.getEntry().isBlockable() : row.getIsNonblockable(), + row.canViewBeDismissed(), mHighPriorityProvider.isHighPriority(row.getEntry()), mAssistantFeedbackController, mMetricsLogger, @@ -605,6 +596,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta return mNotificationGutsExposed; } + @VisibleForTesting public void setExposedGuts(NotificationGuts guts) { mNotificationGutsExposed = guts; } 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 661122510c6c..b6f4ffce8e00 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 @@ -74,6 +74,7 @@ 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.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; @@ -120,6 +121,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private boolean mIsAutomaticChosen; private boolean mIsSingleDefaultChannel; private boolean mIsNonblockable; + private boolean mIsDismissable; private NotificationEntry mEntry; private StatusBarNotification mSbn; private boolean mIsDeviceProvisioned; @@ -160,6 +162,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mPressedApply = true; mGutsContainer.closeControls(v, /* save= */ true); }; + private OnClickListener mOnCloseClickListener; public NotificationInfo(Context context, AttributeSet attrs) { super(context, attrs); @@ -193,6 +196,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G NotificationIconStyleProvider iconStyleProvider, OnUserInteractionCallback onUserInteractionCallback, ChannelEditorDialogController channelEditorDialogController, + PackageDemotionInteractor packageDemotionInteractor, String pkg, NotificationChannel notificationChannel, NotificationEntry entry, @@ -202,6 +206,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G UiEventLogger uiEventLogger, boolean isDeviceProvisioned, boolean isNonblockable, + boolean isDismissable, boolean wasShownHighPriority, AssistantFeedbackController assistantFeedbackController, MetricsLogger metricsLogger, @@ -226,11 +231,13 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mStartingChannelImportance = mSingleNotificationChannel.getImportance(); mWasShownHighPriority = wasShownHighPriority; mIsNonblockable = isNonblockable; + mIsDismissable = isDismissable; mAppUid = mSbn.getUid(); mDelegatePkg = mSbn.getOpPkg(); mIsDeviceProvisioned = isDeviceProvisioned; mShowAutomaticSetting = mAssistantFeedbackController.isFeedbackEnabled(); mUiEventLogger = uiEventLogger; + mOnCloseClickListener = onCloseClick; mIsSystemRegisteredCall = mSbn.getNotification().isStyle(Notification.CallStyle.class) && mINotificationManager.isInCall(mSbn.getPackageName(), mSbn.getUid()); @@ -277,6 +284,11 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonblockable ? VISIBLE : GONE); + View dismissButton = findViewById(R.id.inline_dismiss); + dismissButton.setOnClickListener(mOnCloseClickListener); + dismissButton.setVisibility(dismissButton.hasOnClickListeners() && mIsDismissable + ? VISIBLE : GONE); + View done = findViewById(R.id.done); done.setOnClickListener(mOnDismissSettings); done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate()); 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 2f94d3220dc8..ae52db88358a 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 @@ -541,7 +541,7 @@ constructor( val ident: String = (sbn.packageName + "/0x" + Integer.toHexString(sbn.id)) Log.e(TAG, "couldn't inflate view for notification $ident", e) callback?.handleInflationException( - if (NotificationBundleUi.isEnabled) entry else row.entry, + if (NotificationBundleUi.isEnabled) entry else row.entryLegacy, InflationException("Couldn't inflate contentViews$e"), ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java index 83897f5bc3a7..cec0ae696b26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java @@ -51,7 +51,6 @@ import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.Flags; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; import com.android.systemui.res.R; @@ -477,7 +476,7 @@ public class NotificationSnooze extends LinearLayout @Override public boolean handleCloseControls(boolean save, boolean force) { - if (Flags.notificationUndoGutsOnConfigChanged() && !save) { + if (!save) { // Undo changes and let the guts handle closing the view mSelectedOption = null; showSnoozeOptions(false); 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 6ff711deeb01..01ee788f7fd7 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 @@ -31,6 +31,7 @@ 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.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; @@ -42,6 +43,7 @@ import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyl public class PromotedNotificationInfo extends NotificationInfo { private static final String TAG = "PromotedNotifInfoGuts"; private INotificationManager mNotificationManager; + private PackageDemotionInteractor mPackageDemotionInteractor; private NotificationGuts mGutsContainer; public PromotedNotificationInfo(Context context, AttributeSet attrs) { @@ -56,6 +58,7 @@ public class PromotedNotificationInfo extends NotificationInfo { NotificationIconStyleProvider iconStyleProvider, OnUserInteractionCallback onUserInteractionCallback, ChannelEditorDialogController channelEditorDialogController, + PackageDemotionInteractor packageDemotionInteractor, String pkg, NotificationChannel notificationChannel, NotificationEntry entry, @@ -65,16 +68,19 @@ public class PromotedNotificationInfo extends NotificationInfo { UiEventLogger uiEventLogger, boolean isDeviceProvisioned, boolean isNonblockable, + boolean isDismissable, boolean wasShownHighPriority, AssistantFeedbackController assistantFeedbackController, MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException { super.bindNotification(pm, iNotificationManager, appIconProvider, iconStyleProvider, - onUserInteractionCallback, channelEditorDialogController, pkg, notificationChannel, + onUserInteractionCallback, channelEditorDialogController, packageDemotionInteractor, + pkg, notificationChannel, entry, onSettingsClick, onAppSettingsClick, feedbackClickListener, uiEventLogger, - isDeviceProvisioned, isNonblockable, wasShownHighPriority, + isDeviceProvisioned, isNonblockable, isDismissable, wasShownHighPriority, assistantFeedbackController, metricsLogger, onCloseClick); mNotificationManager = iNotificationManager; + mPackageDemotionInteractor = packageDemotionInteractor; bindDemote(entry.getSbn(), pkg); } @@ -94,8 +100,8 @@ public class PromotedNotificationInfo extends NotificationInfo { private OnClickListener getDemoteClickListener(StatusBarNotification sbn, String packageName) { return ((View v) -> { try { - // TODO(b/391661009): Signal AutomaticPromotionCoordinator here mNotificationManager.setCanBePromoted(packageName, sbn.getUid(), false, true); + mPackageDemotionInteractor.onPackageDemoted(packageName, sbn.getUid()); mGutsContainer.closeControls(v, true); } catch (RemoteException e) { Log.e(TAG, "Couldn't revoke live update permission", e); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt index 4082a5b35f1e..2c5b9f44bc58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt @@ -61,7 +61,7 @@ constructor( row: ExpandableNotificationRow, context: Context, ): NotificationIconProvider { - val sbn = if (NotificationBundleUi.isEnabled) row.entryAdapter?.sbn else row.entry.sbn + val sbn = if (NotificationBundleUi.isEnabled) row.entryAdapter?.sbn else row.entryLegacy.sbn if (sbn == null) { return object : NotificationIconProvider { override fun shouldShowAppIcon(): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java index f492b259e58d..e266dad63d80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java @@ -50,7 +50,7 @@ public class NotificationBigPictureTemplateViewWrapper extends NotificationTempl resolveViews(); updateImageTag(NotificationBundleUi.isEnabled() ? row.getEntryAdapter().getSbn() - : row.getEntry().getSbn()); + : row.getEntryLegacy().getSbn()); } private void resolveViews() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java index dec674c5a0f3..71bb9a2c9e72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java @@ -47,7 +47,7 @@ public class NotificationBigTextTemplateViewWrapper extends NotificationTemplate // the transformation types and we need to have our values set by then. resolveViews(NotificationBundleUi.isEnabled() ? row.getEntryAdapter().getSbn() - : row.getEntry().getSbn()); + : row.getEntryLegacy().getSbn()); super.onContentUpdated(row); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 585051ad26e1..e6dadcd7c8d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -225,7 +225,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple super.onContentUpdated(row); mIsLowPriority = NotificationBundleUi.isEnabled() ? row.getEntryAdapter().isAmbient() - : row.getEntry().isAmbient(); + : row.getEntryLegacy().isAmbient(); mTransformLowPriorityTitle = !row.isChildInGroup() && !row.isSummaryWithChildren(); ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews(); @@ -236,7 +236,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple updateCropToPaddingForImageViews(); Notification n = NotificationBundleUi.isEnabled() ? row.getEntryAdapter().getSbn().getNotification() - : row.getEntry().getSbn().getNotification(); + : row.getEntryLegacy().getSbn().getNotification(); mIcon.setTag(ImageTransformState.ICON_TAG, n.getSmallIcon()); // We need to reset all views that are no longer transforming in case a view was previously diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java index 99db1dba7e65..19321dcef5c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java @@ -327,7 +327,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp // the transformation types and we need to have our values set by then. resolveTemplateViews(NotificationBundleUi.isEnabled() ? row.getEntryAdapter().getSbn() - : row.getEntry().getSbn()); + : row.getEntryLegacy().getSbn()); super.onContentUpdated(row); // With the modern templates, a large icon visually overlaps the header, so we can't // hide the header, we must show it. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 64babb2449d7..35e286c18fd8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -83,7 +83,7 @@ public abstract class NotificationViewWrapper implements TransformableView { if (NotificationBundleUi.isEnabled() ? row.getEntryAdapter().getSbn().getNotification().isStyle( Notification.DecoratedCustomViewStyle.class) - : row.getEntry().getSbn().getNotification().isStyle( + : row.getEntryLegacy().getSbn().getNotification().isStyle( Notification.DecoratedCustomViewStyle.class)) { return new NotificationDecoratedCustomViewWrapper(ctx, v, row); } @@ -141,7 +141,7 @@ public abstract class NotificationViewWrapper implements TransformableView { // Apps targeting Q should fix their dark mode bugs. int targetSdk = NotificationBundleUi.isEnabled() ? mRow.getEntryAdapter().getTargetSdk() - : mRow.getEntry().targetSdk; + : mRow.getEntryLegacy().targetSdk; if (targetSdk >= Build.VERSION_CODES.Q) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt index 9bd5a5bd903f..5a23f7cc2861 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt @@ -181,7 +181,7 @@ constructor( it.setMagneticTranslation(targetTranslation) } } - playPullHaptics(mappedTranslation = swipedRowMultiplier * translation, canSwipedBeDismissed) + // TODO(b/399633875): Enable pull haptics after we have a clear and polished haptics design } private fun playPullHaptics(mappedTranslation: Float, canSwipedBeDismissed: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 315d37e55bc3..f9d8c8e74b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -444,7 +444,7 @@ public class NotificationChildrenContainer extends ViewGroup mIsConversation = isConversation; StatusBarNotification notification = NotificationBundleUi.isEnabled() ? mContainingNotification.getEntryAdapter().getSbn() - : mContainingNotification.getEntry().getSbn(); + : mContainingNotification.getEntryLegacy().getSbn(); if (notification == null) { return; } @@ -615,7 +615,7 @@ public class NotificationChildrenContainer extends ViewGroup RemoteViews header; StatusBarNotification notification = NotificationBundleUi.isEnabled() ? mContainingNotification.getEntryAdapter().getSbn() - : mContainingNotification.getEntry().getSbn(); + : mContainingNotification.getEntryLegacy().getSbn(); if (notification == null) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index 96f0e6f57958..b5562ae15ede 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -167,7 +167,7 @@ internal constructor( view === promoHeaderView -> BUCKET_PROMO view is ExpandableNotificationRow -> if (NotificationBundleUi.isEnabled) view.entryAdapter?.sectionBucket - else view.entry.bucket + else view.entryLegacy.bucket else -> null } 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 e6bb1b9f0273..9fea75048e3e 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 @@ -64,6 +64,7 @@ import android.util.AttributeSet; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; +import android.util.Pair; import android.view.DisplayCutout; import android.view.InputDevice; import android.view.LayoutInflater; @@ -140,6 +141,7 @@ import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScr import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.policy.ScrollAdapter; import com.android.systemui.statusbar.policy.SplitShadeStateController; +import com.android.systemui.statusbar.ui.SystemBarUtilsProxy; import com.android.systemui.util.Assert; import com.android.systemui.util.ColorUtilKt; import com.android.systemui.util.DumpUtilsKt; @@ -1037,10 +1039,10 @@ public class NotificationStackScrollLayout } int bucket = NotificationBundleUi.isEnabled() ? row.getEntryAdapter().getSectionBucket() - : row.getEntry().getBucket(); + : row.getEntryLegacy().getBucket(); boolean isAmbient = NotificationBundleUi.isEnabled() ? row.getEntryAdapter().isAmbient() - : row.getEntry().isAmbient(); + : row.getEntryLegacy().isAmbient(); currentIndex++; boolean beforeSpeedBump; if (mHighPriorityBeforeSpeedBump) { @@ -1845,7 +1847,7 @@ public class NotificationStackScrollLayout } else { if (row.isChildInGroup()) { final NotificationEntry groupSummary = - mGroupMembershipManager.getGroupSummary(row.getEntry()); + mGroupMembershipManager.getGroupSummary(row.getEntryLegacy()); if (groupSummary != null) { row = groupSummary.getRow(); } @@ -1998,16 +2000,16 @@ public class NotificationStackScrollLayout if ((bottom - top >= mMinInteractionHeight || !requireMinHeight) && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { if (slidingChild instanceof ExpandableNotificationRow row) { - NotificationEntry entry = row.getEntry(); boolean isEntrySummaryForTopHun; if (NotificationBundleUi.isEnabled()) { isEntrySummaryForTopHun = Objects.equals( ((ExpandableNotificationRow) slidingChild).getNotificationParent(), mTopHeadsUpRow); } else { + NotificationEntry entry = row.getEntryLegacy(); isEntrySummaryForTopHun = mTopHeadsUpRow != null && - mGroupMembershipManager.getGroupSummary(mTopHeadsUpRow.getEntry()) - == entry; + mGroupMembershipManager.getGroupSummary( + mTopHeadsUpRow.getEntryLegacy()) == entry; } if (!mIsExpanded && row.isHeadsUp() && row.isPinned() && mTopHeadsUpRow != row @@ -3007,7 +3009,7 @@ public class NotificationStackScrollLayout ExpandableNotificationRow childRow = (ExpandableNotificationRow) child; return NotificationBundleUi.isEnabled() ? mGroupMembershipManager.isChildInGroup(childRow.getEntryAdapter()) - : mGroupMembershipManager.isChildInGroup(childRow.getEntry()); + : mGroupMembershipManager.isChildInGroup(childRow.getEntryLegacy()); } return false; } @@ -3852,8 +3854,6 @@ public class NotificationStackScrollLayout // existing overScroll, we have to scroll the view customOverScrollBy((int) scrollAmount, getOwnScrollY(), range, getHeight() / 2); - // If we're scrolling, leavebehinds should be dismissed - mController.checkSnoozeLeavebehind(); } } break; @@ -6002,7 +6002,6 @@ public class NotificationStackScrollLayout * LockscreenShadeTransitionController resets fraction to 0 * where it remains until the next lockscreen-to-shade transition. */ - @Override public void setFractionToShade(float fraction) { mAmbientState.setFractionToShade(fraction); updateContentHeight(); // Recompute stack height with different section gap. @@ -6474,7 +6473,7 @@ public class NotificationStackScrollLayout @SelectedRows int selection) { int bucket = NotificationBundleUi.isEnabled() ? row.getEntryAdapter().getSectionBucket() - : row.getEntry().getBucket(); + : row.getEntryLegacy().getBucket(); switch (selection) { case ROWS_ALL: return true; 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 f7f8acf5fda9..f3d8ee245540 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 @@ -648,11 +648,11 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void onChildSnappedBack(View animView, float targetLeft) { mView.onSwipeEnd(); if (animView instanceof ExpandableNotificationRow row) { - if (row.isPinned() && !canChildBeDismissed(row) - && NotificationBundleUi.isEnabled() + boolean cannotFullScreen = NotificationBundleUi.isEnabled() ? !row.getEntryAdapter().isFullScreenCapable() - : (row.getEntry().getSbn().getNotification().fullScreenIntent - == null)) { + : (row.getEntryLegacy().getSbn().getNotification().fullScreenIntent + == null); + if (row.isPinned() && !canChildBeDismissed(row) && cannotFullScreen) { mHeadsUpManager.removeNotification( row.getKey(), /* removeImmediately= */ true, @@ -1735,7 +1735,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { * they remain until the next lockscreen-to-shade transition. */ public void setTransitionToFullShadeAmount(float fraction) { - SceneContainerFlag.assertInLegacyMode(); mView.setFractionToShade(fraction); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index fcb63df1a528..e5071d9c1e53 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -413,7 +413,7 @@ constructor( (currentNotification as? ExpandableNotificationRow)?.entryAdapter counter.incrementForBucket(entryAdapter?.sectionBucket) } else { - val entry = (currentNotification as? ExpandableNotificationRow)?.entry + val entry = (currentNotification as? ExpandableNotificationRow)?.entryLegacy counter.incrementForBucket(entry?.bucket) } } @@ -470,7 +470,7 @@ constructor( calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex) val canPeek = view is ExpandableNotificationRow && if (NotificationBundleUi.isEnabled) view.entryAdapter?.canPeek() == true - else view.entry.isStickyAndNotDemoted + else view.entryLegacy.isStickyAndNotDemoted var size = if (onLockscreen) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 28218227506c..da1442359071 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -927,7 +927,7 @@ public class StackScrollAlgorithm { childState.headsUpIsVisible, row.showingPulsing(), ambientState.isOnKeyguard(), NotificationBundleUi.isEnabled() ? row.getEntryAdapter().canPeek() - : row.getEntry().isStickyAndNotDemoted())) { + : row.getEntryLegacy().isStickyAndNotDemoted())) { // the height of this child before clamping it to the top float unmodifiedChildHeight = childState.height; clampHunToTop( @@ -984,7 +984,7 @@ public class StackScrollAlgorithm { childState.headsUpIsVisible, row.showingPulsing(), ambientState.isOnKeyguard(), NotificationBundleUi.isEnabled() ? row.getEntryAdapter().canPeek() - : row.getEntry().isStickyAndNotDemoted())) { + : row.getEntryLegacy().isStickyAndNotDemoted())) { // Ensure that the heads up is always visible even when scrolled off. // NSSL y starts at top of screen in non-split-shade, but below the qs // offset diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index ac89f3a63fcd..9c855e9cd9b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -107,9 +107,6 @@ interface NotificationScrollView { /** sets the current expand fraction */ fun setExpandFraction(expandFraction: Float) - /** Sets the fraction of the LockScreen -> Shade transition. */ - fun setFractionToShade(fraction: Float) - /** sets the current QS expand fraction */ fun setQsExpandFraction(expandFraction: Float) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index 40739b386d20..653344ae9203 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -95,11 +95,6 @@ constructor( view.setExpandFraction(it.coerceIn(0f, 1f)) } } - launch { - viewModel.lockScreenToShadeTransitionProgress.collectTraced { - view.setFractionToShade(it.coerceIn(0f, 1f)) - } - } launch { viewModel.qsExpandFraction.collectTraced { view.setQsExpandFraction(it) } } launch { viewModel.blurRadius(maxBlurRadius).collect(view::setBlurRadius) } launch { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index 940b2e541758..c1aa5f12aa99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -207,44 +207,6 @@ constructor( val qsExpandFraction: Flow<Float> = shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction") - /** - * Fraction of the LockScreen -> Shade transition. 0..1 while the transition in progress, and - * snaps back to 0 when it is Idle. - */ - val lockScreenToShadeTransitionProgress: Flow<Float> = - combine( - shadeInteractor.shadeExpansion, - shadeModeInteractor.shadeMode, - sceneInteractor.transitionState, - ) { shadeExpansion, _, transitionState -> - when (transitionState) { - is Idle -> 0f - is ChangeScene -> - if ( - transitionState.isTransitioning( - from = Scenes.Lockscreen, - to = Scenes.Shade, - ) - ) { - shadeExpansion - } else { - 0f - } - - is Transition.OverlayTransition -> - if ( - transitionState.currentScene == Scenes.Lockscreen && - transitionState.isTransitioning(to = Overlays.NotificationsShade) - ) { - shadeExpansion - } else { - 0f - } - } - } - .distinctUntilChanged() - .dumpWhileCollecting("lockScreenToShadeTransitionProgress") - val isOccluded: Flow<Boolean> = bouncerInteractor.bouncerExpansion .map { it == 1f } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt index bc533148f514..afe79718d526 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt @@ -60,7 +60,7 @@ constructor( if (animationsEnabled) { added.forEach { key -> val row = obtainView(key) - val hasStatusBarChip = statusBarChips.contains(row.entry.key) + val hasStatusBarChip = statusBarChips.contains(row.key) parentView.generateHeadsUpAnimation( row, /* isHeadsUp = */ true, @@ -69,7 +69,7 @@ constructor( } removed.forEach { key -> val row = obtainView(key) - val hasStatusBarChip = statusBarChips.contains(row.entry.key) + val hasStatusBarChip = statusBarChips.contains(row.key) if (!parentView.isBeingDragged()) { parentView.generateHeadsUpAnimation( row, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 74a42ef3ff7d..f3d72027238f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -29,6 +29,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.content.Context; import android.graphics.Color; import android.os.Handler; import android.util.Log; @@ -226,6 +227,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private ScrimState mState = ScrimState.UNINITIALIZED; + private Context mContext; + private ScrimView mScrimInFront; private ScrimView mNotificationsScrim; private ScrimView mScrimBehind; @@ -365,7 +368,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump @Main CoroutineDispatcher mainDispatcher, LargeScreenShadeInterpolator largeScreenShadeInterpolator, BlurConfig blurConfig, + @Main Context context, Lazy<WindowRootViewBlurInteractor> windowRootViewBlurInteractor) { + mContext = context; mScrimStateListener = lightBarController::setScrimState; mLargeScreenShadeInterpolator = largeScreenShadeInterpolator; mBlurConfig = blurConfig; @@ -1627,16 +1632,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private void updateThemeColors() { if (mScrimBehind == null) return; - int background = mScrimBehind.getContext().getColor( + int background = mContext.getColor( com.android.internal.R.color.materialColorSurfaceDim); - int accent = mScrimBehind.getContext().getColor( + int accent = mContext.getColor( com.android.internal.R.color.materialColorPrimary); mColors.setMainColor(background); mColors.setSecondaryColor(accent); final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background); mColors.setSupportsDarkText(isBackgroundLight); - int surface = mScrimBehind.getContext().getColor( + int surface = mContext.getColor( com.android.internal.R.color.materialColorSurface); for (ScrimState state : ScrimState.values()) { state.setSurfaceColor(surface); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java index 05a46cd9fa31..8389aab4aac8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java @@ -219,7 +219,7 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, if (NotificationBundleUi.isEnabled()) { mGroupExpansionManager.toggleGroupExpansion(row.getEntryAdapter()); } else { - mGroupExpansionManager.toggleGroupExpansion(row.getEntry()); + mGroupExpansionManager.toggleGroupExpansion(row.getEntryLegacy()); } } else if (!row.isChildInGroup()) { final boolean expandNotification; @@ -241,7 +241,7 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, if (NotificationBundleUi.isEnabled()) { mGroupExpansionManager.toggleGroupExpansion(row.getEntryAdapter()); } else { - mGroupExpansionManager.toggleGroupExpansion(row.getEntry()); + mGroupExpansionManager.toggleGroupExpansion(row.getEntryLegacy()); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt index c91ea9a50028..2f9cff46d687 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt @@ -196,9 +196,8 @@ fun StatusBarRoot( setContent { PlatformTheme { - val chipsVisibilityModel by + val chipsVisibilityModel = statusBarViewModel.ongoingActivityChips - .collectAsStateWithLifecycle() if (chipsVisibilityModel.areChipsAllowed) { OngoingActivityChips( chips = chipsVisibilityModel.chips, @@ -255,7 +254,7 @@ fun StatusBarRoot( expandedMatchesParentHeight = true showsOnlyActiveMedia = true falsingProtectionNeeded = false - disablePagination = true + disableScrolling = true init(MediaHierarchyManager.LOCATION_STATUS_BAR_POPUP) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt index c717b180575c..540babad5dd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt @@ -21,6 +21,8 @@ import android.graphics.Rect import android.view.Display import android.view.View import androidx.compose.runtime.getValue +import com.android.app.tracing.FlowTracing.traceEach +import com.android.app.tracing.TrackGroupUtils.trackGroup import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -130,7 +132,7 @@ interface HomeStatusBarViewModel : Activatable { val primaryOngoingActivityChip: StateFlow<OngoingActivityChipModel> /** All supported activity chips, whether they are currently active or not. */ - val ongoingActivityChips: StateFlow<ChipsVisibilityModel> + val ongoingActivityChips: ChipsVisibilityModel /** * The multiple ongoing activity chips that should be shown on the left-hand side of the status @@ -386,11 +388,9 @@ constructor( } override val isHomeStatusBarAllowed = - isHomeStatusBarAllowedCompat.stateIn( - bgScope, - SharingStarted.WhileSubscribed(), - initialValue = false, - ) + isHomeStatusBarAllowedCompat + .traceEach(trackGroup(TRACK_GROUP, "isHomeStatusBarAllowed"), logcat = true) + .stateIn(bgScope, SharingStarted.WhileSubscribed(), initialValue = false) private val shouldHomeStatusBarBeVisible = combine( @@ -461,24 +461,29 @@ constructor( isHomeStatusBarAllowed && !isSecureCameraActive && !hideStartSideContentForHeadsUp } - override val ongoingActivityChips = + private val chipsVisibilityModel: Flow<ChipsVisibilityModel> = combine(ongoingActivityChipsViewModel.chips, canShowOngoingActivityChips) { chips, canShow -> ChipsVisibilityModel(chips, areChipsAllowed = canShow) } - .stateIn( - bgScope, - SharingStarted.WhileSubscribed(), - initialValue = - ChipsVisibilityModel( - chips = MultipleOngoingActivityChipsModel(), - areChipsAllowed = false, - ), - ) + .traceEach(trackGroup(TRACK_GROUP, "chips"), logcat = true) { + "Chips[allowed=${it.areChipsAllowed} numChips=${it.chips.active.size}]" + } + + override val ongoingActivityChips: ChipsVisibilityModel by + hydrator.hydratedStateOf( + traceName = "ongoingActivityChips", + initialValue = + ChipsVisibilityModel( + chips = MultipleOngoingActivityChipsModel(), + areChipsAllowed = false, + ), + source = chipsVisibilityModel, + ) private val hasOngoingActivityChips = if (StatusBarChipsModernization.isEnabled) { - ongoingActivityChips.map { it.chips.active.any { chip -> !chip.isHidden } } + chipsVisibilityModel.map { it.chips.active.any { chip -> !chip.isHidden } } } else if (StatusBarNotifChips.isEnabled) { ongoingActivityChipsLegacy.map { it.primary is OngoingActivityChipModel.Active } } else { @@ -607,6 +612,8 @@ constructor( private const val COL_PREFIX_NOTIF_CONTAINER = "notifContainer" private const val COL_PREFIX_SYSTEM_INFO = "systemInfo" + private const val TRACK_GROUP = "StatusBar" + fun tableLogBufferName(displayId: Int) = "HomeStatusBarViewModel[$displayId]" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java index b13e01be40f7..fa022b4768fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java @@ -27,6 +27,7 @@ import android.util.IndentingPrintWriter; import androidx.annotation.NonNull; +import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager; import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; import com.android.systemui.Dumpable; import com.android.systemui.dagger.qualifiers.Main; @@ -56,8 +57,8 @@ public final class DeviceStateRotationLockSettingController private int mDeviceState = -1; @Nullable private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; - private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener - mDeviceStateRotationLockSettingsListener; + private DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener + mDeviceStateAutoRotateSettingListener; @Inject public DeviceStateRotationLockSettingController( @@ -83,17 +84,17 @@ public final class DeviceStateRotationLockSettingController // is no user action. mDeviceStateCallback = this::updateDeviceState; mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback); - mDeviceStateRotationLockSettingsListener = () -> + mDeviceStateAutoRotateSettingListener = () -> readPersistedSetting("deviceStateRotationLockChange", mDeviceState); mDeviceStateRotationLockSettingsManager.registerListener( - mDeviceStateRotationLockSettingsListener); + mDeviceStateAutoRotateSettingListener); } else { if (mDeviceStateCallback != null) { mDeviceStateManager.unregisterCallback(mDeviceStateCallback); } - if (mDeviceStateRotationLockSettingsListener != null) { + if (mDeviceStateAutoRotateSettingListener != null) { mDeviceStateRotationLockSettingsManager.unregisterListener( - mDeviceStateRotationLockSettingsListener); + mDeviceStateAutoRotateSettingListener); } } } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 9f60fe212567..bd3feadf4459 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -28,6 +28,7 @@ import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_DYNAMIC_COLOR; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_THEME_STYLE; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_BOTH; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_INDEX; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE; @@ -106,6 +107,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.stream.Collectors; @@ -507,18 +509,52 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor); mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); + + // Condition only applies when booting to Setup Wizard. + // We should check if the device has specific hardware color styles, load the relative + // color palette and more also save the setting in our shared setting with ThemePicker. WallpaperColors systemColor; if (hardwareColorStyles() && !mDeviceProvisionedController.isCurrentUserSetup()) { - Pair<Integer, Color> defaultSettings = getThemeSettingsDefaults(); - mThemeStyle = defaultSettings.first; - Color seedColor = defaultSettings.second; + HardwareDefaultSetting defaultSettings = getThemeSettingsDefaults(); + mThemeStyle = defaultSettings.style; // we only use the first color anyway, so we can pass only the single color we have systemColor = new WallpaperColors( - /*primaryColor*/ seedColor, - /*secondaryColor*/ seedColor, - /*tertiaryColor*/ seedColor + /*primaryColor*/ defaultSettings.seedColor, + /*secondaryColor*/ defaultSettings.seedColor, + /*tertiaryColor*/ defaultSettings.seedColor + ); + + /* We update the json in THEME_CUSTOMIZATION_OVERLAY_PACKAGES to reflect the preset. */ + final int currentUser = mUserTracker.getUserId(); + final String overlayPackageJson = Objects.requireNonNullElse( + mSecureSettings.getStringForUser( + Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + currentUser), + "{}" ); + + try { + JSONObject object = new JSONObject(overlayPackageJson); + String seedColorStr = Integer.toHexString(defaultSettings.seedColor.toArgb()); + object.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, seedColorStr); + object.put(OVERLAY_CATEGORY_ACCENT_COLOR, seedColorStr); + object.put(OVERLAY_COLOR_SOURCE, defaultSettings.colorSource); + object.put(OVERLAY_CATEGORY_THEME_STYLE, Style.toString(mThemeStyle)); + + mSecureSettings.putStringForUser( + Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, object.toString(), + UserHandle.USER_CURRENT); + + Log.d(TAG, "Hardware Color Defaults loaded: " + object.toString()); + + } catch (JSONException e) { + Log.d(TAG, "Failed to store hardware color defaults in " + + "THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e); + } + + // now we have to update + } else { systemColor = mWallpaperManager.getWallpaperColors( getDefaultWallpaperColorsSource(mUserTracker.getUserId())); @@ -863,7 +899,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { try { JSONObject object = new JSONObject(overlayPackageJson); style = Style.valueOf( - object.getString(ThemeOverlayApplier.OVERLAY_CATEGORY_THEME_STYLE)); + object.getString(OVERLAY_CATEGORY_THEME_STYLE)); if (!validStyles.contains(style)) { style = Style.TONAL_SPOT; } @@ -899,26 +935,29 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { } String deviceColorPropertyValue = mSystemPropertiesHelper.get(deviceColorProperty); - Pair<Integer, String> selectedTheme = themeMap.get(deviceColorPropertyValue); - if (selectedTheme == null) { + Pair<Integer, String> styleAndSource = themeMap.get(deviceColorPropertyValue); + if (styleAndSource == null) { Log.d(TAG, "Sysprop `" + deviceColorProperty + "` of value '" + deviceColorPropertyValue + "' not found in theming_defaults: " + Arrays.toString(themeData)); - selectedTheme = fallbackTheme; + styleAndSource = fallbackTheme; } - return selectedTheme; + return styleAndSource; + } + + record HardwareDefaultSetting(Color seedColor, @Style.Type int style, String colorSource) { } @VisibleForTesting - protected Pair<Integer, Color> getThemeSettingsDefaults() { + protected HardwareDefaultSetting getThemeSettingsDefaults() { - Pair<Integer, String> selectedTheme = getHardwareColorSetting(); + Pair<Integer, String> styleAndSource = getHardwareColorSetting(); // Last fallback color Color defaultSeedColor = Color.valueOf(GOOGLE_BLUE); // defaultColor will come from wallpaper or be parsed from a string - boolean isWallpaper = selectedTheme.second.equals(COLOR_SOURCE_HOME); + boolean isWallpaper = styleAndSource.second.equals(COLOR_SOURCE_HOME); if (isWallpaper) { WallpaperColors wallpaperColors = mWallpaperManager.getWallpaperColors( @@ -932,22 +971,24 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { defaultSeedColor.toArgb())); } else { try { - defaultSeedColor = Color.valueOf(Color.parseColor(selectedTheme.second)); + defaultSeedColor = Color.valueOf(Color.parseColor(styleAndSource.second)); Log.d(TAG, "Default seed color read from resource: " + Integer.toHexString( defaultSeedColor.toArgb())); } catch (IllegalArgumentException e) { - Log.e(TAG, "Error parsing color: " + selectedTheme.second, e); + Log.e(TAG, "Error parsing color: " + styleAndSource.second, e); // defaultSeedColor remains unchanged in this case } } - return new Pair<>(selectedTheme.first, defaultSeedColor); + return new HardwareDefaultSetting(defaultSeedColor, styleAndSource.first, + isWallpaper ? COLOR_SOURCE_HOME : COLOR_SOURCE_PRESET); } @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println("mSystemColors=" + mCurrentColors); pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor)); + pw.println("mContrast=" + mContrast); pw.println("mSecondaryOverlay=" + mSecondaryOverlay); pw.println("mNeutralOverlay=" + mNeutralOverlay); pw.println("mDynamicOverlay=" + mDynamicOverlay); diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt index 47e27bc59f96..1cc7a3185a5d 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt @@ -50,6 +50,7 @@ class DisplaySwitchLatencyLogger { onScreenTurningOnToOnDrawnMs, onDrawnToOnScreenTurnedOnMs, trackingResult, + screenWakelockstatus ) } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt index 66de52260b79..5800d5ed41c6 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt @@ -344,6 +344,8 @@ constructor( val onDrawnToOnScreenTurnedOnMs: Int = VALUE_UNKNOWN, val trackingResult: Int = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TRACKING_RESULT__UNKNOWN_RESULT, + val screenWakelockstatus: Int = + SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__SCREEN_WAKELOCK_STATUS__SCREEN_WAKELOCK_STATUS_UNKNOWN, ) enum class TrackingResult { diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt index 735da46667c5..cc4307a67268 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt @@ -19,8 +19,11 @@ package com.android.systemui.util.kotlin import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.util.time.SystemClock import com.android.systemui.util.time.SystemClockImpl +import java.util.LinkedList import java.util.concurrent.atomic.AtomicReference import kotlin.math.max +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -364,3 +367,58 @@ inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine( */ @Suppress("NOTHING_TO_INLINE") inline fun Flow<Unit>.emitOnStart(): Flow<Unit> = onStart { emit(Unit) } + +/** + * Transforms a Flow<T> into a Flow<List<T>> by implementing a sliding window algorithm. + * + * This function creates a sliding window over the input Flow<T>. The window has a specified + * [windowDuration] and slides continuously as time progresses. The emitted List<T> contains all + * items from the input flow that fall within the current window. + * + * The window slides forward by the smallest possible increment to include or exclude *one* event + * based on the time the event was emitted (determined by the System.currentTimeMillis()). This + * means that consecutive emitted lists will have overlapping elements if the elements fall within + * the [windowDuration] + * + * @param windowDuration The duration of the sliding window. + * @return A Flow that emits Lists of elements within the current sliding window. + */ +fun <T> Flow<T>.slidingWindow( + windowDuration: Duration, + clock: SystemClock = SystemClockImpl(), +): Flow<List<T>> = channelFlow { + require(windowDuration.isPositive()) { "Window duration must be positive" } + val buffer = LinkedList<Pair<Duration, T>>() + + coroutineScope { + var windowAdvancementJob: Job? = null + + collect { value -> + windowAdvancementJob?.cancel() + val now = clock.currentTimeMillis().milliseconds + buffer.addLast(now to value) + + while (buffer.isNotEmpty() && buffer.first.first + windowDuration <= now) { + buffer.removeFirst() + } + send(buffer.map { it.second }) + + // Keep the window advancing through time even if the source flow isn't emitting + // anymore. We stop advancing the window as soon as there are no items left in the + // buffer. + windowAdvancementJob = launch { + while (buffer.isNotEmpty()) { + val startOfWindow = clock.currentTimeMillis().milliseconds - windowDuration + // Invariant: At this point, everything in the buffer is guaranteed to be in + // the window, as we removed expired items above. + val timeUntilNextOldest = + (buffer.first.first - startOfWindow).coerceAtLeast(0.milliseconds) + delay(timeUntilNextOldest) + // Remove the oldest item, as it has now fallen out of the window. + buffer.removeFirst() + send(buffer.map { it.second }) + } + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index a42f5d3f67ea..9475bdbd043b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -50,7 +50,6 @@ import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; @@ -1878,7 +1877,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(callback, never()).onTrustGrantedForCurrentUser( anyBoolean() /* dismissKeyguard */, eq(true) /* newlyUnlocked */, - anyObject() /* flags */, + any() /* flags */, anyString() /* message */ ); } @@ -2747,7 +2746,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { () -> mJavaAdapter, () -> mSceneInteractor, () -> mCommunalSceneInteractor, - mKeyguardServiceShowLockscreenInteractor); + () -> mKeyguardServiceShowLockscreenInteractor); setAlternateBouncerVisibility(false); setPrimaryBouncerVisibility(false); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); diff --git a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt index 4f7610ab7d72..f98c1309b24f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt @@ -25,7 +25,6 @@ import com.android.systemui.dagger.GlobalRootComponent import com.android.systemui.dagger.SysUIComponent import com.android.systemui.dump.dumpManager import com.android.systemui.flags.systemPropertiesHelper -import com.android.systemui.kosmos.Kosmos import com.android.systemui.process.processWrapper import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -48,7 +47,7 @@ class SystemUIApplicationTest : SysuiTestCase() { @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) - val kosmos = Kosmos() + val kosmos = testKosmos() @Mock private lateinit var initializer: SystemUIInitializer @Mock private lateinit var rootComponent: GlobalRootComponent @Mock private lateinit var sysuiComponent: SysUIComponent @@ -56,9 +55,13 @@ class SystemUIApplicationTest : SysuiTestCase() { @Mock private lateinit var initController: InitController class StartableA : TestableStartable() + class StartableB : TestableStartable() + class StartableC : TestableStartable() + class StartableD : TestableStartable() + class StartableE : TestableStartable() val dependencyMap: Map<Class<*>, Set<Class<out CoreStartable>>> = @@ -114,7 +117,7 @@ class SystemUIApplicationTest : SysuiTestCase() { .thenReturn( mutableMapOf( StartableA::class.java to Provider { startableA }, - StartableB::class.java to Provider { startableB } + StartableB::class.java to Provider { startableB }, ) ) app.onCreate() @@ -130,7 +133,7 @@ class SystemUIApplicationTest : SysuiTestCase() { mutableMapOf( StartableC::class.java to Provider { startableC }, StartableA::class.java to Provider { startableA }, - StartableB::class.java to Provider { startableB } + StartableB::class.java to Provider { startableB }, ) ) app.onCreate() @@ -150,7 +153,7 @@ class SystemUIApplicationTest : SysuiTestCase() { StartableC::class.java to Provider { startableC }, StartableD::class.java to Provider { startableD }, StartableA::class.java to Provider { startableA }, - StartableB::class.java to Provider { startableB } + StartableB::class.java to Provider { startableB }, ) ) app.onCreate() diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt index 1268de0f4141..4cfe106780f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt @@ -34,9 +34,9 @@ import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.activity.EmptyTestActivity import com.android.systemui.concurrency.fakeExecutor -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.runOnMainThreadAndWaitForIdleSync +import com.android.systemui.testKosmos import kotlin.test.assertTrue import org.junit.Rule import org.junit.Test @@ -77,7 +77,7 @@ class TransitionAnimatorTest( } } - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val pathManager = GoldenPathManager( context, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 0e68fce679b0..2c70249bcb06 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -73,9 +73,9 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.display.data.repository.displayStateRepository import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.res.R +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.withArgCaptor import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat @@ -189,7 +189,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa private lateinit var promptContentViewWithMoreOptionsButton: PromptContentViewWithMoreOptionsButton - private val kosmos = Kosmos() + private val kosmos = testKosmos() @Before fun setup() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt index 9dab9d735603..b134dff89651 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt @@ -31,8 +31,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel import com.android.systemui.common.shared.model.asIcon import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory -import com.android.systemui.kosmos.Kosmos import com.android.systemui.motion.createSysUiComposeMotionTestRule +import com.android.systemui.testKosmos import com.android.systemui.utils.PolicyRestriction import kotlin.test.Test import kotlin.time.Duration.Companion.seconds @@ -61,7 +61,7 @@ import platform.test.screenshot.Displays.Phone class BrightnessSliderMotionTest : SysuiTestCase() { private val deviceSpec = DeviceEmulationSpec(Phone) - private val kosmos = Kosmos() + private val kosmos = testKosmos() @get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos, deviceSpec) diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index 5c893da45b8d..6a4f3da054e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -44,6 +44,7 @@ import android.app.RemoteAction; import android.content.ClipData; import android.content.ClipDescription; import android.content.Context; +import android.content.Intent; import android.graphics.Insets; import android.graphics.Rect; import android.net.Uri; @@ -77,6 +78,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.Optional; +import java.util.function.Consumer; @SmallTest @RunWith(AndroidJUnit4.class) @@ -158,6 +160,31 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { * is false are removed.[ */ private void initController() { + IntentCreator fakeIntentCreator = new IntentCreator() { + @Override + public Intent getTextEditorIntent(Context context) { + return new Intent(); + } + + @Override + public Intent getShareIntent(ClipData clipData, Context context) { + Intent intent = Intent.createChooser(new Intent(Intent.ACTION_SEND), null); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return intent; + } + + @Override + public void getImageEditIntentAsync(Uri uri, Context context, + Consumer<Intent> outputConsumer) { + outputConsumer.accept(new Intent(Intent.ACTION_EDIT)); + } + + @Override + public Intent getRemoteCopyIntent(ClipData clipData, Context context) { + return new Intent(); + } + }; + mOverlayController = new ClipboardOverlayController( mContext, mClipboardOverlayView, @@ -171,7 +198,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mClipboardTransitionExecutor, mClipboardIndicationProvider, mUiEventLogger, - new ActionIntentCreator()); + fakeIntentCreator); verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture()); mCallbacks = mOverlayCallbacksCaptor.getValue(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index fb70846049da..061f7984d44b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -134,7 +134,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private RingerModeTracker mRingerModeTracker; @Mock private RingerModeLiveData mRingerModeLiveData; @Mock private PackageManager mPackageManager; - @Mock private Handler mHandler; @Mock private UserContextProvider mUserContextProvider; @Mock private VibratorHelper mVibratorHelper; @Mock private ShadeController mShadeController; @@ -148,6 +147,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { private TestableLooper mTestableLooper; private KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); private GlobalActionsInteractor mInteractor; + private Handler mHandler; @Before public void setUp() throws Exception { @@ -166,6 +166,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mGlobalSettings = new FakeGlobalSettings(); mSecureSettings = new FakeSettings(); mInteractor = mKosmos.getGlobalActionsInteractor(); + mHandler = new Handler(mTestableLooper.getLooper()); mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext, mWindowManagerFuncs, @@ -771,6 +772,31 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mGlobalActionsDialogLite.showOrHideDialog(false, false, null, Display.DEFAULT_DISPLAY); } + @Test + public void userSwitching_dismissDialog() { + String[] actions = { + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mResources) + .getStringArray(com.android.internal.R.array.config_globalActionsList); + + mGlobalActionsDialogLite.showOrHideDialog(false, true, null, Display.DEFAULT_DISPLAY); + mTestableLooper.processAllMessages(); + + assertThat(mGlobalActionsDialogLite.mDialog.isShowing()).isTrue(); + + ArgumentCaptor<UserTracker.Callback> captor = + ArgumentCaptor.forClass(UserTracker.Callback.class); + + verify(mUserTracker).addCallback(captor.capture(), any()); + + captor.getValue().onBeforeUserSwitching(100); + mTestableLooper.processAllMessages(); + + assertThat(mGlobalActionsDialogLite.mDialog).isNull(); + } + private UserInfo mockCurrentUser(int flags) { return new UserInfo(10, "A User", flags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt index f394c805f5b7..8f1d07bac4df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt @@ -855,6 +855,20 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Test fun contentDescriptionUpdated() { + var elapsedTimeDesc: CharSequence? = null + var durationDesc: CharSequence? = null + val listener = + object : SeekBarViewModel.ContentDescriptionListener { + override fun onContentDescriptionChanged( + elapsedTimeDescription: CharSequence, + durationDescription: CharSequence, + ) { + elapsedTimeDesc = elapsedTimeDescription + durationDesc = durationDescription + } + } + viewModel.setContentDescriptionListener(listener) + // When there is a duration and position val duration = (1.5 * 60 * 60 * 1000).toLong() val metadata = @@ -875,9 +889,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { viewModel.updateController(mockController) fakeExecutor.runNextReady() - // Then the content description is set - val result = viewModel.progress.value!! - + // Then the content description listener gets an update val expectedProgress = MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE) .formatMeasures(Measure(3, MeasureUnit.SECOND)) @@ -888,7 +900,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { Measure(30, MeasureUnit.MINUTE), Measure(0, MeasureUnit.SECOND), ) - assertThat(result.durationDescription).isEqualTo(expectedDuration) - assertThat(result.elapsedTimeDescription).isEqualTo(expectedProgress) + assertThat(elapsedTimeDesc).isEqualTo(expectedProgress) + assertThat(durationDesc).isEqualTo(expectedDuration) } } 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 5c26dac5eb30..798aa428e73e 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 @@ -60,13 +60,13 @@ import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.service.notification.StatusBarNotification; import android.testing.TestableLooper; import android.text.TextUtils; import android.view.View; import androidx.core.graphics.drawable.IconCompat; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.media.flags.Flags; @@ -101,6 +101,9 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -108,7 +111,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class MediaSwitchingControllerTest extends SysuiTestCase { private static final String TEST_DEVICE_1_ID = "test_device_1_id"; @@ -201,6 +204,17 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { private MediaDescription mMediaDescription; private List<RoutingSessionInfo> mRoutingSessionInfos = new ArrayList<>(); + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf( + Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION, + Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING); + } + + public MediaSwitchingControllerTest(FlagsParameterization flags) { + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() { mPackageName = mContext.getPackageName(); @@ -260,7 +274,6 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { mMediaDevices.add(mMediaDevice1); mMediaDevices.add(mMediaDevice2); - when(mNearbyDevice1.getMediaRoute2Id()).thenReturn(TEST_DEVICE_1_ID); when(mNearbyDevice1.getRangeZone()).thenReturn(NearbyDevice.RANGE_FAR); when(mNearbyDevice2.getMediaRoute2Id()).thenReturn(TEST_DEVICE_2_ID); @@ -689,7 +702,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { mMediaSwitchingController.start(mCb); reset(mCb); - mMediaSwitchingController.getMediaItemList().clear(); + mMediaSwitchingController.clearMediaItemList(); mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); final List<MediaDevice> devices = new ArrayList<>(); int dividerSize = 0; @@ -1528,7 +1541,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { .getSelectedMediaDevice(); mMediaSwitchingController.start(mCb); reset(mCb); - mMediaSwitchingController.getMediaItemList().clear(); + mMediaSwitchingController.clearMediaItemList(); mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); @@ -1546,7 +1559,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { .getSelectedMediaDevice(); mMediaSwitchingController.start(mCb); reset(mCb); - mMediaSwitchingController.getMediaItemList().clear(); + mMediaSwitchingController.clearMediaItemList(); mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); @@ -1564,7 +1577,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { .getSelectedMediaDevice(); mMediaSwitchingController.start(mCb); reset(mCb); - mMediaSwitchingController.getMediaItemList().clear(); + mMediaSwitchingController.clearMediaItemList(); mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); @@ -1582,7 +1595,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { .getSelectedMediaDevice(); mMediaSwitchingController.start(mCb); reset(mCb); - mMediaSwitchingController.getMediaItemList().clear(); + mMediaSwitchingController.clearMediaItemList(); mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); mMediaDevices.clear(); mMediaDevices.add(mMediaDevice2); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java new file mode 100644 index 000000000000..f6edd49f142f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java @@ -0,0 +1,383 @@ +/* + * 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.media.dialog; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.media.flags.Flags; +import com.android.settingslib.media.MediaDevice; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +import java.util.List; +import java.util.stream.Collectors; + +@SmallTest +@RunWith(ParameterizedAndroidJunit4.class) +@TestableLooper.RunWithLooper +public class OutputMediaItemListProxyTest extends SysuiTestCase { + private static final String DEVICE_ID_1 = "device_id_1"; + private static final String DEVICE_ID_2 = "device_id_2"; + private static final String DEVICE_ID_3 = "device_id_3"; + private static final String DEVICE_ID_4 = "device_id_4"; + @Mock private MediaDevice mMediaDevice1; + @Mock private MediaDevice mMediaDevice2; + @Mock private MediaDevice mMediaDevice3; + @Mock private MediaDevice mMediaDevice4; + + private MediaItem mMediaItem1; + private MediaItem mMediaItem2; + private MediaItem mConnectNewDeviceMediaItem; + private OutputMediaItemListProxy mOutputMediaItemListProxy; + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf( + Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION, + Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING); + } + + public OutputMediaItemListProxyTest(FlagsParameterization flags) { + mSetFlagsRule.setFlagsParameterization(flags); + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mMediaDevice1.getId()).thenReturn(DEVICE_ID_1); + when(mMediaDevice2.getId()).thenReturn(DEVICE_ID_2); + when(mMediaDevice2.isSuggestedDevice()).thenReturn(true); + when(mMediaDevice3.getId()).thenReturn(DEVICE_ID_3); + when(mMediaDevice4.getId()).thenReturn(DEVICE_ID_4); + mMediaItem1 = MediaItem.createDeviceMediaItem(mMediaDevice1); + mMediaItem2 = MediaItem.createDeviceMediaItem(mMediaDevice2); + mConnectNewDeviceMediaItem = MediaItem.createPairNewDeviceMediaItem(); + + mOutputMediaItemListProxy = new OutputMediaItemListProxy(mContext); + } + + @EnableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION) + @Test + public void updateMediaDevices_shouldUpdateMediaItemList() { + assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue(); + + // Create the initial output media item list with mMediaDevice2 and mMediaDevice3. + mOutputMediaItemListProxy.updateMediaDevices( + /* devices= */ List.of(mMediaDevice2, mMediaDevice3), + /* selectedDevices */ List.of(mMediaDevice3), + /* connectedMediaDevice= */ null, + /* needToHandleMutingExpectedDevice= */ false, + /* connectNewDeviceMediaItem= */ null); + + // Check the output media items to be + // * a media item with the selected mMediaDevice3 + // * a group divider for suggested devices + // * a media item with the mMediaDevice2 + assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) + .containsExactly(mMediaDevice3, null, mMediaDevice2); + assertThat(mOutputMediaItemListProxy.getOutputMediaItemList().get(0).isFirstDeviceInGroup()) + .isEqualTo(Flags.enableOutputSwitcherDeviceGrouping()); + + // Update the output media item list with more media devices. + mOutputMediaItemListProxy.updateMediaDevices( + /* devices= */ List.of(mMediaDevice4, mMediaDevice1, mMediaDevice3, mMediaDevice2), + /* selectedDevices */ List.of(mMediaDevice3), + /* connectedMediaDevice= */ null, + /* needToHandleMutingExpectedDevice= */ false, + /* connectNewDeviceMediaItem= */ null); + + // Check the output media items to be + // * a media item with the selected route mMediaDevice3 + // * a group divider for suggested devices + // * a media item with the route mMediaDevice2 + // * a group divider for speakers and displays + // * a media item with the route mMediaDevice4 + // * a media item with the route mMediaDevice1 + assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) + .containsExactly( + mMediaDevice3, null, mMediaDevice2, null, mMediaDevice4, mMediaDevice1); + assertThat(mOutputMediaItemListProxy.getOutputMediaItemList().get(0).isFirstDeviceInGroup()) + .isEqualTo(Flags.enableOutputSwitcherDeviceGrouping()); + + // Update the output media item list where mMediaDevice4 is offline and new selected device. + mOutputMediaItemListProxy.updateMediaDevices( + /* devices= */ List.of(mMediaDevice1, mMediaDevice3, mMediaDevice2), + /* selectedDevices */ List.of(mMediaDevice1, mMediaDevice3), + /* connectedMediaDevice= */ null, + /* needToHandleMutingExpectedDevice= */ false, + /* connectNewDeviceMediaItem= */ null); + + // Check the output media items to be + // * a media item with the selected route mMediaDevice3 + // * a group divider for suggested devices + // * a media item with the route mMediaDevice2 + // * a group divider for speakers and displays + // * a media item with the route mMediaDevice1 + assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) + .containsExactly(mMediaDevice3, null, mMediaDevice2, null, mMediaDevice1); + assertThat(mOutputMediaItemListProxy.getOutputMediaItemList().get(0).isFirstDeviceInGroup()) + .isEqualTo(Flags.enableOutputSwitcherDeviceGrouping()); + } + + @EnableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION) + @Test + public void updateMediaDevices_multipleSelectedDevices_shouldHaveCorrectDeviceOrdering() { + assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue(); + + // Create the initial output media item list with mMediaDevice2 and mMediaDevice3. + mOutputMediaItemListProxy.updateMediaDevices( + /* devices= */ List.of(mMediaDevice2, mMediaDevice4, mMediaDevice3, mMediaDevice1), + /* selectedDevices */ List.of(mMediaDevice1, mMediaDevice2, mMediaDevice3), + /* connectedMediaDevice= */ null, + /* needToHandleMutingExpectedDevice= */ false, + /* connectNewDeviceMediaItem= */ null); + + if (Flags.enableOutputSwitcherDeviceGrouping()) { + // When the device grouping is enabled, the order of selected devices are preserved: + // * a media item with the selected mMediaDevice2 + // * a media item with the selected mMediaDevice3 + // * a media item with the selected mMediaDevice1 + // * a group divider for speakers and displays + // * a media item with the mMediaDevice4 + assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) + .containsExactly( + mMediaDevice2, mMediaDevice3, mMediaDevice1, null, mMediaDevice4); + assertThat( + mOutputMediaItemListProxy + .getOutputMediaItemList() + .get(0) + .isFirstDeviceInGroup()) + .isTrue(); + } else { + // When the device grouping is disabled, the order of selected devices are reverted: + // * a media item with the selected mMediaDevice1 + // * a media item with the selected mMediaDevice3 + // * a media item with the selected mMediaDevice2 + // * a group divider for speakers and displays + // * a media item with the mMediaDevice4 + assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) + .containsExactly( + mMediaDevice1, mMediaDevice3, mMediaDevice2, null, mMediaDevice4); + } + + // Update the output media item list with a selected device being deselected. + mOutputMediaItemListProxy.updateMediaDevices( + /* devices= */ List.of(mMediaDevice4, mMediaDevice1, mMediaDevice3, mMediaDevice2), + /* selectedDevices */ List.of(mMediaDevice2, mMediaDevice3), + /* connectedMediaDevice= */ null, + /* needToHandleMutingExpectedDevice= */ false, + /* connectNewDeviceMediaItem= */ null); + + if (Flags.enableOutputSwitcherDeviceGrouping()) { + // When the device grouping is enabled, the order of selected devices are preserved: + // * a media item with the selected mMediaDevice2 + // * a media item with the selected mMediaDevice3 + // * a media item with the selected mMediaDevice1 + // * a group divider for speakers and displays + // * a media item with the mMediaDevice4 + assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) + .containsExactly( + mMediaDevice2, mMediaDevice3, mMediaDevice1, null, mMediaDevice4); + assertThat( + mOutputMediaItemListProxy + .getOutputMediaItemList() + .get(0) + .isFirstDeviceInGroup()) + .isTrue(); + } else { + // When the device grouping is disabled, the order of selected devices are reverted: + // * a media item with the selected mMediaDevice1 + // * a media item with the selected mMediaDevice3 + // * a media item with the selected mMediaDevice2 + // * a group divider for speakers and displays + // * a media item with the mMediaDevice4 + assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) + .containsExactly( + mMediaDevice1, mMediaDevice3, mMediaDevice2, null, mMediaDevice4); + } + + // Update the output media item list with a selected device is missing. + mOutputMediaItemListProxy.updateMediaDevices( + /* devices= */ List.of(mMediaDevice1, mMediaDevice3, mMediaDevice4), + /* selectedDevices */ List.of(mMediaDevice3), + /* connectedMediaDevice= */ null, + /* needToHandleMutingExpectedDevice= */ false, + /* connectNewDeviceMediaItem= */ null); + + if (Flags.enableOutputSwitcherDeviceGrouping()) { + // When the device grouping is enabled, the order of selected devices are preserved: + // * a media item with the selected mMediaDevice3 + // * a media item with the selected mMediaDevice1 + // * a group divider for speakers and displays + // * a media item with the mMediaDevice4 + assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) + .containsExactly(mMediaDevice3, mMediaDevice1, null, mMediaDevice4); + assertThat( + mOutputMediaItemListProxy + .getOutputMediaItemList() + .get(0) + .isFirstDeviceInGroup()) + .isTrue(); + } else { + // When the device grouping is disabled, the order of selected devices are reverted: + // * a media item with the selected mMediaDevice1 + // * a media item with the selected mMediaDevice3 + // * a group divider for speakers and displays + // * a media item with the mMediaDevice4 + assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) + .containsExactly(mMediaDevice1, mMediaDevice3, null, mMediaDevice4); + } + } + + @EnableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION) + @Test + public void updateMediaDevices_withConnectNewDeviceMediaItem_shouldUpdateMediaItemList() { + assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue(); + + // Create the initial output media item list with a connect new device media item. + mOutputMediaItemListProxy.updateMediaDevices( + /* devices= */ List.of(mMediaDevice2, mMediaDevice3), + /* selectedDevices */ List.of(mMediaDevice3), + /* connectedMediaDevice= */ null, + /* needToHandleMutingExpectedDevice= */ false, + mConnectNewDeviceMediaItem); + + // Check the output media items to be + // * a media item with the selected mMediaDevice3 + // * a group divider for suggested devices + // * a media item with the mMediaDevice2 + // * a connect new device media item + assertThat(mOutputMediaItemListProxy.getOutputMediaItemList()) + .contains(mConnectNewDeviceMediaItem); + assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) + .containsExactly(mMediaDevice3, null, mMediaDevice2, null); + + // Update the output media item list without a connect new device media item. + mOutputMediaItemListProxy.updateMediaDevices( + /* devices= */ List.of(mMediaDevice2, mMediaDevice3), + /* selectedDevices */ List.of(mMediaDevice3), + /* connectedMediaDevice= */ null, + /* needToHandleMutingExpectedDevice= */ false, + /* connectNewDeviceMediaItem= */ null); + + // Check the output media items to be + // * a media item with the selected mMediaDevice3 + // * a group divider for suggested devices + // * a media item with the mMediaDevice2 + assertThat(mOutputMediaItemListProxy.getOutputMediaItemList()) + .doesNotContain(mConnectNewDeviceMediaItem); + assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList())) + .containsExactly(mMediaDevice3, null, mMediaDevice2); + } + + @DisableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION) + @Test + public void clearAndAddAll_shouldUpdateMediaItemList() { + assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue(); + + mOutputMediaItemListProxy.clearAndAddAll(List.of(mMediaItem1)); + assertThat(mOutputMediaItemListProxy.getOutputMediaItemList()).containsExactly(mMediaItem1); + assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse(); + + mOutputMediaItemListProxy.clearAndAddAll(List.of(mMediaItem2)); + assertThat(mOutputMediaItemListProxy.getOutputMediaItemList()).containsExactly(mMediaItem2); + assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse(); + } + + @EnableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION) + @Test + public void clear_flagOn_shouldClearMediaItemList() { + assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue(); + + mOutputMediaItemListProxy.updateMediaDevices( + /* devices= */ List.of(mMediaDevice1), + /* selectedDevices */ List.of(), + /* connectedMediaDevice= */ null, + /* needToHandleMutingExpectedDevice= */ false, + /* connectNewDeviceMediaItem= */ null); + assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse(); + + mOutputMediaItemListProxy.clear(); + assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue(); + } + + @DisableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION) + @Test + public void clear_flagOff_shouldClearMediaItemList() { + assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue(); + + mOutputMediaItemListProxy.clearAndAddAll(List.of(mMediaItem1)); + assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse(); + + mOutputMediaItemListProxy.clear(); + assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue(); + } + + @EnableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION) + @Test + public void removeMutingExpectedDevices_flagOn_shouldClearMediaItemList() { + assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue(); + + mOutputMediaItemListProxy.updateMediaDevices( + /* devices= */ List.of(mMediaDevice1), + /* selectedDevices */ List.of(), + /* connectedMediaDevice= */ null, + /* needToHandleMutingExpectedDevice= */ false, + /* connectNewDeviceMediaItem= */ null); + assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse(); + + mOutputMediaItemListProxy.removeMutingExpectedDevices(); + assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse(); + } + + @DisableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION) + @Test + public void removeMutingExpectedDevices_flagOff_shouldClearMediaItemList() { + assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue(); + + mOutputMediaItemListProxy.clearAndAddAll(List.of(mMediaItem1)); + assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse(); + + mOutputMediaItemListProxy.removeMutingExpectedDevices(); + assertThat(mOutputMediaItemListProxy.getOutputMediaItemList()).containsExactly(mMediaItem1); + assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse(); + } + + private List<MediaDevice> getMediaDevices(List<MediaItem> mediaItems) { + return mediaItems.stream() + .map(item -> item.getMediaDevice().orElse(null)) + .collect(Collectors.toList()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt index 92b26ea3a8ef..7e42ec7e83b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.text.AnnotatedString import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest +import com.android.compose.theme.PlatformTheme import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -55,19 +56,21 @@ class DragAndDropTest : SysuiTestCase() { listState: EditTileListState, onSetTiles: (List<TileSpec>) -> Unit, ) { - DefaultEditTileGrid( - listState = listState, - otherTiles = listOf(), - columns = 4, - largeTilesSpan = 4, - modifier = Modifier.fillMaxSize(), - onAddTile = {}, - onRemoveTile = {}, - onSetTiles = onSetTiles, - onResize = { _, _ -> }, - onStopEditing = {}, - onReset = null, - ) + PlatformTheme { + DefaultEditTileGrid( + listState = listState, + otherTiles = listOf(), + columns = 4, + largeTilesSpan = 4, + modifier = Modifier.fillMaxSize(), + onAddTile = {}, + onRemoveTile = {}, + onSetTiles = onSetTiles, + onResize = { _, _ -> }, + onStopEditing = {}, + onReset = null, + ) + } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt index e76be82c9340..9d4a425c678b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.test.performClick import androidx.compose.ui.text.AnnotatedString import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.theme.PlatformTheme import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -56,19 +57,22 @@ class EditModeTest : SysuiTestCase() { var tiles by remember { mutableStateOf(TestEditTiles) } val (currentTiles, otherTiles) = tiles.partition { it.tile.isCurrent } val listState = EditTileListState(currentTiles, columns = 4, largeTilesSpan = 2) - DefaultEditTileGrid( - listState = listState, - otherTiles = otherTiles, - columns = 4, - largeTilesSpan = 4, - modifier = Modifier.fillMaxSize(), - onAddTile = { tiles = tiles.add(it) }, - onRemoveTile = { tiles = tiles.remove(it) }, - onSetTiles = {}, - onResize = { _, _ -> }, - onStopEditing = {}, - onReset = null, - ) + + PlatformTheme { + DefaultEditTileGrid( + listState = listState, + otherTiles = otherTiles, + columns = 4, + largeTilesSpan = 4, + modifier = Modifier.fillMaxSize(), + onAddTile = { tiles = tiles.add(it) }, + onRemoveTile = { tiles = tiles.remove(it) }, + onSetTiles = {}, + onResize = { _, _ -> }, + onStopEditing = {}, + onReset = null, + ) + } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt index 021be3235ee1..5e76000cc7f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.test.swipeRight import androidx.compose.ui.text.AnnotatedString import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.theme.PlatformTheme import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -61,19 +62,21 @@ class ResizingTest : SysuiTestCase() { listState: EditTileListState, onResize: (TileSpec, Boolean) -> Unit, ) { - DefaultEditTileGrid( - listState = listState, - otherTiles = listOf(), - columns = 4, - largeTilesSpan = 4, - modifier = Modifier.fillMaxSize(), - onAddTile = {}, - onRemoveTile = {}, - onSetTiles = {}, - onResize = onResize, - onStopEditing = {}, - onReset = null, - ) + PlatformTheme { + DefaultEditTileGrid( + listState = listState, + otherTiles = listOf(), + columns = 4, + largeTilesSpan = 4, + modifier = Modifier.fillMaxSize(), + onAddTile = {}, + onRemoveTile = {}, + onSetTiles = {}, + onResize = onResize, + onStopEditing = {}, + onReset = null, + ) + } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt index a8bfbd18d0c5..356d445ab4d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt @@ -40,9 +40,9 @@ import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.flags.EnableSceneContainer -import com.android.systemui.kosmos.Kosmos import com.android.systemui.res.R import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.android.wifitrackerlib.WifiEntry @@ -66,7 +66,7 @@ import org.mockito.kotlin.whenever @EnableFlags(Flags.FLAG_QS_TILE_DETAILED_VIEW) @UiThreadTest class InternetDetailsContentManagerTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = testKosmos() private val handler: Handler = kosmos.fakeExecutorHandler private val scope: CoroutineScope = mock<CoroutineScope>() private val telephonyManager: TelephonyManager = kosmos.telephonyManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt index cf54df8565d3..997cf417fe10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt @@ -22,6 +22,7 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.Display import androidx.test.filters.SmallTest +import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.deviceStateManager import com.android.systemui.display.domain.interactor.RearDisplayStateInteractor @@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import org.junit.Before import org.junit.Test +import org.mockito.Mockito.times import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -59,6 +61,7 @@ class RearDisplayCoreStartableTest : SysuiTestCase() { fakeRearDisplayStateInteractor, kosmos.rearDisplayInnerDialogDelegateFactory, kosmos.testScope, + kosmos.keyguardUpdateMonitor, ) @Before @@ -96,6 +99,26 @@ class RearDisplayCoreStartableTest : SysuiTestCase() { } } + @Test + @EnableFlags(FLAG_DEVICE_STATE_RDM_V2) + fun testDialogResumesAfterKeyguardGone() = + kosmos.runTest { + impl.use { + it.start() + fakeRearDisplayStateInteractor.emitRearDisplay() + + verify(mockDialog).show() + + it.keyguardCallback.onKeyguardVisibilityChanged(true) + // Do not need to check that the dialog is dismissed, since SystemUIDialog + // implementation handles that. We just toggle keyguard here so that the flow + // emits. + + it.keyguardCallback.onKeyguardVisibilityChanged(false) + verify(mockDialog, times(2)).show() + } + } + private class FakeRearDisplayStateInteractor(private val kosmos: Kosmos) : RearDisplayStateInteractor { private val stateFlow = MutableSharedFlow<RearDisplayStateInteractor.State>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt index e0118b18ff64..9b03833fd1b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt @@ -22,6 +22,7 @@ import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.os.PowerManager import android.os.UserManager +import android.platform.test.annotations.EnableFlags import android.testing.TestableContext import android.testing.TestableLooper import android.view.Display @@ -29,17 +30,23 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.app.AssistUtils import com.android.internal.logging.UiEventLogger +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager +import com.android.systemui.kosmos.testScope import com.android.systemui.log.assertLogsWtf +import com.android.systemui.model.fakeSysUIStatePerDisplayRepository import com.android.systemui.model.sysUiState +import com.android.systemui.model.sysUiStateFactory import com.android.systemui.navigationbar.NavigationBarController import com.android.systemui.navigationbar.NavigationModeController +import com.android.systemui.navigationbar.views.NavigationBar import com.android.systemui.process.ProcessWrapper import com.android.systemui.recents.LauncherProxyService.ACTION_QUICKSTEP import com.android.systemui.settings.FakeDisplayTracker @@ -56,18 +63,20 @@ import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.testKosmos import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.android.wm.shell.back.BackAnimation import com.android.wm.shell.sysui.ShellInterface import com.google.common.util.concurrent.MoreExecutors import java.util.Optional import java.util.concurrent.Executor +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.any @@ -81,6 +90,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @@ -96,8 +106,10 @@ class LauncherProxyServiceTest : SysuiTestCase() { private val displayTracker = FakeDisplayTracker(mContext) private val fakeSystemClock = FakeSystemClock() private val sysUiState = kosmos.sysUiState + private val sysUiStateFactory = kosmos.sysUiStateFactory private val wakefulnessLifecycle = WakefulnessLifecycle(mContext, null, fakeSystemClock, dumpManager) + private val sysuiStatePerDisplayRepository = kosmos.fakeSysUIStatePerDisplayRepository @Mock private lateinit var launcherProxy: ILauncherProxy.Stub @Mock private lateinit var packageManager: PackageManager @@ -149,6 +161,8 @@ class LauncherProxyServiceTest : SysuiTestCase() { // return isSystemUser as true by default. `when`(processWrapper.isSystemUser).thenReturn(true) + sysuiStatePerDisplayRepository.add(Display.DEFAULT_DISPLAY, sysUiState) + runBlocking { kosmos.displayRepository.apply { addDisplay(0) } } subject = createLauncherProxyService(context) } @@ -249,6 +263,48 @@ class LauncherProxyServiceTest : SysuiTestCase() { verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any()) } + @Test + fun notifySysUiStateFlagsForAllDisplays_triggersUpdateInAllDisplays() = + kosmos.testScope.runTest { + kosmos.displayRepository.apply { + addDisplay(0) + addDisplay(1) + addDisplay(2) + } + kosmos.fakeSysUIStatePerDisplayRepository.apply { + add(1, sysUiStateFactory.create(1)) + add(2, sysUiStateFactory.create(2)) + } + clearInvocations(launcherProxy) + subject.notifySysUiStateFlagsForAllDisplays() + + verify(launcherProxy).onSystemUiStateChanged(anyLong(), eq(0)) + verify(launcherProxy).onSystemUiStateChanged(anyLong(), eq(1)) + verify(launcherProxy).onSystemUiStateChanged(anyLong(), eq(2)) + } + + @Test + @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND) + fun updateSystemUiStateFlags_updatesAllNavBars() = + kosmos.testScope.runTest { + kosmos.displayRepository.apply { + addDisplay(0) + addDisplay(1) + } + kosmos.fakeSysUIStatePerDisplayRepository.apply { + add(1, sysUiStateFactory.create(1)) + } + val navBar0 = mock<NavigationBar>() + val navBar1 = mock<NavigationBar>() + whenever(navBarController.getNavigationBar(eq(0))).thenReturn(navBar0) + whenever(navBarController.getNavigationBar(eq(1))).thenReturn(navBar1) + + subject.updateSystemUiStateFlags() + + verify(navBar0).updateSystemUiStateFlags() + verify(navBar1).updateSystemUiStateFlags() + } + private fun createLauncherProxyService(ctx: Context): LauncherProxyService { return LauncherProxyService( ctx, @@ -260,7 +316,7 @@ class LauncherProxyServiceTest : SysuiTestCase() { screenPinningRequest, navModeController, statusBarWinController, - sysUiState, + kosmos.fakeSysUIStatePerDisplayRepository, mock(), mock(), userTracker, @@ -276,6 +332,7 @@ class LauncherProxyServiceTest : SysuiTestCase() { broadcastDispatcher, backAnimation, processWrapper, + kosmos.displayRepository, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java index c231be181977..826c54787662 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java @@ -23,6 +23,7 @@ import android.media.AudioManager; import android.net.Uri; import android.os.Binder; import android.os.UserHandle; +import android.util.Log; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -37,36 +38,34 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class RingtonePlayerTest extends SysuiTestCase { - private AudioManager mAudioManager; - private static final String TAG = "RingtonePlayerTest"; - @Before - public void setup() throws Exception { - mAudioManager = getContext().getSystemService(AudioManager.class); - } - @Test public void testRingtonePlayerUriUserCheck() { - android.media.IRingtonePlayer irp = mAudioManager.getRingtonePlayer(); - final AudioAttributes aa = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build(); - // get a UserId that doesn't belong to mine - final int otherUserId = UserHandle.myUserId() == 0 ? 10 : 0; - // build a URI that I shouldn't have access to - final Uri uri = new Uri.Builder() - .scheme("content").authority(otherUserId + "@media") - .appendPath("external").appendPath("downloads") - .appendPath("bogusPathThatDoesNotMatter.mp3") - .build(); - if (android.media.audio.Flags.ringtoneUserUriCheck()) { - assertThrows(SecurityException.class, () -> - irp.play(new Binder(), uri, aa, 1.0f /*volume*/, false /*looping*/) - ); + // temporarily skipping this test + Log.i(TAG, "skipping testRingtonePlayerUriUserCheck"); + return; - assertThrows(SecurityException.class, () -> - irp.getTitle(uri)); - } + // TODO change how IRingtonePlayer is created +// android.media.IRingtonePlayer irp = mAudioManager.getRingtonePlayer(); +// final AudioAttributes aa = new AudioAttributes.Builder() +// .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build(); +// // get a UserId that doesn't belong to mine +// final int otherUserId = UserHandle.myUserId() == 0 ? 10 : 0; +// // build a URI that I shouldn't have access to +// final Uri uri = new Uri.Builder() +// .scheme("content").authority(otherUserId + "@media") +// .appendPath("external").appendPath("downloads") +// .appendPath("bogusPathThatDoesNotMatter.mp3") +// .build(); +// if (android.media.audio.Flags.ringtoneUserUriCheck()) { +// assertThrows(SecurityException.class, () -> +// irp.play(new Binder(), uri, aa, 1.0f /*volume*/, false /*looping*/) +// ); +// +// assertThrows(SecurityException.class, () -> +// irp.getTitle(uri)); +// } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index b7040ee2a11e..020b5dd054b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.nullable import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After @@ -61,10 +62,6 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit -private fun <T> anyObject(): T { - return Mockito.anyObject<T>() -} - @SmallTest @RunWithLooper(setAsMainLooper = true) @RunWith(AndroidJUnit4::class) @@ -265,7 +262,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { verify(statusbarStateController, never()).setState(anyInt()) verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true) verify(centralSurfaces) - .showBouncerWithDimissAndCancelIfKeyguard(anyObject(), anyObject()) + .showBouncerWithDimissAndCancelIfKeyguard(nullable(), nullable()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt index 3937d3d46d68..ff17a362eb32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt @@ -20,7 +20,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener @@ -97,7 +96,6 @@ class ViewConfigCoordinatorTest : SysuiTestCase() { fun themeChangePropagatesToEntry() { configurationListener.onThemeChanged() verify(entry).onDensityOrFontScaleChanged() - checkGutsExposedCalled() verifyNoMoreInteractions(entry, row) } @@ -105,7 +103,6 @@ class ViewConfigCoordinatorTest : SysuiTestCase() { fun densityChangePropagatesToEntry() { configurationListener.onDensityOrFontScaleChanged() verify(entry).onDensityOrFontScaleChanged() - checkGutsExposedCalled() verifyNoMoreInteractions(entry, row) } @@ -129,7 +126,6 @@ class ViewConfigCoordinatorTest : SysuiTestCase() { verify(entry).row verify(row).onUiModeChanged() verify(entry).onDensityOrFontScaleChanged() - checkGutsExposedCalled() verifyNoMoreInteractions(entry, row) clearInvocations(entry, row) @@ -160,7 +156,6 @@ class ViewConfigCoordinatorTest : SysuiTestCase() { verify(entry).row verify(row).onUiModeChanged() verify(entry).onDensityOrFontScaleChanged() - checkGutsExposedCalled() verifyNoMoreInteractions(entry, row) clearInvocations(entry, row) @@ -196,14 +191,7 @@ class ViewConfigCoordinatorTest : SysuiTestCase() { verify(entry).row verify(row).onUiModeChanged() verify(entry).onDensityOrFontScaleChanged() - checkGutsExposedCalled() verifyNoMoreInteractions(entry, row) clearInvocations(entry, row) } - - private fun checkGutsExposedCalled() { - if (!Flags.notificationUndoGutsOnConfigChanged()) { - verify(entry).areGutsExposed() - } - } } 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 84f39be2eeed..2ea4e7f67b3c 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 @@ -61,6 +61,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.internal.logging.MetricsLogger; import com.android.internal.widget.CachingIconView; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestableContext; @@ -71,12 +72,18 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.FeedbackIcon; +import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.SourceType; +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.headsup.PinnedStatus; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; +import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization; @@ -173,6 +180,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test + @DisableFlags(NotificationBundleUi.FLAG_NAME) public void testUpdateBackgroundColors_isRecursive() throws Exception { ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); group.setTintColor(Color.RED); @@ -597,14 +605,14 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { public void testGetIsNonblockable() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification()); - row.setEntry(null); + row.setEntryLegacy(null); assertTrue(row.getIsNonblockable()); NotificationEntry entry = mock(NotificationEntry.class); Mockito.doReturn(false, true).when(entry).isBlockable(); - row.setEntry(entry); + row.setEntryLegacy(entry); assertTrue(row.getIsNonblockable()); assertFalse(row.getIsNonblockable()); } @@ -939,12 +947,14 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) + @DisableFlags(NotificationBundleUi.FLAG_NAME) public void isExpanded_sensitivePromotedNotification_notExpanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); NotificationEntry entry = mock(NotificationEntry.class); when(entry.isPromotedOngoing()).thenReturn(true); - row.setEntry(entry); + row.setEntryLegacy(entry); + setRowPromotedOngoing(row); row.setSensitive(/* sensitive= */true, /* hideSensitive= */false); row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */true); @@ -954,12 +964,14 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) + @DisableFlags(NotificationBundleUi.FLAG_NAME) public void isExpanded_promotedNotificationNotOnKeyguard_expanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); NotificationEntry entry = mock(NotificationEntry.class); when(entry.isPromotedOngoing()).thenReturn(true); - row.setEntry(entry); + row.setEntryLegacy(entry); + setRowPromotedOngoing(row); row.setOnKeyguard(false); // THEN @@ -968,12 +980,14 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) + @DisableFlags(NotificationBundleUi.FLAG_NAME) public void isExpanded_promotedNotificationAllowOnKeyguard_expanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); NotificationEntry entry = mock(NotificationEntry.class); when(entry.isPromotedOngoing()).thenReturn(true); - row.setEntry(entry); + row.setEntryLegacy(entry); + setRowPromotedOngoing(row); row.setOnKeyguard(true); // THEN @@ -982,13 +996,15 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) + @DisableFlags(NotificationBundleUi.FLAG_NAME) public void isExpanded_promotedNotificationIgnoreLockscreenConstraints_expanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); NotificationEntry entry = mock(NotificationEntry.class); when(entry.isPromotedOngoing()).thenReturn(true); - row.setEntry(entry); + row.setEntryLegacy(entry); + setRowPromotedOngoing(row); row.setOnKeyguard(true); row.setIgnoreLockscreenConstraints(true); @@ -996,15 +1012,35 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { assertThat(row.isExpanded()).isTrue(); } + private static void setRowPromotedOngoing(ExpandableNotificationRow row) { + final NotificationEntry entry = mock(NotificationEntry.class); + when(entry.isPromotedOngoing()).thenReturn(true); + if (NotificationBundleUi.isEnabled()) { + final EntryAdapter entryAdapter = new NotificationEntryAdapter( + mock(NotificationActivityStarter.class), + mock(MetricsLogger.class), + mock(PeopleNotificationIdentifier.class), + mock(NotificationIconStyleProvider.class), + mock(VisualStabilityCoordinator.class), + mock(NotificationActionClickManager.class), + entry); + row.setEntryAdapter(entryAdapter); + } else { + row.setEntryLegacy(entry); + } + } + @Test @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) + @DisableFlags(NotificationBundleUi.FLAG_NAME) public void isExpanded_promotedNotificationSaveSpaceOnLockScreen_notExpanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); NotificationEntry entry = mock(NotificationEntry.class); when(entry.isPromotedOngoing()).thenReturn(true); - row.setEntry(entry); + row.setEntryLegacy(entry); + setRowPromotedOngoing(row); row.setOnKeyguard(true); row.setSaveSpaceOnLockscreen(true); @@ -1014,13 +1050,15 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) + @DisableFlags(NotificationBundleUi.FLAG_NAME) public void isExpanded_promotedNotificationNotSaveSpaceOnLockScreen_expanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); NotificationEntry entry = mock(NotificationEntry.class); when(entry.isPromotedOngoing()).thenReturn(true); - row.setEntry(entry); + row.setEntryLegacy(entry); + setRowPromotedOngoing(row); row.setOnKeyguard(true); row.setSaveSpaceOnLockscreen(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index c874bc6056c6..cf8278eb8ac6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row import android.annotation.DimenRes import android.content.res.Resources import android.os.UserHandle +import android.platform.test.annotations.DisableFlags import android.service.notification.StatusBarNotification import android.testing.TestableLooper import android.testing.ViewUtils @@ -93,13 +94,12 @@ class NotificationContentViewTest : SysuiTestCase() { /* attrs= */ null, UserHandle.CURRENT ).apply { - entry = mockEntry entryAdapter = mockEntryAdapter } } false -> { ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply { - entry = mockEntry + entryLegacy = mockEntry } } } @@ -402,6 +402,7 @@ class NotificationContentViewTest : SysuiTestCase() { } @Test + @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES) fun setExpandedChild_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() { // Given: bottom margin of actionListMarginTarget is notificationContentMargin // Bubble button should not be shown for the given NotificationEntry @@ -429,6 +430,7 @@ class NotificationContentViewTest : SysuiTestCase() { } @Test + @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES) fun setExpandedChild_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() { // Given: bottom margin of actionListMarginTarget is notificationContentMargin // Bubble button should be shown for the given NotificationEntry @@ -458,6 +460,7 @@ class NotificationContentViewTest : SysuiTestCase() { } @Test + @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES) fun onNotificationUpdated_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() { // Given: bottom margin of actionListMarginTarget is notificationContentMargin val mockNotificationEntry = createMockNotificationEntry() @@ -486,6 +489,7 @@ class NotificationContentViewTest : SysuiTestCase() { } @Test + @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES) fun onNotificationUpdated_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() { // Given: bottom margin of actionListMarginTarget is notificationContentMargin val mockNotificationEntry = createMockNotificationEntry() @@ -514,7 +518,7 @@ class NotificationContentViewTest : SysuiTestCase() { // Given: controller says bubbles are enabled for the user view.setBubblesEnabledForUser(true) - // Then: bottom margin of actionListMarginTarget should not change, still be 20 + // Then: bottom margin of actionListMarginTarget should be changed to 0 assertEquals(0, getMarginBottom(actionListMarginTarget)) } @@ -628,8 +632,7 @@ class NotificationContentViewTest : SysuiTestCase() { whenever(sbnMock.user).thenReturn(userMock) } - private fun createMockNotificationEntryAdapter() = - mock<EntryAdapter>() + private fun createMockNotificationEntryAdapter() = mock<EntryAdapter>() private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout { val outerLayout = LinearLayout(mContext) 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 0c0ef9d5edfe..10de86644015 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 @@ -67,6 +67,7 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior 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.stack.NotificationListContainer @@ -146,6 +147,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { @Mock private lateinit var notificationManager: INotificationManager @Mock private lateinit var shortcutManager: ShortcutManager @Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController + @Mock private lateinit var packageDemotionInteractor: PackageDemotionInteractor @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier @Mock private lateinit var contextTracker: UserContextProvider @Mock private lateinit var bubblesManager: BubblesManager @@ -185,6 +187,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { launcherApps, shortcutManager, channelEditorDialogController, + packageDemotionInteractor, contextTracker, assistantFeedbackController, Optional.of(bubblesManager), @@ -297,45 +300,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { } @Test - fun testChangeDensityOrFontScale() { - val guts = spy(NotificationGuts(mContext)) - whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock -> - handler.post((invocation.arguments[0] as Runnable)) - null - } - - // Test doesn't support animation since the guts view is not attached. - doNothing() - .whenever(guts) - .openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>()) - val realRow = createTestNotificationRow() - val menuItem = createTestMenuItem(realRow) - val row = spy(realRow) - whenever(row!!.windowToken).thenReturn(Binder()) - whenever(row.guts).thenReturn(guts) - doNothing().whenever(row).ensureGutsInflated() - val realEntry = realRow!!.entry - val entry = spy(realEntry) - whenever(entry.row).thenReturn(row) - whenever(entry.getGuts()).thenReturn(guts) - Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) - executor.runAllReady() - verify(guts).openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>()) - - // called once by mGutsManager.bindGuts() in mGutsManager.openGuts() - verify(row).setGutsView(any()) - row.onDensityOrFontScaleChanged() - gutsManager.onDensityOrFontScaleChanged(entry) - executor.runAllReady() - gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false) - verify(guts) - .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>()) - - // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged() - verify(row, times(2)).setGutsView(any()) - } - - @Test fun testAppOpsSettingsIntent_camera() { val ops = ArraySet<Int>() ops.add(AppOpsManager.OP_CAMERA) @@ -427,6 +391,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) .setImportance(NotificationManager.IMPORTANCE_HIGH) .build() + whenever(row.canViewBeDismissed()).thenReturn(true) whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true) val statusBarNotification = entry.sbn gutsManager.initializeNotificationInfo(row, notificationInfoView) @@ -438,6 +403,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { eq(iconStyleProvider), eq(onUserInteractionCallback), eq(channelEditorDialogController), + eq(packageDemotionInteractor), eq(statusBarNotification.packageName), any<NotificationChannel>(), eq(entry), @@ -447,7 +413,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { any<UiEventLogger>(), eq(true), eq(false), - eq(true), /* wasShownHighPriority */ + eq(true), + eq(true), eq(assistantFeedbackController), any<MetricsLogger>(), any<View.OnClickListener>(), @@ -462,6 +429,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { 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) @@ -473,6 +441,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { eq(iconStyleProvider), eq(onUserInteractionCallback), eq(channelEditorDialogController), + eq(packageDemotionInteractor), eq(statusBarNotification.packageName), any<NotificationChannel>(), eq(entry), @@ -482,7 +451,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { any<UiEventLogger>(), eq(true), eq(false), - eq(false), /* wasShownHighPriority */ + eq(true), /* wasShownHighPriority */ + eq(false), eq(assistantFeedbackController), any<MetricsLogger>(), any<View.OnClickListener>(), @@ -497,6 +467,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { 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) @@ -508,6 +479,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { eq(iconStyleProvider), eq(onUserInteractionCallback), eq(channelEditorDialogController), + eq(packageDemotionInteractor), eq(statusBarNotification.packageName), any<NotificationChannel>(), eq(entry), @@ -517,7 +489,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { any<UiEventLogger>(), eq(true), eq(false), - eq(false), /* wasShownHighPriority */ + eq(true), /* wasShownHighPriority */ + eq(false), eq(assistantFeedbackController), any<MetricsLogger>(), any<View.OnClickListener>(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt index 2e65478714af..12f7af106d10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt @@ -108,7 +108,7 @@ class FoldStateListenerTest : SysuiTestCase() { private fun setGoToSleepStates(vararg states: Int) { mContext.orCreateTestableResources.addOverride( R.array.config_deviceStatesOnWhichToSleep, - states + states, ) } @@ -117,9 +117,10 @@ class FoldStateListenerTest : SysuiTestCase() { } companion object { - private val DEVICE_STATE_FOLDED = Kosmos().foldedDeviceStateList.first() - private val DEVICE_STATE_HALF_FOLDED = Kosmos().halfFoldedDeviceState - private val DEVICE_STATE_UNFOLDED = Kosmos().unfoldedDeviceState + private val kosmos = Kosmos() + private val DEVICE_STATE_FOLDED = kosmos.foldedDeviceStateList.first() + private val DEVICE_STATE_HALF_FOLDED = kosmos.halfFoldedDeviceState + private val DEVICE_STATE_UNFOLDED = kosmos.unfoldedDeviceState private const val FOLDED = true private const val NOT_FOLDED = false diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index ffb861db182c..063b546cbae9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -296,6 +296,7 @@ public class ScrimControllerTest extends SysuiTestCase { mKosmos.getTestDispatcher(), mLinearLargeScreenShadeInterpolator, new BlurConfig(0.0f, 0.0f), + mContext, mKosmos::getWindowRootViewBlurInteractor); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); @@ -1247,6 +1248,7 @@ public class ScrimControllerTest extends SysuiTestCase { mKosmos.getTestDispatcher(), mLinearLargeScreenShadeInterpolator, new BlurConfig(0.0f, 0.0f), + mContext, mKosmos::getWindowRootViewBlurInteractor); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt index c22c62825d04..293e1fdd6d46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt @@ -57,7 +57,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.anyObject +import org.mockito.ArgumentMatchers.any @SmallTest @RunWith(AndroidJUnit4::class) @@ -138,7 +138,7 @@ class DeviceControlsControllerImplTest : SysuiTestCase() { `when`(secureSettings.getInt(Settings.Secure.CONTROLS_ENABLED, 1)).thenReturn(0) controller.setCallback(callback) - verify(controlsListingController, never()).addCallback(anyObject()) + verify(controlsListingController, never()).addCallback(any()) verify(callback).onControlsUpdate(null) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt index a553b176c34a..6618843ab46d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt @@ -26,9 +26,8 @@ import com.android.internal.util.LatencyTracker import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.foldedDeviceStateList -import com.android.systemui.halfFoldedDeviceState import com.android.systemui.keyguard.ScreenLifecycle -import com.android.systemui.kosmos.Kosmos +import com.android.systemui.testKosmos import com.android.systemui.unfold.util.FoldableDeviceStates import com.android.systemui.unfold.util.FoldableTestUtils import com.android.systemui.unfoldedDeviceState @@ -70,12 +69,10 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(deviceStateManager.supportedDeviceStates).thenReturn( - listOf( - Kosmos().foldedDeviceStateList[0], - Kosmos().unfoldedDeviceState + whenever(deviceStateManager.supportedDeviceStates) + .thenReturn( + listOf(testKosmos().foldedDeviceStateList[0], testKosmos().unfoldedDeviceState) ) - ) unfoldLatencyTracker = UnfoldLatencyTracker( @@ -85,7 +82,7 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() { context.mainExecutor, context, context.contentResolver, - screenLifecycle + screenLifecycle, ) .apply { init() } @@ -206,7 +203,7 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() { Settings.Global.putString( context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, - durationScale.toString() + durationScale.toString(), ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt index 9440280649dd..54ac3bf8220a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -33,6 +34,7 @@ import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch @@ -50,11 +52,7 @@ import org.junit.runner.RunWith class PairwiseFlowTest : SysuiTestCase() { @Test fun simple() = runBlocking { - assertThatFlow((1..3).asFlow().pairwise()) - .emitsExactly( - WithPrev(1, 2), - WithPrev(2, 3), - ) + assertThatFlow((1..3).asFlow().pairwise()).emitsExactly(WithPrev(1, 2), WithPrev(2, 3)) } @Test fun notEnough() = runBlocking { assertThatFlow(flowOf(1).pairwise()).emitsNothing() } @@ -157,48 +155,27 @@ class SetChangesFlowTest : SysuiTestCase() { fun simple() = runBlocking { assertThatFlow(flowOf(setOf(1, 2, 3), setOf(2, 3, 4)).setChanges()) .emitsExactly( - SetChanges( - added = setOf(1, 2, 3), - removed = emptySet(), - ), - SetChanges( - added = setOf(4), - removed = setOf(1), - ), + SetChanges(added = setOf(1, 2, 3), removed = emptySet()), + SetChanges(added = setOf(4), removed = setOf(1)), ) } @Test fun onlyOneEmission() = runBlocking { assertThatFlow(flowOf(setOf(1)).setChanges()) - .emitsExactly( - SetChanges( - added = setOf(1), - removed = emptySet(), - ) - ) + .emitsExactly(SetChanges(added = setOf(1), removed = emptySet())) } @Test fun fromEmptySet() = runBlocking { assertThatFlow(flowOf(emptySet(), setOf(1, 2)).setChanges()) - .emitsExactly( - SetChanges( - removed = emptySet(), - added = setOf(1, 2), - ) - ) + .emitsExactly(SetChanges(removed = emptySet(), added = setOf(1, 2))) } @Test fun dontEmitFirstEvent() = runBlocking { assertThatFlow(flowOf(setOf(1, 2), setOf(2, 3)).setChanges(emitFirstEvent = false)) - .emitsExactly( - SetChanges( - removed = setOf(1), - added = setOf(3), - ) - ) + .emitsExactly(SetChanges(removed = setOf(1), added = setOf(3))) } } @@ -235,11 +212,7 @@ class SampleFlowTest : SysuiTestCase() { emit(4) } assertThatFlow(sampler.sample(samplee) { a, b -> a to b }) - .emitsExactly( - 2 to 1, - 3 to 3, - 4 to 3, - ) + .emitsExactly(2 to 1, 3 to 3, 4 to 3) } } @@ -419,10 +392,262 @@ class ThrottleFlowTest : SysuiTestCase() { } } +@SmallTest +@RunWith(AndroidJUnit4::class) +class SlidingWindowFlowTest : SysuiTestCase() { + + @Test + fun basicWindowing() = runTest { + val choreographer = createChoreographer(this) + val output = mutableListOf<List<Int>>() + val collectJob = + backgroundScope.launch { + (1..5) + .asFlow() + .onEach { delay(100) } + .slidingWindow(300.milliseconds, choreographer.fakeClock) + .toList(output) + } + + choreographer.advanceAndRun(0) + assertThat(output).isEmpty() + + choreographer.advanceAndRun(100) + assertThat(output).containsExactly(listOf(1)) + + choreographer.advanceAndRun(1) + assertThat(output).containsExactly(listOf(1)) + + choreographer.advanceAndRun(99) + assertThat(output).containsExactly(listOf(1), listOf(1, 2)) + + choreographer.advanceAndRun(100) + assertThat(output).containsExactly(listOf(1), listOf(1, 2), listOf(1, 2, 3)) + + choreographer.advanceAndRun(100) + assertThat(output) + .containsExactly(listOf(1), listOf(1, 2), listOf(1, 2, 3), listOf(2, 3, 4)) + + choreographer.advanceAndRun(100) + assertThat(output) + .containsExactly( + listOf(1), + listOf(1, 2), + listOf(1, 2, 3), + listOf(2, 3, 4), + listOf(3, 4, 5), + ) + + choreographer.advanceAndRun(100) + assertThat(output) + .containsExactly( + listOf(1), + listOf(1, 2), + listOf(1, 2, 3), + listOf(2, 3, 4), + listOf(3, 4, 5), + listOf(4, 5), + ) + + choreographer.advanceAndRun(100) + assertThat(output) + .containsExactly( + listOf(1), + listOf(1, 2), + listOf(1, 2, 3), + listOf(2, 3, 4), + listOf(3, 4, 5), + listOf(4, 5), + listOf(5), + ) + + choreographer.advanceAndRun(100) + assertThat(output) + .containsExactly( + listOf(1), + listOf(1, 2), + listOf(1, 2, 3), + listOf(2, 3, 4), + listOf(3, 4, 5), + listOf(4, 5), + listOf(5), + emptyList<Int>(), + ) + + // Verify no more emissions + choreographer.advanceAndRun(9999999999) + assertThat(output) + .containsExactly( + listOf(1), + listOf(1, 2), + listOf(1, 2, 3), + listOf(2, 3, 4), + listOf(3, 4, 5), + listOf(4, 5), + listOf(5), + emptyList<Int>(), + ) + + assertThat(collectJob.isCompleted).isTrue() + } + + @Test + fun initialEmptyFlow() = runTest { + val choreographer = createChoreographer(this) + val output = mutableListOf<List<Int>>() + val collectJob = + backgroundScope.launch { + flow { + delay(200) + emit(1) + } + .slidingWindow(100.milliseconds, choreographer.fakeClock) + .toList(output) + } + + choreographer.advanceAndRun(0) + assertThat(output).isEmpty() + + choreographer.advanceAndRun(200) + assertThat(output).containsExactly(listOf(1)) + + choreographer.advanceAndRun(100) + assertThat(output).containsExactly(listOf(1), emptyList<Int>()) + + assertThat(collectJob.isCompleted).isTrue() + } + + @Test + fun windowLargerThanData() = runTest { + val choreographer = createChoreographer(this) + val output = mutableListOf<List<Int>>() + val collectJob = + backgroundScope.launch { + (1..3) + .asFlow() + .onEach { delay(50) } + .slidingWindow(500.milliseconds, choreographer.fakeClock) + .toList(output) + } + + choreographer.advanceAndRun(0) + assertThat(output).isEmpty() + + choreographer.advanceAndRun(50) + assertThat(output).containsExactly(listOf(1)) + + choreographer.advanceAndRun(50) + assertThat(output).containsExactly(listOf(1), listOf(1, 2)) + + choreographer.advanceAndRun(50) + assertThat(output).containsExactly(listOf(1), listOf(1, 2), listOf(1, 2, 3)) + + // It has been 100ms since the first emission, which means we have 400ms left until the + // first item is evicted from the window. Ensure that we have no evictions until that time. + choreographer.advanceAndRun(399) + assertThat(output).containsExactly(listOf(1), listOf(1, 2), listOf(1, 2, 3)) + + choreographer.advanceAndRun(1) + assertThat(output).containsExactly(listOf(1), listOf(1, 2), listOf(1, 2, 3), listOf(2, 3)) + + choreographer.advanceAndRun(50) + assertThat(output) + .containsExactly(listOf(1), listOf(1, 2), listOf(1, 2, 3), listOf(2, 3), listOf(3)) + + choreographer.advanceAndRun(50) + assertThat(output) + .containsExactly( + listOf(1), + listOf(1, 2), + listOf(1, 2, 3), + listOf(2, 3), + listOf(3), + emptyList<Int>(), + ) + + assertThat(collectJob.isCompleted).isTrue() + } + + @Test + fun dataGapLargerThanWindow() = runTest { + val choreographer = createChoreographer(this) + val output = mutableListOf<List<Int>>() + val collectJob = + backgroundScope.launch { + flow { + emit(1) + delay(200) + emit(2) + delay(500) // Gap larger than window + emit(3) + } + .slidingWindow(300.milliseconds, choreographer.fakeClock) + .toList(output) + } + + choreographer.advanceAndRun(0) + assertThat(output).containsExactly(listOf(1)) + + choreographer.advanceAndRun(200) + assertThat(output).containsExactly(listOf(1), listOf(1, 2)) + + choreographer.advanceAndRun(100) + assertThat(output).containsExactly(listOf(1), listOf(1, 2), listOf(2)) + + choreographer.advanceAndRun(200) + assertThat(output).containsExactly(listOf(1), listOf(1, 2), listOf(2), emptyList<Int>()) + + choreographer.advanceAndRun(200) + assertThat(output) + .containsExactly(listOf(1), listOf(1, 2), listOf(2), emptyList<Int>(), listOf(3)) + + choreographer.advanceAndRun(300) + assertThat(output) + .containsExactly( + listOf(1), + listOf(1, 2), + listOf(2), + emptyList<Int>(), + listOf(3), + emptyList<Int>(), + ) + + assertThat(collectJob.isCompleted).isTrue() + } + + @Test + fun emptyFlow() = runTest { + val choreographer = createChoreographer(this) + val output = mutableListOf<List<Int>>() + + val collectJob = + backgroundScope.launch { + emptyFlow<Int>().slidingWindow(100.milliseconds).toList(output) + } + + choreographer.advanceAndRun(0) + assertThat(output).isEmpty() + + assertThat(collectJob.isCompleted).isTrue() + } + + private fun createChoreographer(testScope: TestScope) = + object { + val fakeClock = FakeSystemClock() + + fun advanceAndRun(millis: Long) { + fakeClock.advanceTime(millis) + testScope.advanceTimeBy(millis) + testScope.runCurrent() + } + } +} + private fun <T> assertThatFlow(flow: Flow<T>) = object { suspend fun emitsExactly(vararg emissions: T) = assertThat(flow.toList()).containsExactly(*emissions).inOrder() + suspend fun emitsNothing() = assertThat(flow.toList()).isEmpty() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt index 5f3442048fcd..422b20e8b951 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt @@ -45,7 +45,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.times import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever @SmallTest @@ -92,7 +92,7 @@ class GradientColorWallpaperTest : SysuiTestCase() { engine.onSurfaceRedrawNeeded(surfaceHolder) - verifyZeroInteractions(canvas) + verifyNoMoreInteractions(canvas) } @Test diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt index 663a85330f70..338e4bec7aa2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import com.android.app.displaylib.DisplayRepository.PendingDisplay import org.mockito.Mockito.`when` as whenever /** Creates a mock display. */ @@ -41,8 +42,8 @@ fun display(type: Int, flags: Int = 0, id: Int = 0, state: Int? = null): Display } /** Creates a mock [DisplayRepository.PendingDisplay]. */ -fun createPendingDisplay(id: Int = 0): DisplayRepository.PendingDisplay = - mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) } +fun createPendingDisplay(id: Int = 0): PendingDisplay = + mock<PendingDisplay> { whenever(this.id).thenReturn(id) } @SysUISingleton /** Fake [DisplayRepository] implementation for testing. */ @@ -50,7 +51,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository { private val flow = MutableStateFlow<Set<Display>>(emptySet()) private val displayIdFlow = MutableStateFlow<Set<Int>>(emptySet()) private val pendingDisplayFlow = - MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1) + MutableSharedFlow<PendingDisplay?>(replay = 1) private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0) private val displayRemovalEventFlow = MutableSharedFlow<Int>(replay = 0) private val displayIdsWithSystemDecorationsFlow = MutableStateFlow<Set<Int>>(emptySet()) @@ -101,7 +102,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository { suspend fun emit(value: Set<Display>) = flow.emit(value) /** Emits [value] as [pendingDisplay] flow value. */ - suspend fun emit(value: DisplayRepository.PendingDisplay?) = pendingDisplayFlow.emit(value) + suspend fun emit(value: PendingDisplay?) = pendingDisplayFlow.emit(value) override val displays: StateFlow<Set<Display>> get() = flow @@ -109,7 +110,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository { override val displayIds: StateFlow<Set<Int>> get() = displayIdFlow - override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?> + override val pendingDisplay: Flow<PendingDisplay?> get() = pendingDisplayFlow private val _defaultDisplayOff: MutableStateFlow<Boolean> = MutableStateFlow(false) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt index aa23aa30b7bc..5ab3b3de49f4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt @@ -66,6 +66,7 @@ class FakePerDisplayInstanceProviderWithTeardown : val Kosmos.fakePerDisplayInstanceProviderWithTeardown by Kosmos.Fixture { FakePerDisplayInstanceProviderWithTeardown() } +val Kosmos.perDisplayDumpHelper by Kosmos.Fixture { PerDisplayRepoDumpHelper(dumpManager) } val Kosmos.fakePerDisplayInstanceRepository by Kosmos.Fixture { PerDisplayInstanceRepositoryImpl( @@ -73,6 +74,6 @@ val Kosmos.fakePerDisplayInstanceRepository by instanceProvider = fakePerDisplayInstanceProviderWithTeardown, testScope.backgroundScope, displayRepository, - dumpManager, + perDisplayDumpHelper, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepositoryKosmos.kt index 3cec5a9cd822..361d21dc134d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepositoryKosmos.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 The Android Open Source Project + * 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. @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.android.systemui.keyguard.domain.interactor +package com.android.systemui.keyguard.data.repository import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testScope -val Kosmos.keyguardServiceShowLockscreenInteractor by - Kosmos.Fixture { KeyguardServiceShowLockscreenInteractor(backgroundScope = testScope) } +val Kosmos.keyguardServiceShowLockscreenRepository by + Kosmos.Fixture { KeyguardServiceShowLockscreenRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractorKosmos.kt new file mode 100644 index 000000000000..447aa1255463 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractorKosmos.kt @@ -0,0 +1,35 @@ +/* + * 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.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.keyguardServiceShowLockscreenRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.settings.userTracker +import com.android.systemui.user.domain.interactor.selectedUserInteractor + +val Kosmos.keyguardServiceShowLockscreenInteractor by + Kosmos.Fixture { + KeyguardServiceShowLockscreenInteractor( + backgroundScope = testScope, + selectedUserInteractor = selectedUserInteractor, + repository = keyguardServiceShowLockscreenRepository, + userTracker = userTracker, + wmLockscreenVisibilityInteractor = { windowManagerLockscreenVisibilityInteractor }, + keyguardEnabledInteractor = keyguardEnabledInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt index f7caeb69f17e..dd868e767d9b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt @@ -18,10 +18,12 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope val Kosmos.keyguardShowWhileAwakeInteractor by Kosmos.Fixture { KeyguardShowWhileAwakeInteractor( + backgroundScope = testScope, biometricSettingsRepository = biometricSettingsRepository, keyguardEnabledInteractor = keyguardEnabledInteractor, keyguardServiceShowLockscreenInteractor = keyguardServiceShowLockscreenInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt index 43fa718cff9a..c89fb705f417 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt @@ -27,7 +27,7 @@ import com.android.systemui.user.domain.interactor.selectedUserInteractor import com.android.systemui.util.settings.fakeSettings import com.android.systemui.util.time.systemClock -val Kosmos.keyguardWakeDirectlyToGoneInteractor by +val Kosmos.keyguardWakeDirectlyToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor by Kosmos.Fixture { KeyguardWakeDirectlyToGoneInteractor( applicationCoroutineScope, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt index 11bd4c7b7940..54261c7f622b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt @@ -38,7 +38,9 @@ val Kosmos.sysUiStateFactory by Fixture { } } -val Kosmos.fakeSysUIStatePerDisplayRepository by Fixture { FakePerDisplayRepository<SysUiState>() } +val Kosmos.fakeSysUIStatePerDisplayRepository by Fixture { + FakePerDisplayRepository<SysUiState>().apply { add(Display.DEFAULT_DISPLAY, sysUiState) } +} val Kosmos.sysuiStateInteractor by Fixture { SysUIStateDisplaysInteractor(fakeSysUIStatePerDisplayRepository, displayRepository) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java index 4efcada96a14..215df9d59ec9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java @@ -58,7 +58,7 @@ public class GroupEntryBuilder { return this; } - /** Sets the creation time. */ + /** Sets the creation time. Should be SystemClock.elapsedRealtime */ public GroupEntryBuilder setCreationTime(long creationTime) { mCreationTime = creationTime; return this; diff --git a/packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractorKosmos.kt index 626a68f6a59d..38b59945ab7d 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractorKosmos.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,11 +14,8 @@ * limitations under the License. */ -package com.android.systemui.display.data +package com.android.systemui.statusbar.notification.promoted.domain.interactor -sealed interface DisplayEvent { - val displayId: Int - data class Added(override val displayId: Int) : DisplayEvent - data class Removed(override val displayId: Int) : DisplayEvent - data class Changed(override val displayId: Int) : DisplayEvent -} +import com.android.systemui.kosmos.Kosmos + +val Kosmos.packageDemotionInteractor by Kosmos.Fixture { PackageDemotionInteractor() } diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING index 1148539187ac..083d2aa15316 100644 --- a/ravenwood/TEST_MAPPING +++ b/ravenwood/TEST_MAPPING @@ -180,9 +180,11 @@ // AUTO-GENERATED-END ], "ravenwood-postsubmit": [ - { - "name": "SystemUiRavenTests", - "host": true - } + // We haven't maintained SystemUiRavenTests, and as a result, it's been demoted already. + // Disable it until we fix the issues: b/319647875 + // { + // "name": "SystemUiRavenTests", + // "host": true + // } ] } diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java index d935626c34df..d8741975c71a 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -23,7 +23,9 @@ import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACK import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK; +import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP; +import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath; import static com.android.ravenwood.common.RavenwoodCommonUtils.parseNullableInt; import static com.android.ravenwood.common.RavenwoodCommonUtils.withDefault; @@ -271,6 +273,13 @@ public class RavenwoodRuntimeEnvironmentController { dumpJavaProperties(); dumpOtherInfo(); + System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1"); + var runtimePath = getRavenwoodRuntimePath(); + System.setProperty(RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP, runtimePath); + + Log.i(TAG, "PWD=" + System.getProperty("user.dir")); + Log.i(TAG, "RuntimePath=" + runtimePath); + // Make sure libravenwood_runtime is loaded. System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME)); @@ -314,7 +323,6 @@ public class RavenwoodRuntimeEnvironmentController { Typeface.loadPreinstalledSystemFontMap(); Typeface.loadNativeSystemFonts(); - System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1"); // This will let AndroidJUnit4 use the original runner. System.setProperty("android.junit.runner", "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner"); diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java index 893b354d4645..e1b537e11842 100644 --- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java +++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java @@ -68,6 +68,8 @@ public class RavenwoodCommonUtils { RAVENWOOD_RUNTIME_PATH + "ravenwood-data/ravenwood-empty-res.apk"; public static final String RAVENWOOD_VERSION_JAVA_SYSPROP = "android.ravenwood.version"; + public static final String RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP = + "android.ravenwood.runtime_path"; /** * @return if we're running on Ravenwood. diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt index fd6d6fb66465..b3af8753ee83 100644 --- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt +++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt @@ -25,7 +25,6 @@ import com.android.internal.os.Flags import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Assert.fail -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -40,7 +39,6 @@ class RavenwoodAconfigSimpleReadTests { } @Test - @Ignore // TODO: Enable this test after rolling out the "2" flags. fun testTrueFlags() { assertTrue(Flags.ravenwoodFlagRo2()) assertTrue(Flags.ravenwoodFlagRw2()) @@ -67,14 +65,12 @@ class RavenwoodAconfigCheckFlagsRuleTests { @Test @RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RO_2) - @Ignore // TODO: Enable this test after rolling out the "2" flags. fun testRequireFlagsDisabledRo() { fail("This test shouldn't be executed") } @Test @RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RW_2) - @Ignore // TODO: Enable this test after rolling out the "2" flags. fun testRequireFlagsDisabledRw() { fail("This test shouldn't be executed") } diff --git a/ravenwood/tests/resapk_test/Android.bp b/ravenwood/tests/resapk_test/Android.bp index c14576550f78..960b3ed0013a 100644 --- a/ravenwood/tests/resapk_test/Android.bp +++ b/ravenwood/tests/resapk_test/Android.bp @@ -10,7 +10,7 @@ package { android_ravenwood_test { name: "RavenwoodResApkTest", - resource_apk: "RavenwoodResApkTest-apk", + resource_apk: "RavenwoodResApkTest-res", libs: [ // Normally, tests shouldn't directly access it, but we need to access RavenwoodCommonUtils @@ -24,6 +24,7 @@ android_ravenwood_test { ], srcs: [ "test/**/*.java", + ":RavenwoodResApkTest-res{.aapt.srcjar}", ], sdk_version: "test_current", auto_gen_config: true, diff --git a/ravenwood/tests/resapk_test/apk/Android.bp b/ravenwood/tests/resapk_test/apk/Android.bp index 10ed5e2f8410..fd8976df4316 100644 --- a/ravenwood/tests/resapk_test/apk/Android.bp +++ b/ravenwood/tests/resapk_test/apk/Android.bp @@ -8,7 +8,13 @@ package { } android_app { - name: "RavenwoodResApkTest-apk", + name: "RavenwoodResApkTest-res", sdk_version: "current", + + use_resource_processor: false, + + flags_packages: [ + "com.android.internal.os.flags-aconfig", + ], } diff --git a/ravenwood/tests/resapk_test/apk/res/layout/testlayout.xml b/ravenwood/tests/resapk_test/apk/res/layout/testlayout.xml new file mode 100644 index 000000000000..17cdb868fc6b --- /dev/null +++ b/ravenwood/tests/resapk_test/apk/res/layout/testlayout.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"> + <View android:id="@+id/view1" text="no-flags" /> + <View android:id="@+id/view2" text="ro-enabled" android:featureFlag="com.android.internal.os.ravenwood_flag_ro_2"/> + <View android:id="@+id/view3" text="ro-disabled" android:featureFlag="com.android.internal.os.ravenwood_flag_ro_1"/> + <View android:id="@+id/view2" text="rw-enabled" android:featureFlag="com.android.internal.os.ravenwood_flag_rw_2"/> + <View android:id="@+id/view3" text="rw-disabled" android:featureFlag="com.android.internal.os.ravenwood_flag_rw_1"/> +</LinearLayout> diff --git a/ravenwood/tests/resapk_test/apk/res/values/strings.xml b/ravenwood/tests/resapk_test/apk/res/values/strings.xml index 23d4c0f21007..5abf7475caa7 100644 --- a/ravenwood/tests/resapk_test/apk/res/values/strings.xml +++ b/ravenwood/tests/resapk_test/apk/res/values/strings.xml @@ -13,7 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Test string 1 --> <string name="test_string_1" translatable="false" >Test String 1</string> + <!-- values can only use readonly flags --> + <string name="test_string_enabled" translatable="false" android:featureFlag="com.android.internal.os.ravenwood_flag_ro_2">Enabled</string> + <string name="test_string_disabled" translatable="false" android:featureFlag="com.android.internal.os.ravenwood_flag_ro_1">Disabled</string> </resources> diff --git a/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java b/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java index e547114bbe40..89f8d40da7d4 100644 --- a/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java +++ b/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java @@ -16,23 +16,41 @@ package com.android.ravenwoodtest.resapk_test; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; + +import android.content.Context; +import android.content.res.XmlResourceParser; +import android.platform.test.annotations.DisabledOnRavenwood; +import android.util.Log; + import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.ravenwood.common.RavenwoodCommonUtils; +import com.android.ravenwood.restest_apk.R; import org.junit.Test; import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; import java.io.File; +import java.util.ArrayList; +import java.util.List; @RunWith(AndroidJUnit4.class) public class RavenwoodResApkTest { + private static final String TAG = "RavenwoodResApkTest"; + + private static final Context sContext = + InstrumentationRegistry.getInstrumentation().getContext(); + /** * Ensure the file "ravenwood-res.apk" exists. - * TODO Check the content of it, once Ravenwood supports resources. The file should - * be a copy of RavenwoodResApkTest-apk.apk */ @Test public void testResApkExists() { @@ -48,4 +66,73 @@ public class RavenwoodResApkTest { assertTrue(new File( RavenwoodCommonUtils.getRavenwoodRuntimePath() + "/" + file).exists()); } + + @Test + public void testReadStringNoFlag() { + assertThat(sContext.getString(R.string.test_string_1)).isEqualTo("Test String 1"); + } + + @Test + public void testReadStringRoFlagEnabled() { + assertThat(sContext.getString(R.string.test_string_enabled)).isEqualTo("Enabled"); + } + + @Test + public void testReadStringRoFlagDisabled() { + assertThrows(android.content.res.Resources.NotFoundException.class, () -> { + sContext.getString(R.string.test_string_disabled); + }); + } + + /** + * Look into the layout and collect the "text" attribute. + * + * It _should_ respect android:featureFlag, but until b/396458006 gets fixed, this returns + * even disabled elements. + */ + private List<String> getTextsFromEnabledChildren() throws Exception { + try (XmlResourceParser parser = sContext.getResources().getLayout(R.layout.testlayout)) { + assertNotNull(parser); + + var ret = new ArrayList<String>(); + + while (parser.next() != XmlPullParser.END_DOCUMENT) { + var text = parser.getAttributeValue(null, "text"); + if (text == null) { + continue; + } + + Log.d(TAG, "Found tag: " + parser.getName() + " text='" + text + "'"); + ret.add(text); + } + return ret; + } + } + + @Test + public void testElementNoFlag() throws Exception { + assertThat(getTextsFromEnabledChildren()).contains("no-flags"); + } + + @Test + public void testElementWithRoFlagEnabled() throws Exception { + assertThat(getTextsFromEnabledChildren()).contains("ro-enabled"); + } + + @Test + public void testElementWithRoFlagDisabled() throws Exception { + assertThat(getTextsFromEnabledChildren()).doesNotContain("ro-disabled"); + } + + @Test + public void testElementWithRwFlagEnabled() throws Exception { + assertThat(getTextsFromEnabledChildren()).contains("rw-enabled"); + } + + @Test + @DisabledOnRavenwood(bug = 396458006, + reason = "RW flags in XML are all handled as enabled for now") + public void testElementWithRwFlagDisabled() throws Exception { + assertThat(getTextsFromEnabledChildren()).doesNotContain("rw-disabled"); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 6c26c1f74002..703e37fad5ad 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -531,7 +531,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilitySecurityPolicy securityPolicy, SystemActionPerformer systemActionPerformer, AccessibilityWindowManager a11yWindowManager, - AccessibilityDisplayListener a11yDisplayListener, + AccessibilityDisplayListener.DisplayManagerWrapper displayManagerWrapper, MagnificationController magnificationController, @Nullable AccessibilityInputFilter inputFilter, ProxyManager proxyManager, @@ -550,7 +550,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mSecurityPolicy = securityPolicy; mSystemActionPerformer = systemActionPerformer; mA11yWindowManager = a11yWindowManager; - mA11yDisplayListener = a11yDisplayListener; + mA11yDisplayListener = new AccessibilityDisplayListener(displayManagerWrapper, + new MainHandler(Looper.getMainLooper())); mMagnificationController = magnificationController; mMagnificationProcessor = new MagnificationProcessor(mMagnificationController); mCaptioningManagerImpl = new CaptioningManagerImpl(mContext); @@ -596,7 +597,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub this, LocalServices.getService(PackageManagerInternal.class)); mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler, mWindowManagerService, this, mSecurityPolicy, this, mTraceManager); - mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); + mA11yDisplayListener = new AccessibilityDisplayListener( + new AccessibilityDisplayListener.DisplayManagerWrapper(mContext), mMainHandler); mMagnificationController = new MagnificationController( this, mLock, @@ -5457,11 +5459,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * A Utility class to handle display state. */ public class AccessibilityDisplayListener implements DisplayManager.DisplayListener { - private final DisplayManager mDisplayManager; + private final DisplayManagerWrapper mDisplayManager; private final ArrayList<Display> mDisplaysList = new ArrayList<>(); private int mSystemUiUid = 0; - AccessibilityDisplayListener(Context context, Handler handler) { + AccessibilityDisplayListener(DisplayManagerWrapper displayManager, Handler handler) { // Avoid concerns about one thread adding displays while another thread removes // them by ensuring the looper is the main looper and the DisplayListener // callbacks are always executed on the one main thread. @@ -5474,7 +5476,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub Slog.e(LOG_TAG, errorMessage); } - mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + mDisplayManager = displayManager; mDisplayManager.registerDisplayListener(this, handler); initializeDisplayList(); @@ -5626,6 +5628,34 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } return true; } + + /** Wrapper of DisplayManager for testing. */ + @VisibleForTesting + static class DisplayManagerWrapper { + private final DisplayManager mDm; + + DisplayManagerWrapper(Context context) { + mDm = context.getSystemService(DisplayManager.class); + } + + /** + * @see DisplayManager#registerDisplayListener(DisplayManager.DisplayListener, Handler) + */ + public void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, + @Nullable Handler handler) { + mDm.registerDisplayListener(listener, handler); + } + + /** @see DisplayManager#getDisplays() */ + public Display[] getDisplays() { + return mDm.getDisplays(); + } + + /** @see DisplayManager#getDisplay(int) */ + public Display getDisplay(int displayId) { + return mDm.getDisplay(displayId); + } + } } /** Represents an {@link AccessibilityManager} */ diff --git a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java index 805d7f820c8d..94cef418b6c8 100644 --- a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java @@ -165,6 +165,9 @@ public class HearingDevicePhoneCallNotificationController { 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(); diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java index 23166a800245..84158cf911ad 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java @@ -24,6 +24,7 @@ import static android.view.accessibility.AccessibilityManager.AUTOCLICK_IGNORE_M import static android.view.accessibility.AccessibilityManager.AUTOCLICK_REVERT_TO_LEFT_CLICK_DEFAULT; import static com.android.server.accessibility.autoclick.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME; +import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_DOUBLE_CLICK; import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK; import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK; import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL; @@ -45,6 +46,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; +import android.view.ViewConfiguration; import android.view.WindowManager; import androidx.annotation.VisibleForTesting; @@ -799,7 +801,7 @@ public class AutoclickController extends BaseEventStreamTransformation { final long now = SystemClock.uptimeMillis(); - int actionButton; + int actionButton = BUTTON_PRIMARY; if (mHoveredState) { // Always triggers left-click when the cursor hovers over the autoclick type // panel, to always allow users to change a different click type. Otherwise, if @@ -807,15 +809,32 @@ public class AutoclickController extends BaseEventStreamTransformation { // select other click types. actionButton = BUTTON_PRIMARY; } else { - actionButton = mActiveClickType == AUTOCLICK_TYPE_RIGHT_CLICK - ? BUTTON_SECONDARY - : BUTTON_PRIMARY; + switch (mActiveClickType) { + case AUTOCLICK_TYPE_LEFT_CLICK: + actionButton = BUTTON_PRIMARY; + break; + case AUTOCLICK_TYPE_RIGHT_CLICK: + actionButton = BUTTON_SECONDARY; + break; + case AUTOCLICK_TYPE_DOUBLE_CLICK: + actionButton = BUTTON_PRIMARY; + long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime(); + sendMotionEvent(actionButton, now); + sendMotionEvent(actionButton, now + doubleTapMinimumTimeout); + return; + default: + break; + } } + sendMotionEvent(actionButton, now); + } + + private void sendMotionEvent(int actionButton, long eventTime) { MotionEvent downEvent = MotionEvent.obtain( - /* downTime= */ now, - /* eventTime= */ now, + /* downTime= */ eventTime, + /* eventTime= */ eventTime, MotionEvent.ACTION_DOWN, /* pointerCount= */ 1, mTempPointerProperties, diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java index 88b791046c87..6e098d014ee3 100644 --- a/services/companion/java/com/android/server/companion/virtual/SensorController.java +++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java @@ -21,15 +21,18 @@ import android.annotation.Nullable; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.sensor.IVirtualSensorCallback; import android.companion.virtual.sensor.VirtualSensor; +import android.companion.virtual.sensor.VirtualSensorAdditionalInfo; import android.companion.virtual.sensor.VirtualSensorConfig; import android.companion.virtual.sensor.VirtualSensorEvent; import android.content.AttributionSource; +import android.hardware.SensorAdditionalInfo; import android.hardware.SensorDirectChannel; import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.SharedMemory; +import android.os.SystemClock; import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; @@ -140,7 +143,7 @@ public class SensorController { final IBinder sensorToken = new Binder("android.hardware.sensor.VirtualSensor:" + config.getName()); VirtualSensor sensor = new VirtualSensor(handle, config.getType(), config.getName(), - virtualDevice, sensorToken); + config.getFlags(), virtualDevice, sensorToken); synchronized (mLock) { mSensorDescriptors.put(sensorToken, sensorDescriptor); mVirtualSensors.put(handle, sensor); @@ -164,6 +167,37 @@ public class SensorController { } } + boolean sendSensorAdditionalInfo(@NonNull IBinder token, + @NonNull VirtualSensorAdditionalInfo info) { + Objects.requireNonNull(token); + Objects.requireNonNull(info); + synchronized (mLock) { + final SensorDescriptor sensorDescriptor = mSensorDescriptors.get(token); + long timestamp = SystemClock.elapsedRealtimeNanos(); + if (sensorDescriptor == null) { + throw new IllegalArgumentException("Could not send sensor event for given token"); + } + if (!mSensorManagerInternal.sendSensorAdditionalInfo( + sensorDescriptor.getHandle(), SensorAdditionalInfo.TYPE_FRAME_BEGIN, + /* serial= */ 0, timestamp++, /* values= */ null)) { + return false; + } + for (int i = 0; i < info.getValues().size(); ++i) { + if (!mSensorManagerInternal.sendSensorAdditionalInfo( + sensorDescriptor.getHandle(), info.getType(), /* serial= */ i, + timestamp++, info.getValues().get(i))) { + return false; + } + } + if (!mSensorManagerInternal.sendSensorAdditionalInfo( + sensorDescriptor.getHandle(), SensorAdditionalInfo.TYPE_FRAME_END, + /* serial= */ 0, timestamp, /* values= */ null)) { + return false; + } + } + return true; + } + @Nullable VirtualSensor getSensorByHandle(int handle) { synchronized (mLock) { diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 28efdfc01ee2..0023b6d53837 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -55,6 +55,7 @@ import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; import android.companion.virtual.camera.VirtualCameraConfig; import android.companion.virtual.sensor.VirtualSensor; +import android.companion.virtual.sensor.VirtualSensorAdditionalInfo; import android.companion.virtual.sensor.VirtualSensorEvent; import android.companion.virtualdevice.flags.Flags; import android.compat.annotation.ChangeId; @@ -1294,6 +1295,18 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } @Override // Binder call + public boolean sendSensorAdditionalInfo(@NonNull IBinder token, + @NonNull VirtualSensorAdditionalInfo info) { + checkCallerIsDeviceOwner(); + final long ident = Binder.clearCallingIdentity(); + try { + return mSensorController.sendSensorAdditionalInfo(token, info); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call public void registerIntentInterceptor(IVirtualDeviceIntentInterceptor intentInterceptor, IntentFilter filter) { checkCallerIsDeviceOwner(); diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java index 7eb7072520de..a1d39faa7039 100644 --- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java @@ -313,7 +313,8 @@ public class ContextualSearchManagerService extends SystemService { android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS }) - private Intent getContextualSearchIntent(int entrypoint, int userId, CallbackToken mToken) { + private Intent getContextualSearchIntent(int entrypoint, int userId, String callingPackage, + CallbackToken mToken) { final Intent launchIntent = getResolvedLaunchIntent(userId); if (launchIntent == null) { if (DEBUG) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null"); @@ -332,6 +333,9 @@ public class ContextualSearchManagerService extends SystemService { launchIntent.putExtra(ContextualSearchManager.EXTRA_IS_AUDIO_PLAYING, mAudioManager.isMusicActive()); } + if (Flags.selfInvocation()) { + launchIntent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage); + } boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed(); final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities(); final List<IBinder> activityTokens = new ArrayList<>(records.size()); @@ -500,6 +504,7 @@ public class ContextualSearchManagerService extends SystemService { } private void startContextualSearchInternal(int entrypoint) { + final String callingPackage = mPackageManager.getNameForUid(Binder.getCallingUid()); final int callingUserId = Binder.getCallingUserHandle().getIdentifier(); mAssistDataRequester.cancel(); // Creates a new CallbackToken at mToken and an expiration handler. @@ -508,7 +513,8 @@ public class ContextualSearchManagerService extends SystemService { // server has READ_FRAME_BUFFER permission to get the screenshot and because only // the system server can invoke non-exported activities. Binder.withCleanCallingIdentity(() -> { - Intent launchIntent = getContextualSearchIntent(entrypoint, callingUserId, mToken); + Intent launchIntent = getContextualSearchIntent(entrypoint, callingUserId, + callingPackage, mToken); if (launchIntent != null) { int result = invokeContextualSearchIntent(launchIntent, callingUserId); if (DEBUG) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 76ba0054583b..ce5a12179d44 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2004,7 +2004,7 @@ public class ActivityManagerService extends IActivityManager.Stub new IAppOpsCallback.Stub() { @Override public void opChanged(int op, int uid, String packageName, String persistentDeviceId) { - if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) { + if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && uid >= 0) { if (getAppOpsManager().checkOpNoThrow(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) { runInBackgroundDisabled(uid); diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java index a47beae1d9cb..800e2b2d9657 100644 --- a/services/core/java/com/android/server/am/AppPermissionTracker.java +++ b/services/core/java/com/android/server/am/AppPermissionTracker.java @@ -394,6 +394,7 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy private class MyAppOpsCallback extends IAppOpsCallback.Stub { @Override public void opChanged(int op, int uid, String packageName, String persistentDeviceId) { + if (uid < 0) return; mHandler.obtainMessage(MyHandler.MSG_APPOPS_CHANGED, op, uid, packageName) .sendToTarget(); } diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 81d34f67dee9..fe4fa1068bb3 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -41,6 +41,7 @@ import dalvik.annotation.optimization.NeverCompile; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.TimeUnit; /** * Tunable parameters for broadcast dispatch policy @@ -286,6 +287,17 @@ public class BroadcastConstants { "max_frozen_outgoing_broadcasts"; private static final int DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS = 32; + /** + * For {@link BroadcastQueueImpl}: Indicates how long after a process start was initiated, + * it should be considered abandoned and discarded. + */ + public long PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS = + DEFAULT_PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS * Build.HW_TIMEOUT_MULTIPLIER; + private static final String KEY_PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS = + "pending_cold_start_abandon_timeout_millis"; + private static final long DEFAULT_PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS = + TimeUnit.MINUTES.toMillis(5); + // Settings override tracking for this instance private String mSettingsKey; private SettingsObserver mSettingsObserver; @@ -434,6 +446,10 @@ public class BroadcastConstants { MAX_FROZEN_OUTGOING_BROADCASTS = getDeviceConfigInt( KEY_MAX_FROZEN_OUTGOING_BROADCASTS, DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS); + PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS = getDeviceConfigLong( + KEY_PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS, + DEFAULT_PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS) + * Build.HW_TIMEOUT_MULTIPLIER; } // TODO: migrate BroadcastRecord to accept a BroadcastConstants @@ -491,6 +507,8 @@ public class BroadcastConstants { PENDING_COLD_START_CHECK_INTERVAL_MILLIS).println(); pw.print(KEY_MAX_FROZEN_OUTGOING_BROADCASTS, MAX_FROZEN_OUTGOING_BROADCASTS).println(); + pw.print(KEY_PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS, + PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS).println(); pw.decreaseIndent(); pw.println(); } diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 508c01802156..c0fe73877c01 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -245,6 +245,24 @@ class BroadcastProcessQueue { */ private final ArrayList<BroadcastRecord> mOutgoingBroadcasts = new ArrayList<>(); + /** + * The timestamp, in {@link SystemClock#uptimeMillis()}, at which a cold start was initiated + * for the process associated with this queue. + * + * Note: We could use the already existing {@link ProcessRecord#getStartUptime()} instead + * of this, but the need for this timestamp is to identify an issue (b/393898613) where the + * suspicion is that process is not attached or getting changed. So, we don't want to rely on + * ProcessRecord directly for this purpose. + */ + private long mProcessStartInitiatedTimestampMillis; + + /** + * Indicates whether the number of current receivers has been incremented using + * {@link ProcessReceiverRecord#incrementCurReceivers()}. This allows to skip decrementing + * the receivers when it is not required. + */ + private boolean mCurReceiversIncremented; + public BroadcastProcessQueue(@NonNull BroadcastConstants constants, @NonNull String processName, int uid) { this.constants = Objects.requireNonNull(constants); @@ -652,6 +670,52 @@ class BroadcastProcessQueue { return mActiveFirstLaunch; } + public void incrementCurAppReceivers() { + app.mReceivers.incrementCurReceivers(); + mCurReceiversIncremented = true; + } + + public void decrementCurAppReceivers() { + if (mCurReceiversIncremented) { + app.mReceivers.decrementCurReceivers(); + mCurReceiversIncremented = false; + } + } + + public void setProcessStartInitiatedTimestampMillis(@UptimeMillisLong long timestampMillis) { + mProcessStartInitiatedTimestampMillis = timestampMillis; + } + + @UptimeMillisLong + public long getProcessStartInitiatedTimestampMillis() { + return mProcessStartInitiatedTimestampMillis; + } + + public boolean hasProcessStartInitiationTimedout() { + if (mProcessStartInitiatedTimestampMillis <= 0) { + return false; + } + return (SystemClock.uptimeMillis() - mProcessStartInitiatedTimestampMillis) + > constants.PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS; + } + + /** + * Returns if the process start initiation is expected to be timed out at this point. This + * allows us to dump necessary state for debugging before the process start is timed out + * and discarded. + */ + public boolean isProcessStartInitiationTimeoutExpected() { + if (mProcessStartInitiatedTimestampMillis <= 0) { + return false; + } + return (SystemClock.uptimeMillis() - mProcessStartInitiatedTimestampMillis) + > constants.PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS / 2; + } + + public void clearProcessStartInitiatedTimestampMillis() { + mProcessStartInitiatedTimestampMillis = 0; + } + /** * Get package name of the first application loaded into this process. */ @@ -1558,6 +1622,10 @@ class BroadcastProcessQueue { if (mActiveReEnqueued) { pw.print("activeReEnqueued:"); pw.println(mActiveReEnqueued); } + if (mProcessStartInitiatedTimestampMillis > 0) { + pw.print("processStartInitiatedTimestamp:"); pw.println( + TimeUtils.formatUptime(mProcessStartInitiatedTimestampMillis)); + } } @NeverCompile diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index d276b9a94791..6e893ad0a425 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -534,6 +534,7 @@ class BroadcastQueueImpl extends BroadcastQueue { // skip to look for another warm process if (mRunningColdStart == null) { mRunningColdStart = queue; + mRunningColdStart.clearProcessStartInitiatedTimestampMillis(); } else if (isPendingColdStartValid()) { // Move to considering next runnable queue queue = nextQueue; @@ -542,6 +543,7 @@ class BroadcastQueueImpl extends BroadcastQueue { // Pending cold start is not valid, so clear it and move on. clearInvalidPendingColdStart(); mRunningColdStart = queue; + mRunningColdStart.clearProcessStartInitiatedTimestampMillis(); } } @@ -588,7 +590,9 @@ class BroadcastQueueImpl extends BroadcastQueue { @GuardedBy("mService") private boolean isPendingColdStartValid() { - if (mRunningColdStart.app.getPid() > 0) { + if (mRunningColdStart.hasProcessStartInitiationTimedout()) { + return false; + } else if (mRunningColdStart.app.getPid() > 0) { // If the process has already started, check if it wasn't killed. return !mRunningColdStart.app.isKilled(); } else { @@ -673,6 +677,7 @@ class BroadcastQueueImpl extends BroadcastQueue { if ((mRunningColdStart != null) && (mRunningColdStart == queue)) { // We've been waiting for this app to cold start, and it's ready // now; dispatch its next broadcast and clear the slot + mRunningColdStart.clearProcessStartInitiatedTimestampMillis(); mRunningColdStart = null; // Now that we're running warm, we can finally request that OOM @@ -756,6 +761,7 @@ class BroadcastQueueImpl extends BroadcastQueue { // We've been waiting for this app to cold start, and it had // trouble; clear the slot and fail delivery below + mRunningColdStart.clearProcessStartInitiatedTimestampMillis(); mRunningColdStart = null; // We might be willing to kick off another cold start @@ -1036,6 +1042,7 @@ class BroadcastQueueImpl extends BroadcastQueue { "startProcessLocked failed"); return true; } + queue.setProcessStartInitiatedTimestampMillis(SystemClock.uptimeMillis()); // TODO: b/335420031 - cache receiver intent to avoid multiple calls to getReceiverIntent. mService.mProcessList.getAppStartInfoTracker().handleProcessBroadcastStart( startTimeNs, queue.app, r.getReceiverIntent(receiver), r.alarm /* isAlarm */); @@ -1991,6 +1998,32 @@ class BroadcastQueueImpl extends BroadcastQueue { if (mRunningColdStart != null) { checkState(getRunningIndexOf(mRunningColdStart) >= 0, "isOrphaned " + mRunningColdStart); + + final BroadcastProcessQueue queue = getProcessQueue(mRunningColdStart.processName, + mRunningColdStart.uid); + checkState(queue == mRunningColdStart, "Conflicting " + mRunningColdStart + + " with queue " + queue + + ";\n mRunningColdStart.app: " + mRunningColdStart.app.toDetailedString() + + ";\n queue.app: " + queue.app.toDetailedString()); + + checkState(mRunningColdStart.app != null, "Empty cold start queue " + + mRunningColdStart); + + if (mRunningColdStart.isProcessStartInitiationTimeoutExpected()) { + final StringBuilder sb = new StringBuilder(); + sb.append("Process start timeout expected for app "); + sb.append(mRunningColdStart.app); + sb.append(" in queue "); + sb.append(mRunningColdStart); + sb.append("; startUpTime: "); + final long startupTimeMs = + mRunningColdStart.getProcessStartInitiatedTimestampMillis(); + sb.append(startupTimeMs == 0 ? "<none>" + : TimeUtils.formatDuration(startupTimeMs - SystemClock.uptimeMillis())); + sb.append(";\n app: "); + sb.append(mRunningColdStart.app.toDetailedString()); + checkState(false, sb.toString()); + } } // Verify health of all known process queues @@ -2090,7 +2123,7 @@ class BroadcastQueueImpl extends BroadcastQueue { @GuardedBy("mService") private void notifyStartedRunning(@NonNull BroadcastProcessQueue queue) { if (queue.app != null) { - queue.app.mReceivers.incrementCurReceivers(); + queue.incrementCurAppReceivers(); // Don't bump its LRU position if it's in the background restricted. if (mService.mInternal.getRestrictionLevel( @@ -2115,7 +2148,7 @@ class BroadcastQueueImpl extends BroadcastQueue { @GuardedBy("mService") private void notifyStoppedRunning(@NonNull BroadcastProcessQueue queue) { if (queue.app != null) { - queue.app.mReceivers.decrementCurReceivers(); + queue.decrementCurAppReceivers(); if (queue.runningOomAdjusted) { mService.enqueueOomAdjTargetLocked(queue.app); diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS index 4b6d6bc955cc..fd2d8352eb4f 100644 --- a/services/core/java/com/android/server/am/OWNERS +++ b/services/core/java/com/android/server/am/OWNERS @@ -61,7 +61,7 @@ per-file *Oom* = file:/OOM_ADJUSTER_OWNERS per-file ProcessStateController.java = file:/OOM_ADJUSTER_OWNERS # Miscellaneous -per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com, dzshen@google.com, zhidou@google.com, tedbauer@google.com +per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com, dzshen@google.com, zhidou@google.com per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNERS # Activity Security diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index eea667ef2f39..400c699bf93f 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -70,6 +70,7 @@ import com.android.server.wm.WindowProcessController; import com.android.server.wm.WindowProcessListener; import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; @@ -1414,6 +1415,16 @@ class ProcessRecord implements WindowProcessListener { return mStringName = sb.toString(); } + String toDetailedString() { + final StringBuilder sb = new StringBuilder(); + sb.append(this); + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw); + dump(pw, " "); + sb.append(sw); + return sb.toString(); + } + /* * Return true if package has been added false if not */ diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index cfd22fbdeece..cb4342f27bc8 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -253,11 +253,12 @@ public class SettingsToPropertiesMapper { "pixel_state_server", "pixel_system_sw_video", "pixel_video_sw", + "pixel_vpn", "pixel_watch", + "pixel_watch_debug_trace", "pixel_wifi", "platform_compat", "platform_security", - "pixel_watch_debug_trace", "pmw", "power", "preload_safety", diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index fb33cb1ff8c0..286c4cd709b1 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -822,7 +822,8 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void onOpModeChanged(int op, int uid, String packageName, String persistentDeviceId) throws RemoteException { - mCallback.opChanged(op, uid, packageName, persistentDeviceId); + mCallback.opChanged(op, uid, packageName != null ? packageName : "", + Objects.requireNonNull(persistentDeviceId)); } } @@ -1059,7 +1060,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (Flags.enableAllSqliteAppopsAccesses()) { mHistoricalRegistry = new HistoricalRegistrySql(context); } else { - mHistoricalRegistry = new HistoricalRegistry(this, context); + mHistoricalRegistry = new LegacyHistoricalRegistry(this, context); } } @@ -7011,7 +7012,8 @@ public class AppOpsService extends IAppOpsService.Stub { mHistoricalRegistry = new HistoricalRegistrySql( (HistoricalRegistrySql) mHistoricalRegistry); } else { - mHistoricalRegistry = new HistoricalRegistry((HistoricalRegistry) mHistoricalRegistry); + mHistoricalRegistry = new LegacyHistoricalRegistry( + (LegacyHistoricalRegistry) mHistoricalRegistry); } mHistoricalRegistry.systemReady(mContext.getContentResolver()); diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java index c53e4bdc2205..3a8d5835ccb1 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java @@ -24,6 +24,7 @@ import android.database.DatabaseErrorHandler; import android.database.DefaultDatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteFullException; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteRawStatement; import android.os.Environment; @@ -174,11 +175,16 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper { if (DEBUG) { Slog.i(LOG_TAG, "DB execSQL, sql: " + sql); } - SQLiteDatabase db = getWritableDatabase(); - if (bindArgs == null) { - db.execSQL(sql); - } else { - db.execSQL(sql, bindArgs); + try { + SQLiteDatabase db = getWritableDatabase(); + if (bindArgs == null) { + db.execSQL(sql); + } else { + db.execSQL(sql, bindArgs); + } + } catch (SQLiteFullException exception) { + Slog.e(LOG_TAG, "Couldn't exec sql command, disk is full. Discrete ops " + + "db file size (bytes) : " + getDatabaseFile().length(), exception); } } diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java index 70b7016fbb90..3867cbe1e98b 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java @@ -82,9 +82,8 @@ import java.util.Set; * INITIALIZATION: We can initialize persistence only after the system is ready * as we need to check the optional configuration override from the settings * database which is not initialized at the time the app ops service is created. This class - * relies on {@link HistoricalRegistry} for controlling that no calls are allowed until then. All - * outside calls are going through {@link HistoricalRegistry}. - * + * relies on {@link LegacyHistoricalRegistry} for controlling that no calls are allowed until then. + * All outside calls are going through {@link LegacyHistoricalRegistry}. */ abstract class DiscreteOpsRegistry { private static final String TAG = DiscreteOpsRegistry.class.getSimpleName(); diff --git a/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java index 20706b659ffb..771df19ec906 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java @@ -81,7 +81,7 @@ import java.util.Set; * THREADING AND LOCKING: * For in-memory transactions this class relies on {@link DiscreteOpsXmlRegistry#mInMemoryLock}. * It is assumed that the same lock is used for in-memory transactions in {@link AppOpsService}, - * {@link HistoricalRegistry}, and {@link DiscreteOpsXmlRegistry }. + * {@link LegacyHistoricalRegistry}, and {@link DiscreteOpsXmlRegistry }. * {@link DiscreteOpsRegistry#recordDiscreteAccess} must only be called while holding this lock. * {@link DiscreteOpsXmlRegistry#mOnDiskLock} is used when disk transactions are performed. * It is very important to release {@link DiscreteOpsXmlRegistry#mInMemoryLock} as soon as diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/LegacyHistoricalRegistry.java index a8128dd11cfe..f4f5775bbced 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/LegacyHistoricalRegistry.java @@ -128,11 +128,11 @@ import java.util.concurrent.TimeUnit; */ // TODO (bug:122218838): Make sure we handle start of epoch time // TODO (bug:122218838): Validate changed time is handled correctly -final class HistoricalRegistry implements HistoricalRegistryInterface { +final class LegacyHistoricalRegistry implements HistoricalRegistryInterface { private static final boolean DEBUG = false; private static final boolean KEEP_WTF_LOG = Build.IS_DEBUGGABLE; - private static final String LOG_TAG = HistoricalRegistry.class.getSimpleName(); + private static final String LOG_TAG = LegacyHistoricalRegistry.class.getSimpleName(); private static final String PARAMETER_DELIMITER = ","; private static final String PARAMETER_ASSIGNMENT = "="; @@ -200,7 +200,7 @@ final class HistoricalRegistry implements HistoricalRegistryInterface { private final Context mContext; - HistoricalRegistry(@NonNull Object lock, Context context) { + LegacyHistoricalRegistry(@NonNull Object lock, Context context) { mInMemoryLock = lock; mContext = context; if (Flags.enableSqliteAppopsAccesses()) { @@ -210,7 +210,7 @@ final class HistoricalRegistry implements HistoricalRegistryInterface { } } - HistoricalRegistry(@NonNull HistoricalRegistry other) { + LegacyHistoricalRegistry(@NonNull LegacyHistoricalRegistry other) { this(other.mInMemoryLock, other.mContext); mMode = other.mMode; mBaseSnapshotInterval = other.mBaseSnapshotInterval; @@ -313,9 +313,9 @@ final class HistoricalRegistry implements HistoricalRegistryInterface { final int mode = AppOpsManager.parseHistoricalMode(modeValue); final long baseSnapshotInterval = Long.parseLong(baseSnapshotIntervalValue); final int intervalCompressionMultiplier = Integer.parseInt(intervalMultiplierValue); - setHistoryParameters(mode, baseSnapshotInterval,intervalCompressionMultiplier); + setHistoryParameters(mode, baseSnapshotInterval, intervalCompressionMultiplier); return; - } catch (NumberFormatException ignored) {} + } catch (NumberFormatException ignored) { } } Slog.w(LOG_TAG, "Bad value for" + Settings.Global.APPOP_HISTORY_PARAMETERS + "=" + setting + " resetting!"); @@ -805,7 +805,7 @@ final class HistoricalRegistry implements HistoricalRegistryInterface { private void schedulePersistHistoricalOpsMLocked(@NonNull HistoricalOps ops) { final Message message = PooledLambda.obtainMessage( - HistoricalRegistry::persistPendingHistory, HistoricalRegistry.this); + LegacyHistoricalRegistry::persistPendingHistory, LegacyHistoricalRegistry.this); message.what = MSG_WRITE_PENDING_HISTORY; IoThread.getHandler().sendMessage(message); mPendingWrites.offerFirst(ops); @@ -813,7 +813,7 @@ final class HistoricalRegistry implements HistoricalRegistryInterface { private static void makeRelativeToEpochStart(@NonNull HistoricalOps ops, long nowMillis) { ops.setBeginAndEndTime(nowMillis - ops.getEndTimeMillis(), - nowMillis- ops.getBeginTimeMillis()); + nowMillis - ops.getBeginTimeMillis()); } private void pruneFutureOps(@NonNull List<HistoricalOps> ops) { @@ -979,7 +979,7 @@ final class HistoricalRegistry implements HistoricalRegistryInterface { final HistoricalOps readOp = readOps.get(i); currentOps.merge(readOp); } - } + } } private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(int filterUid, @@ -1125,7 +1125,7 @@ final class HistoricalRegistry implements HistoricalRegistryInterface { if (existingOpCount > 0) { // Compute elapsed time final long elapsedTimeMillis = passedOps.get(passedOps.size() - 1) - .getEndTimeMillis(); + .getEndTimeMillis(); for (int i = 0; i < existingOpCount; i++) { final HistoricalOps existingOp = existingOps.get(i); existingOp.offsetBeginAndEndTime(elapsedTimeMillis); diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 8ef79a916530..4b5f06b13885 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1472,8 +1472,8 @@ public class AudioDeviceBroker { mAudioService.postAccessoryPlugMediaUnmute(device); } - /*package*/ int getVssVolumeForDevice(int streamType, int device) { - return mAudioService.getVssVolumeForDevice(streamType, device); + /*package*/ int getVolumeForDeviceIgnoreMute(int streamType, int device) { + return mAudioService.getVolumeForDeviceIgnoreMute(streamType, device); } /*package*/ int getMaxVssVolumeForStream(int streamType) { diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 829d9ea7495f..2e6d98485e85 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -2482,7 +2482,7 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeHearingAidDeviceAvailable( String address, String name, int streamType, String eventSource) { - final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, + final int hearingAidVolIndex = mDeviceBroker.getVolumeForDeviceIgnoreMute(streamType, DEVICE_OUT_HEARING_AID); mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType); @@ -2672,7 +2672,7 @@ public class AudioDeviceInventory { } final int leAudioVolIndex = (volumeIndex == -1) - ? mDeviceBroker.getVssVolumeForDevice(streamType, device) + ? mDeviceBroker.getVolumeForDeviceIgnoreMute(streamType, device) : volumeIndex; final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType); mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 766456134b20..7f03b2713d9b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -529,7 +529,7 @@ public class AudioService extends IAudioService.Stub */ private InputDeviceVolumeHelper mInputDeviceVolumeHelper; - /*package*/ int getVssVolumeForDevice(int stream, int device) { + /*package*/ int getVolumeForDeviceIgnoreMute(int stream, int device) { final VolumeStreamState streamState = mStreamStates.get(stream); return streamState != null ? streamState.getIndex(device) : -1; } @@ -3336,11 +3336,25 @@ public class AudioService extends IAudioService.Stub } private int rescaleIndex(int index, int srcStream, int dstStream) { - return rescaleIndex(index, - getVssForStreamOrDefault(srcStream).getMinIndex(), - getVssForStreamOrDefault(srcStream).getMaxIndex(), - getVssForStreamOrDefault(dstStream).getMinIndex(), - getVssForStreamOrDefault(dstStream).getMaxIndex()); + final VolumeStreamState srcVss = getVssForStreamOrDefault(srcStream); + final VolumeStreamState dstVss = getVssForStreamOrDefault(dstStream); + int newIndex = rescaleIndex(index, srcVss.getMinIndex(), srcVss.getMaxIndex(), + dstVss.getMinIndex(), dstVss.getMaxIndex()); + // only apply solution for DTMF stream to make sure that it is not muted when + // re-aliasing to voice call stream. With ringMyCar flag enabled this will be + // automatically solved since we are sending the mute state to APM + // TODO(b/402542630): revisit stream aliasing logic with different min index + // values / mute states + if (!ringMyCar() && dstStream == AudioSystem.STREAM_DTMF + && srcStream == AudioSystem.STREAM_VOICE_CALL + && srcVss.getMinIndex() > dstVss.getMinIndex()) { + newIndex += srcVss.getMinIndex() - dstVss.getMinIndex(); + if (newIndex > dstVss.getMaxIndex()) { + newIndex = dstVss.getMaxIndex(); + } + } + + return newIndex; } private int rescaleIndex(int index, int srcMin, int srcMax, int dstMin, int dstMax) { @@ -5100,7 +5114,7 @@ public class AudioService extends IAudioService.Stub } final int device = absVolumeDevices.toArray(new Integer[0])[0].intValue(); - final int index = getStreamVolume(streamType, device); + final int index = (getVolumeForDeviceIgnoreMute(streamType, device) + 5) / 10; if (DEBUG_VOL) { Slog.i(TAG, "onUpdateContextualVolumes streamType: " + streamType @@ -15109,6 +15123,61 @@ public class AudioService extends IAudioService.Stub /** * @hide + * Returns the current audio output device delay value of key in milliseconds. + * + * In aidl implementation, the param of getParameters is "key" and reply delay of all supported + * devices which be composited as "key=device,address,delay|device,address,delay|...". + * e.g. + * param: additional_output_device_delay= + * reply: additional_output_device_delay=2,,0|4,,0|8,,0|128,,0|256,,0|512,,0|1024,,0|8192,,0| + * 16384,,0|262144,,0|262145,,0|524288,,0|67108864,,0|134217728,,0|536870912,,0|536870913,,0| + * 536870914,,0 + * + * In hidl implementation, the param of getParameters is "key=device,address" and reply a + * specific device delay which be composited as "key=device,address,delay". + * e.g. + * param: additional_output_device_delay=2, + * reply: additional_output_device_delay=2,,0 + * + * @param key + * @param deviceType + * @param address + * @return the delay value of key. This is a non-negative number. + * {@code 0} is returned if unsupported. + */ + private long getDelayByKeyDevice(@NonNull String key, @NonNull AudioDeviceAttributes device) { + long delayMillis = 0; + + try { + if (AudioHalVersionInfo.AUDIO_HAL_TYPE_AIDL == getHalVersion().getHalType()) { + final String reply = AudioSystem.getParameters(key); + final String keyDeviceAddressPrefix = + Integer.toUnsignedString(device.getInternalType()) + "," + device.getAddress() + + ","; + int start = reply.indexOf(keyDeviceAddressPrefix); + int end = -1; + if (start != -1) { + start += keyDeviceAddressPrefix.length(); + end = reply.indexOf("|", start); + delayMillis = Long.parseLong( + end == -1 ? reply.substring(start) : reply.substring(start, end)); + } + } else { + final String reply = AudioSystem.getParameters( + key + "=" + device.getInternalType() + "," + device.getAddress()); + if (reply.contains(key)) { + delayMillis = Long.parseLong(reply.substring(key.length() + 1)); + } + } + } catch (NullPointerException e) { + Log.w(TAG, "NullPointerException when getting delay for device " + device, e); + } + + return delayMillis; + } + + /** + * @hide * Returns the current additional audio output device delay in milliseconds. * * @param deviceType @@ -15124,17 +15193,8 @@ public class AudioService extends IAudioService.Stub device = retrieveBluetoothAddress(device); final String key = "additional_output_device_delay"; - final String reply = AudioSystem.getParameters( - key + "=" + device.getInternalType() + "," + device.getAddress()); - long delayMillis = 0; - if (reply.contains(key)) { - try { - delayMillis = Long.parseLong(reply.substring(key.length() + 1)); - } catch (NullPointerException e) { - delayMillis = 0; - } - } - return delayMillis; + + return getDelayByKeyDevice(key, device); } /** @@ -15156,17 +15216,8 @@ public class AudioService extends IAudioService.Stub device = retrieveBluetoothAddress(device); final String key = "max_additional_output_device_delay"; - final String reply = AudioSystem.getParameters( - key + "=" + device.getInternalType() + "," + device.getAddress()); - long delayMillis = 0; - if (reply.contains(key)) { - try { - delayMillis = Long.parseLong(reply.substring(key.length() + 1)); - } catch (NullPointerException e) { - delayMillis = 0; - } - } - return delayMillis; + + return getDelayByKeyDevice(key, device); } @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 643f3308d8f5..67afff79dffd 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -724,7 +724,7 @@ public class SoundDoseHelper { int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC); if (safeDevicesContains(device) && isStreamActive) { scheduleMusicActiveCheck(); - int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC, + int index = mAudioService.getVolumeForDeviceIgnoreMute(AudioSystem.STREAM_MUSIC, device); if (index > safeMediaVolumeIndex(device)) { // Approximate cumulative active music time diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index ac0892b92646..aa985907071f 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1113,7 +1113,11 @@ public class Vpn { } // Remove always-on VPN if it's not supported. if (!isAlwaysOnPackageSupported(alwaysOnPackage)) { - setAlwaysOnPackage(null, false, null); + // Do not remove the always-on setting due to the restricted ability in safe mode. + // The always-on VPN can then start after the device reboots to normal mode. + if (!mContext.getPackageManager().isSafeMode()) { + setAlwaysOnPackage(null, false, null); + } return false; } // Skip if the service is already established. This isn't bulletproof: it's not bound diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 3aaf4f6fe85a..7450dffc05ab 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -122,8 +122,8 @@ final class DisplayDeviceInfo { public static final int FLAG_MASK_DISPLAY_CUTOUT = 1 << 11; /** - * Flag: This flag identifies secondary displays that should show system decorations, such as - * navigation bar, home activity or wallpaper. + * Flag: This flag identifies secondary displays that should always show system decorations, + * such as navigation bar, home activity or wallpaper. * <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p> * @hide */ @@ -191,6 +191,19 @@ final class DisplayDeviceInfo { public static final int FLAG_STEAL_TOP_FOCUS_DISABLED = 1 << 19; /** + * Flag: Indicates that the display is allowed to switch the content mode between + * projected/extended and mirroring. This allows the display to dynamically add or remove the + * home and system decorations. + * + * Note that this flag should not be enabled with any of {@link #FLAG_PRIVATE}, + * {@link #FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}, or {@link #FLAG_OWN_CONTENT_ONLY} at the + * same time; otherwise it will be ignored. + * + * @hide + */ + public static final int FLAG_ALLOWS_CONTENT_MODE_SWITCH = 1 << 20; + + /** * Touch attachment: Display does not receive touch. */ public static final int TOUCH_NONE = 0; diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java index f73b66c78fce..ce8d8a6db9d3 100644 --- a/services/core/java/com/android/server/display/DisplayGroup.java +++ b/services/core/java/com/android/server/display/DisplayGroup.java @@ -16,6 +16,8 @@ package com.android.server.display; +import android.util.IndentingPrintWriter; + import java.util.ArrayList; import java.util.List; @@ -97,4 +99,14 @@ public class DisplayGroup { } return displayIds; } + + /** Dumps information about the DisplayGroup. */ + void dumpLocked(IndentingPrintWriter ipw) { + final int numDisplays = mDisplays.size(); + for (int i = 0; i < numDisplays; i++) { + LogicalDisplay logicalDisplay = mDisplays.get(i); + ipw.println("Display " + logicalDisplay.getDisplayIdLocked() + " " + + logicalDisplay.getPrimaryDisplayDeviceLocked()); + } + } } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 7b714ad2bd9e..2cad7ed2e9e9 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -766,7 +766,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND; } } else { - if (!res.getBoolean(R.bool.config_localDisplaysMirrorContent)) { + if (shouldOwnContentOnly()) { mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY; } @@ -780,6 +780,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } + if (getFeatureFlags().isDisplayContentModeManagementEnabled()) { + // Public display with FLAG_OWN_CONTENT_ONLY disabled is allowed to switch the + // content mode. + if (mIsFirstDisplay + || (!isDisplayPrivate(physicalAddress) && !shouldOwnContentOnly())) { + mInfo.flags |= DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH; + } + } + if (DisplayCutout.getMaskBuiltInDisplayCutout(res, mInfo.uniqueId)) { mInfo.flags |= DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT; } @@ -822,6 +831,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { R.string.display_manager_hdmi_display_name); } } + mInfo.frameRateOverrides = mFrameRateOverrides; // The display is trusted since it is created by system. @@ -1467,6 +1477,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { return false; } + private boolean shouldOwnContentOnly() { + final Resources res = getOverlayContext().getResources(); + return !res.getBoolean(R.bool.config_localDisplaysMirrorContent); + } + private boolean isDisplayStealTopFocusDisabled(DisplayAddress.Physical physicalAddress) { if (physicalAddress == null) { return false; diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index b2b9ef17ec8d..0e6870f7ed7d 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -489,6 +489,11 @@ final class LogicalDisplay { if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED) != 0) { mBaseDisplayInfo.flags |= Display.FLAG_STEAL_TOP_FOCUS_DISABLED; } + // Rear display should not be allowed to use the content mode switch. + if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0 + && mDevicePosition != Layout.Display.POSITION_REAR) { + mBaseDisplayInfo.flags |= Display.FLAG_ALLOWS_CONTENT_MODE_SWITCH; + } Rect maskingInsets = getMaskingInsets(deviceInfo); int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right; int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom; @@ -1155,6 +1160,7 @@ final class LogicalDisplay { pw.println("mRequestedMinimalPostProcessing=" + mRequestedMinimalPostProcessing); pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides)); pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids); + pw.println("mDisplayGroupId=" + mDisplayGroupId); pw.println("mDisplayGroupName=" + mDisplayGroupName); pw.println("mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId); pw.println("mLeadDisplayId=" + mLeadDisplayId); diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index f4daf8761e9b..4a4c616b34e3 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -478,6 +478,21 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { ipw.decreaseIndent(); ipw.println(); } + + final int displayGroupCount = mDisplayGroups.size(); + ipw.println(); + ipw.println("Display Groups: size=" + displayGroupCount); + for (int i = 0; i < displayGroupCount; i++) { + int groupId = mDisplayGroups.keyAt(i); + DisplayGroup displayGroup = mDisplayGroups.valueAt(i); + ipw.println("Group " + groupId + ":"); + ipw.increaseIndent(); + displayGroup.dumpLocked(ipw); + ipw.decreaseIndent(); + ipw.println(); + } + + mDeviceStateToLayoutMap.dumpLocked(ipw); } diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index b5a9b19bc5c5..60b7fca99e7b 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -76,6 +76,7 @@ import java.util.regex.Pattern; * <li><code>secure</code>: creates a secure display</li> * <li><code>own_content_only</code>: only shows this display's own content</li> * <li><code>should_show_system_decorations</code>: supports system decorations</li> + * <li><code>fixed_content_mode</code>: not allowed to switch content mode</li> * <li><code>gravity_top_left</code>: display the overlay at the top left of the screen</li> * <li><code>gravity_top_right</code>: display the overlay at the top right of the screen</li> * <li><code>gravity_bottom_right</code>: display the overlay at the bottom right of the screen</li> @@ -117,6 +118,18 @@ final class OverlayDisplayAdapter extends DisplayAdapter { private static final String OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = "should_show_system_decorations"; + /** + * When this flag is set, the overlay display is not allowed to switch content mode. + * Note that it is the opposite of {@link DisplayDeviceInfo#FLAG_ALLOWS_CONTENT_MODE_SWITCH}, + * because we want overlay displays (such as those used for connected display simulation in + * development) to have {@link DisplayDeviceInfo#FLAG_ALLOWS_CONTENT_MODE_SWITCH} enabled by + * default without explicitly specifying it. + * + * @see DisplayDeviceInfo#FLAG_ALLOWS_CONTENT_MODE_SWITCH + */ + private static final String OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE = + "fixed_content_mode"; + // 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"; @@ -384,6 +397,17 @@ final class OverlayDisplayAdapter extends DisplayAdapter { if (mFlags.mShouldShowSystemDecorations) { mInfo.flags |= DisplayDeviceInfo.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; } + if (getFeatureFlags().isDisplayContentModeManagementEnabled()) { + if (!mFlags.mFixedContentMode + && !mFlags.mOwnContentOnly + && !mFlags.mShouldShowSystemDecorations) { + // For overlay displays, if FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS and + // FLAG_OWN_CONTENT_ONLY are both disabled, + // then FLAG_ALLOWS_CONTENT_MODE_SWITCH should be enabled by default, + // unless OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE is set. + mInfo.flags |= DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH; + } + } mInfo.type = Display.TYPE_OVERLAY; mInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL; mInfo.state = mState; @@ -628,16 +652,21 @@ final class OverlayDisplayAdapter extends DisplayAdapter { /** See {@link #OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}. */ final boolean mShouldShowSystemDecorations; + /** See {@link #OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE}. */ + final boolean mFixedContentMode; + final int mGravity; OverlayFlags( boolean secure, boolean ownContentOnly, boolean shouldShowSystemDecorations, + boolean fixedContentMode, int gravity) { mSecure = secure; mOwnContentOnly = ownContentOnly; mShouldShowSystemDecorations = shouldShowSystemDecorations; + mFixedContentMode = fixedContentMode; mGravity = gravity; } @@ -647,12 +676,14 @@ final class OverlayDisplayAdapter extends DisplayAdapter { false /* secure */, false /* ownContentOnly */, false /* shouldShowSystemDecorations */, + false /* fixedContentMode */, Gravity.NO_GRAVITY); } boolean secure = false; boolean ownContentOnly = false; boolean shouldShowSystemDecorations = false; + boolean fixedContentMode = false; int gravity = Gravity.NO_GRAVITY; for (String flag: flagString.split(FLAG_SPLITTER)) { if (OVERLAY_DISPLAY_FLAG_SECURE.equals(flag)) { @@ -661,11 +692,14 @@ final class OverlayDisplayAdapter extends DisplayAdapter { ownContentOnly = true; } else if (OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS.equals(flag)) { shouldShowSystemDecorations = true; + } else if (OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE.equals(flag)) { + fixedContentMode = true; } else { gravity = parseOverlayGravity(flag); } } - return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations, gravity); + return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations, + fixedContentMode, gravity); } @Override @@ -674,6 +708,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { .append("secure=").append(mSecure) .append(", ownContentOnly=").append(mOwnContentOnly) .append(", shouldShowSystemDecorations=").append(mShouldShowSystemDecorations) + .append(", fixedContentMode=").append(mFixedContentMode) .append(", gravity").append(Gravity.toString(mGravity)) .append("}") .toString(); diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index 902eefa824b5..89679c728127 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -666,6 +666,12 @@ final class WifiDisplayAdapter extends DisplayAdapter { mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight); // The display is trusted since it is created by system. mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED; + if (getFeatureFlags().isDisplayContentModeManagementEnabled()) { + // The wifi display is allowed to switch content mode since FLAG_PRIVATE, + // FLAG_OWN_CONTENT_ONLY, and FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS are not + // enabled in WifiDisplayDevice#getDisplayDeviceInfoLocked(). + mInfo.flags |= DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH; + } mInfo.displayShape = DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false); } diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 7cc178d5ff6c..f5228df3b8b2 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -280,6 +280,11 @@ public class DisplayManagerFlags { Flags::committedStateSeparateEvent ); + private final FlagState mSeparateTimeouts = new FlagState( + Flags.FLAG_SEPARATE_TIMEOUTS, + Flags::separateTimeouts + ); + private final FlagState mDelayImplicitRrRegistrationUntilRrAccessed = new FlagState( Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED, Flags::delayImplicitRrRegistrationUntilRrAccessed @@ -608,6 +613,14 @@ public class DisplayManagerFlags { } /** + * @return {@code true} if the flag for having a separate timeouts for power groups + * is enabled + */ + public boolean isSeparateTimeoutsEnabled() { + return mSeparateTimeouts.isEnabled(); + } + + /** * @return {@code true} if the flag for only explicit subscription for RR changes is enabled */ public boolean isDelayImplicitRrRegistrationUntilRrAccessedEnabled() { @@ -671,6 +684,7 @@ public class DisplayManagerFlags { pw.println(" " + mFramerateOverrideTriggersRrCallbacks); pw.println(" " + mRefreshRateEventForForegroundApps); pw.println(" " + mCommittedStateSeparateEvent); + pw.println(" " + mSeparateTimeouts); pw.println(" " + mDelayImplicitRrRegistrationUntilRrAccessed); } diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index a0064a9f5d1d..007646f6a605 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -509,6 +509,14 @@ flag { } } + +flag { + name: "separate_timeouts" + namespace: "lse_desktop_experience" + description: "Allow separate timeouts for different power groups" + bug: "402356291" +} + flag { name: "delay_implicit_rr_registration_until_rr_accessed" namespace: "display_manager" diff --git a/services/core/java/com/android/server/display/mode/ModeChangeObserver.java b/services/core/java/com/android/server/display/mode/ModeChangeObserver.java index 2751835f9958..50782a2f22c8 100644 --- a/services/core/java/com/android/server/display/mode/ModeChangeObserver.java +++ b/services/core/java/com/android/server/display/mode/ModeChangeObserver.java @@ -16,9 +16,11 @@ package com.android.server.display.mode; +import android.hardware.display.DisplayManager; +import android.os.Handler; import android.os.Looper; +import android.util.LongSparseArray; import android.util.Slog; -import android.util.SparseArray; import android.view.Display; import android.view.DisplayAddress; import android.view.DisplayEventReceiver; @@ -34,72 +36,128 @@ final class ModeChangeObserver { @SuppressWarnings("unused") private DisplayEventReceiver mModeChangeListener; - private final SparseArray<Set<Integer>> mRejectedModesByDisplay = new SparseArray<>(); - private Looper mLooper; + private DisplayManager.DisplayListener mDisplayListener; + private final LongSparseArray<Set<Integer>> mRejectedModesMap = + new LongSparseArray<>(); + private final LongSparseArray<Integer> mPhysicalIdToLogicalIdMap = new LongSparseArray<>(); + private final Looper mLooper; + private final Handler mHandler; + /** + * Observer for display mode changes. + * This class observes display mode rejections and updates the vote storage + * for rejected modes vote accordingly. + */ ModeChangeObserver(VotesStorage votesStorage, DisplayModeDirector.Injector injector, Looper looper) { mVotesStorage = votesStorage; mInjector = injector; mLooper = looper; + mHandler = new Handler(mLooper); } + /** + * Start observing display mode changes. + */ void observe() { - mModeChangeListener = new DisplayEventReceiver(mLooper) { + updatePhysicalIdToLogicalIdMap(); + mDisplayListener = new DisplayManager.DisplayListener() { @Override - public void onModeRejected(long physicalDisplayId, int modeId) { - Slog.d(TAG, "Mode Rejected event received"); - int displayId = getLogicalDisplayId(physicalDisplayId); - if (displayId < 0) { - Slog.e(TAG, "Logical Display Id not found"); + public void onDisplayAdded(int displayId) { + updateVoteForDisplay(displayId); + } + + @Override + public void onDisplayRemoved(int displayId) { + int oldPhysicalDisplayIdIndex = mPhysicalIdToLogicalIdMap.indexOfValue(displayId); + if (oldPhysicalDisplayIdIndex < 0) { + Slog.e(TAG, "Removed display not found"); return; } - populateRejectedModesListByDisplay(displayId, modeId); + long oldPhysicalDisplayId = + mPhysicalIdToLogicalIdMap.keyAt(oldPhysicalDisplayIdIndex); + mPhysicalIdToLogicalIdMap.delete(oldPhysicalDisplayId); + mRejectedModesMap.delete(oldPhysicalDisplayId); + mVotesStorage.updateVote(displayId, Vote.PRIORITY_REJECTED_MODES, null); } @Override - public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) { - Slog.d(TAG, "Hotplug event received"); - if (!connected) { - int displayId = getLogicalDisplayId(physicalDisplayId); - if (displayId < 0) { - Slog.e(TAG, "Logical Display Id not found"); - return; - } - clearRejectedModesListByDisplay(displayId); + public void onDisplayChanged(int displayId) { + int oldPhysicalDisplayIdIndex = mPhysicalIdToLogicalIdMap.indexOfValue(displayId); + if (oldPhysicalDisplayIdIndex < 0) { + Slog.e(TAG, "Changed display not found"); + return; + } + long oldPhysicalDisplayId = + mPhysicalIdToLogicalIdMap.keyAt(oldPhysicalDisplayIdIndex); + mPhysicalIdToLogicalIdMap.delete(oldPhysicalDisplayId); + + updateVoteForDisplay(displayId); + } + }; + mInjector.registerDisplayListener(mDisplayListener, mHandler, + DisplayManager.EVENT_TYPE_DISPLAY_ADDED + | DisplayManager.EVENT_TYPE_DISPLAY_CHANGED + | DisplayManager.EVENT_TYPE_DISPLAY_REMOVED); + mModeChangeListener = new DisplayEventReceiver(mLooper) { + @Override + public void onModeRejected(long physicalDisplayId, int modeId) { + Slog.d(TAG, "Mode Rejected event received"); + updateRejectedModesListByDisplay(physicalDisplayId, modeId); + if (mPhysicalIdToLogicalIdMap.indexOfKey(physicalDisplayId) < 0) { + Slog.d(TAG, "Rejected Modes Vote will be updated after display is added"); + return; } + mVotesStorage.updateVote(mPhysicalIdToLogicalIdMap.get(physicalDisplayId), + Vote.PRIORITY_REJECTED_MODES, + Vote.forRejectedModes(mRejectedModesMap.get(physicalDisplayId))); } }; } - private int getLogicalDisplayId(long rejectedModePhysicalDisplayId) { + private void updateVoteForDisplay(int displayId) { + Display display = mInjector.getDisplay(displayId); + if (display == null) { + // We can occasionally get a display added or changed event for a display that was + // subsequently removed, which means this returns null. Check this case and bail + // out early; if it gets re-attached we will eventually get another call back for it. + Slog.e(TAG, "Added or Changed display has disappeared"); + return; + } + DisplayAddress address = display.getAddress(); + if (address instanceof DisplayAddress.Physical physical) { + long physicalDisplayId = physical.getPhysicalDisplayId(); + mPhysicalIdToLogicalIdMap.put(physicalDisplayId, displayId); + Set<Integer> modes = mRejectedModesMap.get(physicalDisplayId); + mVotesStorage.updateVote(displayId, Vote.PRIORITY_REJECTED_MODES, + modes != null ? Vote.forRejectedModes(modes) : null); + } + } + + private void updatePhysicalIdToLogicalIdMap() { Display[] displays = mInjector.getDisplays(); for (Display display : displays) { + if (display == null) { + continue; + } DisplayAddress address = display.getAddress(); if (address instanceof DisplayAddress.Physical physical) { - long physicalDisplayId = physical.getPhysicalDisplayId(); - if (physicalDisplayId == rejectedModePhysicalDisplayId) { - return display.getDisplayId(); - } + mPhysicalIdToLogicalIdMap.put(physical.getPhysicalDisplayId(), + display.getDisplayId()); } } - return -1; } - private void populateRejectedModesListByDisplay(int displayId, int rejectedModeId) { - Set<Integer> alreadyRejectedModes = mRejectedModesByDisplay.get(displayId); + private void updateRejectedModesListByDisplay(long rejectedModePhysicalDisplayId, + int rejectedModeId) { + Set<Integer> alreadyRejectedModes = + mRejectedModesMap.get(rejectedModePhysicalDisplayId); if (alreadyRejectedModes == null) { alreadyRejectedModes = new HashSet<>(); - mRejectedModesByDisplay.put(displayId, alreadyRejectedModes); + mRejectedModesMap.put(rejectedModePhysicalDisplayId, + alreadyRejectedModes); } alreadyRejectedModes.add(rejectedModeId); - mVotesStorage.updateVote(displayId, Vote.PRIORITY_REJECTED_MODES, - Vote.forRejectedModes(alreadyRejectedModes)); - } - - private void clearRejectedModesListByDisplay(int displayId) { - mRejectedModesByDisplay.remove(displayId); - mVotesStorage.updateVote(displayId, Vote.PRIORITY_REJECTED_MODES, null); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 23757757e336..fde9165a84c6 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -5669,11 +5669,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. LocalServices.addService(InputMethodManagerInternal.class, mInputMethodManagerInternal); } - // TODO(b/352228316): Remove it once IMMIProxy is removed. - InputMethodManagerInternal getLocalService(){ - return mInputMethodManagerInternal; - } - private final class LocalServiceImpl extends InputMethodManagerInternal { @ImfLockFree diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java index 2d937bdcc683..6db62c8397f3 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java @@ -171,7 +171,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } } - public boolean isInMessageHistory(HubMessage message) { + public boolean isInReliableMessageHistory(HubMessage message) { + if (!message.isResponseRequired()) return false; // Clean up the history Iterator<Map.Entry<Integer, Long>> iterator = mRxMessageHistoryMap.entrySet().iterator(); @@ -188,7 +189,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub return mRxMessageHistoryMap.containsKey(message.getMessageSequenceNumber()); } - public void addMessageToHistory(HubMessage message) { + public void addReliableMessageToHistory(HubMessage message) { + if (!message.isResponseRequired()) return; if (mRxMessageHistoryMap.containsKey(message.getMessageSequenceNumber())) { long value = mRxMessageHistoryMap.get(message.getMessageSequenceNumber()); Log.w( @@ -623,7 +625,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub return ErrorCode.PERMANENT_ERROR; } HubEndpointInfo remote = mSessionMap.get(sessionId).getRemoteEndpointInfo(); - if (mSessionMap.get(sessionId).isInMessageHistory(message)) { + if (mSessionMap.get(sessionId).isInReliableMessageHistory(message)) { Log.e(TAG, "Dropping duplicate message: " + message); return ErrorCode.TRANSIENT_ERROR; } @@ -648,7 +650,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub boolean success = invokeCallback((consumer) -> consumer.onMessageReceived(sessionId, message)); if (success) { - mSessionMap.get(sessionId).addMessageToHistory(message); + mSessionMap.get(sessionId).addReliableMessageToHistory(message); } return success ? ErrorCode.OK : ErrorCode.TRANSIENT_ERROR; } diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index 6a72cc7c7779..7d9f2c29f943 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -24,6 +24,7 @@ import android.hardware.location.GeofenceHardware; import android.hardware.location.GeofenceHardwareImpl; import android.location.FusedBatchOptions; import android.location.GnssAntennaInfo; +import android.location.GnssAssistance; import android.location.GnssCapabilities; import android.location.GnssMeasurementCorrections; import android.location.GnssMeasurementRequest; @@ -35,6 +36,8 @@ import android.location.IGnssStatusListener; import android.location.IGpsGeofenceHardware; import android.location.Location; import android.location.LocationManager; +import android.location.flags.Flags; +import android.location.provider.IGnssAssistanceCallback; import android.location.util.identity.CallerIdentity; import android.os.BatteryStats; import android.os.Binder; @@ -47,12 +50,13 @@ import com.android.internal.app.IBatteryStats; import com.android.server.FgThread; import com.android.server.location.gnss.hal.GnssNative; import com.android.server.location.injector.Injector; +import com.android.server.location.provider.proxy.ProxyGnssAssistanceProvider; import java.io.FileDescriptor; import java.util.List; /** Manages Gnss providers and related Gnss functions for LocationManagerService. */ -public class GnssManagerService { +public class GnssManagerService implements GnssNative.GnssAssistanceCallbacks { public static final String TAG = "GnssManager"; public static final boolean D = Log.isLoggable(TAG, Log.DEBUG); @@ -75,6 +79,8 @@ public class GnssManagerService { private final GnssMetrics mGnssMetrics; + private @Nullable ProxyGnssAssistanceProvider mProxyGnssAssistanceProvider = null; + public GnssManagerService(Context context, Injector injector, GnssNative gnssNative) { mContext = context.createAttributionContext(ATTRIBUTION_ID); mGnssNative = gnssNative; @@ -100,6 +106,16 @@ public class GnssManagerService { /** Called when system is ready. */ public void onSystemReady() { mGnssLocationProvider.onSystemReady(); + + if (Flags.gnssAssistanceInterfaceJni()) { + mProxyGnssAssistanceProvider = + ProxyGnssAssistanceProvider.createAndRegister(mContext); + if (mProxyGnssAssistanceProvider == null) { + Log.e(TAG, "no gnss assistance provider found"); + } else { + mGnssNative.setGnssAssistanceCallbacks(this); + } + } } /** Retrieve the GnssLocationProvider. */ @@ -323,6 +339,29 @@ public class GnssManagerService { } } + @Override + public void onRequestGnssAssistanceInject() { + if (!Flags.gnssAssistanceInterfaceJni()) { + return; + } + if (mProxyGnssAssistanceProvider == null) { + Log.e(TAG, "ProxyGnssAssistanceProvider is null"); + return; + } + mProxyGnssAssistanceProvider.request(new IGnssAssistanceCallback.Stub() { + @Override + public void onError() { + Log.e(TAG, "GnssAssistanceCallback.onError"); + } + + @Override + public void onResult(GnssAssistance gnssAssistance) { + Log.d(TAG, "GnssAssistanceCallback.onResult"); + mGnssNative.injectGnssAssistance(gnssAssistance); + } + }); + } + private class GnssCapabilitiesHalModule implements GnssNative.BaseCallbacks { GnssCapabilitiesHalModule(GnssNative gnssNative) { diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java index c79a21a7eea8..7b4c56334868 100644 --- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java +++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java @@ -23,6 +23,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.location.GnssAntennaInfo; +import android.location.GnssAssistance; import android.location.GnssCapabilities; import android.location.GnssMeasurementCorrections; import android.location.GnssMeasurementsEvent; @@ -30,6 +31,7 @@ import android.location.GnssNavigationMessage; import android.location.GnssSignalType; import android.location.GnssStatus; import android.location.Location; +import android.location.flags.Flags; import android.os.Binder; import android.os.Handler; import android.os.SystemClock; @@ -275,6 +277,12 @@ public class GnssNative { void onRequestPsdsDownload(int psdsType); } + /** Callbacks for HAL requesting GNSS assistance. */ + public interface GnssAssistanceCallbacks { + /** On request GnssAssistance injection. */ + void onRequestGnssAssistanceInject(); + } + /** Callbacks for AGPS functionality. */ public interface AGpsCallbacks { @@ -400,6 +408,7 @@ public class GnssNative { private TimeCallbacks mTimeCallbacks; private LocationRequestCallbacks mLocationRequestCallbacks; private PsdsCallbacks mPsdsCallbacks; + private @Nullable GnssAssistanceCallbacks mGnssAssistanceCallbacks; private AGpsCallbacks mAGpsCallbacks; private NotificationCallbacks mNotificationCallbacks; @@ -504,6 +513,16 @@ public class GnssNative { mNotificationCallbacks = Objects.requireNonNull(callbacks); } + /** Sets GnssAssistanceCallbacks. */ + public void setGnssAssistanceCallbacks(GnssAssistanceCallbacks callbacks) { + if (!Flags.gnssAssistanceInterfaceJni()) { + return; + } + Preconditions.checkState(!mRegistered); + Preconditions.checkState(mGnssAssistanceCallbacks == null); + mGnssAssistanceCallbacks = Objects.requireNonNull(callbacks); + } + /** * Registers with the HAL and allows callbacks to begin. Once registered with the native HAL, * no more callbacks can be added or set. Must only be called once. @@ -1053,6 +1072,17 @@ public class GnssNative { mGnssHal.injectNiSuplMessageData(data, length, slotIndex); } + /** + * Injects GNSS assistance data into the GNSS HAL. + */ + public void injectGnssAssistance(GnssAssistance assistance) { + if (!Flags.gnssAssistanceInterfaceJni()) { + return; + } + Preconditions.checkState(mRegistered); + mGnssHal.injectGnssAssistance(assistance); + } + @NativeEntryPoint void reportGnssServiceDied() { // Not necessary to clear (and restore) binder identity since it runs on another thread. @@ -1269,6 +1299,15 @@ public class GnssNative { } @NativeEntryPoint + void gnssAssistanceInjectRequest() { + if (!Flags.gnssAssistanceInterfaceJni() || mGnssAssistanceCallbacks == null) { + return; + } + Binder.withCleanCallingIdentity( + () -> mGnssAssistanceCallbacks.onRequestGnssAssistanceInject()); + } + + @NativeEntryPoint void reportGeofenceTransition(int geofenceId, Location location, int transition, long transitionTimestamp) { Binder.withCleanCallingIdentity( @@ -1569,6 +1608,10 @@ public class GnssNative { protected void injectNiSuplMessageData(byte[] data, int length, int slotIndex) { native_inject_ni_supl_message_data(data, length, slotIndex); } + + protected void injectGnssAssistance(GnssAssistance gnssAssistance) { + native_inject_gnss_assistance(gnssAssistance); + } } // basic APIs @@ -1718,4 +1761,7 @@ public class GnssNative { private static native boolean native_supports_psds(); private static native void native_inject_psds_data(byte[] data, int length, int psdsType); + + // GNSS Assistance APIs + private static native void native_inject_gnss_assistance(GnssAssistance gnssAssistance); } diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index dd52cce9e927..3f2c2228e453 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -46,7 +46,6 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; public class ConditionProviders extends ManagedServices { @@ -203,14 +202,7 @@ public class ConditionProviders extends ManagedServices { @Override protected void loadDefaultsFromConfig() { - for (String dndPackage : getDefaultDndAccessPackages(mContext)) { - addDefaultComponentOrPackage(dndPackage); - } - } - - static List<String> getDefaultDndAccessPackages(Context context) { - ArrayList<String> packages = new ArrayList<>(); - String defaultDndAccess = context.getResources().getString( + String defaultDndAccess = mContext.getResources().getString( R.string.config_defaultDndAccessPackages); if (defaultDndAccess != null) { String[] dnds = defaultDndAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR); @@ -218,10 +210,9 @@ public class ConditionProviders extends ManagedServices { if (TextUtils.isEmpty(dnds[i])) { continue; } - packages.add(dnds[i]); + addDefaultComponentOrPackage(dnds[i]); } } - return packages; } @Override diff --git a/services/core/java/com/android/server/notification/TEST_MAPPING b/services/core/java/com/android/server/notification/TEST_MAPPING index dc7129cde5e5..ea7ee4addbe6 100644 --- a/services/core/java/com/android/server/notification/TEST_MAPPING +++ b/services/core/java/com/android/server/notification/TEST_MAPPING @@ -4,7 +4,10 @@ "name": "CtsNotificationTestCases_notification" }, { - "name": "FrameworksUiServicesTests_notification" + "name": "FrameworksUiServicesNotificationTests" + }, + { + "name": "FrameworksUiServicesZenTests" } ], "postsubmit": [ diff --git a/services/core/java/com/android/server/notification/ZenConfigTrimmer.java b/services/core/java/com/android/server/notification/ZenConfigTrimmer.java deleted file mode 100644 index d65954d11646..000000000000 --- a/services/core/java/com/android/server/notification/ZenConfigTrimmer.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.notification; - -import android.content.Context; -import android.os.Parcel; -import android.service.notification.SystemZenRules; -import android.service.notification.ZenModeConfig; -import android.util.Slog; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; - -class ZenConfigTrimmer { - - private static final String TAG = "ZenConfigTrimmer"; - private static final int MAXIMUM_PARCELED_SIZE = 150_000; // bytes - - private final HashSet<String> mTrustedPackages; - - ZenConfigTrimmer(Context context) { - mTrustedPackages = new HashSet<>(); - mTrustedPackages.add(SystemZenRules.PACKAGE_ANDROID); - mTrustedPackages.addAll(ConditionProviders.getDefaultDndAccessPackages(context)); - } - - void trimToMaximumSize(ZenModeConfig config) { - Map<String, PackageRules> rulesPerPackage = new HashMap<>(); - for (ZenModeConfig.ZenRule rule : config.automaticRules.values()) { - PackageRules pkgRules = rulesPerPackage.computeIfAbsent(rule.pkg, PackageRules::new); - pkgRules.mRules.add(rule); - } - - int totalSize = 0; - for (PackageRules pkgRules : rulesPerPackage.values()) { - totalSize += pkgRules.dataSize(); - } - - if (totalSize > MAXIMUM_PARCELED_SIZE) { - List<PackageRules> deletionCandidates = new ArrayList<>(); - for (PackageRules pkgRules : rulesPerPackage.values()) { - if (!mTrustedPackages.contains(pkgRules.mPkg)) { - deletionCandidates.add(pkgRules); - } - } - deletionCandidates.sort(Comparator.comparingInt(PackageRules::dataSize).reversed()); - - evictPackagesFromConfig(config, deletionCandidates, totalSize); - } - } - - private static void evictPackagesFromConfig(ZenModeConfig config, - List<PackageRules> deletionCandidates, int currentSize) { - while (currentSize > MAXIMUM_PARCELED_SIZE && !deletionCandidates.isEmpty()) { - PackageRules rulesToDelete = deletionCandidates.removeFirst(); - Slog.w(TAG, String.format("Evicting %s zen rules from package '%s' (%s bytes)", - rulesToDelete.mRules.size(), rulesToDelete.mPkg, rulesToDelete.dataSize())); - - for (ZenModeConfig.ZenRule rule : rulesToDelete.mRules) { - config.automaticRules.remove(rule.id); - } - - currentSize -= rulesToDelete.dataSize(); - } - } - - private static class PackageRules { - private final String mPkg; - private final List<ZenModeConfig.ZenRule> mRules; - private int mParceledSize = -1; - - PackageRules(String pkg) { - mPkg = pkg; - mRules = new ArrayList<>(); - } - - private int dataSize() { - if (mParceledSize >= 0) { - return mParceledSize; - } - Parcel parcel = Parcel.obtain(); - try { - parcel.writeParcelableList(mRules, 0); - mParceledSize = parcel.dataSize(); - return mParceledSize; - } finally { - parcel.recycle(); - } - } - } -} diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 8b09c2acb96a..889df512dd60 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -48,7 +48,6 @@ import static android.service.notification.ZenModeConfig.isImplicitRuleId; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.notification.Flags.preventZenDeviceEffectsWhileDriving; -import static com.android.server.notification.Flags.limitZenConfigSize; import static java.util.Objects.requireNonNull; @@ -193,7 +192,6 @@ public class ZenModeHelper { private final ConditionProviders.Config mServiceConfig; private final SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver; private final ZenModeEventLogger mZenModeEventLogger; - private final ZenConfigTrimmer mConfigTrimmer; @VisibleForTesting protected int mZenMode; @VisibleForTesting protected NotificationManager.Policy mConsolidatedPolicy; @@ -228,7 +226,6 @@ public class ZenModeHelper { mClock = clock; addCallback(mMetrics); mAppOps = context.getSystemService(AppOpsManager.class); - mConfigTrimmer = new ZenConfigTrimmer(mContext); mDefaultConfig = Flags.modesUi() ? ZenModeConfig.getDefaultConfig() @@ -2064,20 +2061,20 @@ public class ZenModeHelper { Log.w(TAG, "Invalid config in setConfigLocked; " + config); return false; } - if (limitZenConfigSize() && (origin == ORIGIN_APP || origin == ORIGIN_USER_IN_APP)) { - mConfigTrimmer.trimToMaximumSize(config); - } - if (config.user != mUser) { // simply store away for background users - mConfigs.put(config.user, config); + synchronized (mConfigLock) { + mConfigs.put(config.user, config); + } if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user); return true; } // handle CPS backed conditions - danger! may modify config mConditions.evaluateConfig(config, null, false /*processSubscriptions*/); - mConfigs.put(config.user, config); + synchronized (mConfigLock) { + mConfigs.put(config.user, config); + } if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable()); ZenLog.traceConfig(origin, reason, triggeringComponent, mConfig, config, callingUid); diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 346d65a06cc9..76cd5c88b388 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -212,16 +212,6 @@ flag { } flag { - name: "limit_zen_config_size" - namespace: "systemui" - description: "Enforce a maximum (serialized) size for the Zen configuration" - bug: "387498139" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "managed_services_concurrent_multiuser" namespace: "systemui" description: "Enables ManagedServices to support Concurrent multi user environment" diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index 1fda4782fc86..92e8eb9cd1bb 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -133,6 +133,38 @@ ] }, { + "name": "CtsPackageInstallerCUJInstallationViaIntentForResultTestCases", + "file_patterns": [ + "core/java/.*Install.*", + "services/core/.*Install.*", + "services/core/java/com/android/server/pm/.*" + ], + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJInstallationViaSessionTestCases", + "file_patterns": [ + "core/java/.*Install.*", + "services/core/.*Install.*", + "services/core/java/com/android/server/pm/.*" + ], + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJMultiUsersTestCases", "file_patterns": [ "core/java/.*Install.*", @@ -288,6 +320,38 @@ ] }, { + "name": "CtsPackageInstallerCUJInstallationViaIntentForResultTestCases", + "file_patterns": [ + "core/java/.*Install.*", + "services/core/.*Install.*", + "services/core/java/com/android/server/pm/.*" + ], + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { + "name": "CtsPackageInstallerCUJInstallationViaSessionTestCases", + "file_patterns": [ + "core/java/.*Install.*", + "services/core/.*Install.*", + "services/core/java/com/android/server/pm/.*" + ], + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJMultiUsersTestCases", "file_patterns": [ "core/java/.*Install.*", diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 092ec8ef4a8a..233b577e1c61 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1078,7 +1078,7 @@ public class UserManagerService extends IUserManager.Stub { mUserVisibilityMediator = new UserVisibilityMediator(mHandler); mUserDataPreparer = userDataPreparer; mUserTypes = UserTypeFactory.getUserTypes(); - invalidateOwnerNameIfNecessary(context.getResources(), true /* forceUpdate */); + invalidateOwnerNameIfNecessary(getContextResources(), true /* forceUpdate */); synchronized (mPackagesLock) { mUsersDir = new File(dataDir, USER_INFO_DIR); mUsersDir.mkdirs(); @@ -1184,6 +1184,15 @@ public class UserManagerService extends IUserManager.Stub { && android.multiuser.Flags.enablePrivateSpaceFeatures(); } + private Resources getSystemResources() { + return android.multiuser.Flags.useUnifiedResources() + ? getContextResources() : Resources.getSystem(); + } + + private Resources getContextResources() { + return mContext.getResources(); + } + /** * This method retrieves the {@link UserManagerInternal} only for the purpose of * PackageManagerService construction. @@ -1223,7 +1232,7 @@ public class UserManagerService extends IUserManager.Stub { // Avoid marking pre-created users for removal. return; } - if (ui.lastLoggedInTime == 0 && ui.isGuest() && Resources.getSystem().getBoolean( + if (ui.lastLoggedInTime == 0 && ui.isGuest() && getSystemResources().getBoolean( com.android.internal.R.bool.config_guestUserAutoCreated)) { // Avoid marking auto-created but not-yet-logged-in guest user for removal. Because a // new one will be created anyway, and this one doesn't have any personal data in it yet @@ -1402,7 +1411,7 @@ public class UserManagerService extends IUserManager.Stub { } if (isHeadlessSystemUserMode()) { - final int bootStrategy = mContext.getResources() + final int bootStrategy = getContextResources() .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); switch (bootStrategy) { case BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER: @@ -2983,7 +2992,7 @@ public class UserManagerService extends IUserManager.Stub { boolean isUserSwitcherEnabled(@UserIdInt int userId) { boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.USER_SWITCHER_ENABLED, - Resources.getSystem().getBoolean(com.android.internal + getSystemResources().getBoolean(com.android.internal .R.bool.config_showUserSwitcherByDefault) ? 1 : 0) != 0; return UserManager.supportsMultipleUsers() @@ -4672,7 +4681,7 @@ public class UserManagerService extends IUserManager.Stub { UserData userData = getUserDataNoChecks(UserHandle.USER_SYSTEM); if ("Primary".equals(userData.info.name)) { userData.info.name = - mContext.getResources().getString(com.android.internal.R.string.owner_name); + getContextResources().getString(com.android.internal.R.string.owner_name); userIdsToWrite.add(userData.info.id); } userVersion = 1; @@ -5002,7 +5011,7 @@ public class UserManagerService extends IUserManager.Stub { final Bundle restrictions = new Bundle(); try { - final String[] defaultFirstUserRestrictions = mContext.getResources().getStringArray( + final String[] defaultFirstUserRestrictions = getContextResources().getStringArray( com.android.internal.R.array.config_defaultFirstUserRestrictions); for (String userRestriction : defaultFirstUserRestrictions) { if (UserRestrictionsUtils.isValidRestriction(userRestriction)) { @@ -6178,7 +6187,7 @@ public class UserManagerService extends IUserManager.Stub { // If the user switch hasn't been explicitly toggled on or off by the user, turn it on. if (android.provider.Settings.Global.getString(mContext.getContentResolver(), android.provider.Settings.Global.USER_SWITCHER_ENABLED) == null) { - if (Resources.getSystem().getBoolean( + if (getSystemResources().getBoolean( com.android.internal.R.bool.config_enableUserSwitcherUponUserCreation)) { android.provider.Settings.Global.putInt(mContext.getContentResolver(), android.provider.Settings.Global.USER_SWITCHER_ENABLED, 1); @@ -7490,7 +7499,6 @@ public class UserManagerService extends IUserManager.Stub { final long now = System.currentTimeMillis(); final long nowRealtime = SystemClock.elapsedRealtime(); final StringBuilder sb = new StringBuilder(); - final Resources resources = Resources.getSystem(); if (args != null && args.length > 0) { switch (args[0]) { @@ -7573,13 +7581,14 @@ public class UserManagerService extends IUserManager.Stub { pw.println(); int effectiveMaxSupportedUsers = UserManager.getMaxSupportedUsers(); pw.print(" Max users: " + effectiveMaxSupportedUsers); - int defaultMaxSupportedUsers = resources.getInteger(R.integer.config_multiuserMaximumUsers); + int defaultMaxSupportedUsers = getSystemResources() + .getInteger(R.integer.config_multiuserMaximumUsers); if (effectiveMaxSupportedUsers != defaultMaxSupportedUsers) { pw.print(" (built-in value: " + defaultMaxSupportedUsers + ")"); } pw.println(" (limit reached: " + isUserLimitReached() + ")"); pw.println(" Supports switchable users: " + UserManager.supportsMultipleUsers()); - pw.println(" All guests ephemeral: " + resources.getBoolean( + pw.println(" All guests ephemeral: " + getSystemResources().getBoolean( com.android.internal.R.bool.config_guestUserEphemeral)); pw.println(" Force ephemeral users: " + mForceEphemeralUsers); final boolean isHeadlessSystemUserMode = isHeadlessSystemUserMode(); @@ -7594,7 +7603,7 @@ public class UserManagerService extends IUserManager.Stub { } } if (isHeadlessSystemUserMode) { - pw.println(" Can switch to headless system user: " + resources + pw.println(" Can switch to headless system user: " + getSystemResources() .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser)); } pw.println(" User version: " + mUserVersion); @@ -8536,8 +8545,7 @@ public class UserManagerService extends IUserManager.Stub { * or downgraded to non-admin status. */ public boolean isMainUserPermanentAdmin() { - return Resources.getSystem() - .getBoolean(R.bool.config_isMainUserPermanentAdmin); + return getSystemResources().getBoolean(R.bool.config_isMainUserPermanentAdmin); } /** @@ -8546,8 +8554,7 @@ public class UserManagerService extends IUserManager.Stub { * it is not a full user. */ public boolean canSwitchToHeadlessSystemUser() { - return Resources.getSystem() - .getBoolean(R.bool.config_canSwitchToHeadlessSystemUser); + return getSystemResources().getBoolean(R.bool.config_canSwitchToHeadlessSystemUser); } /** diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index c31c287017c3..4ffdb1124571 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -235,15 +235,12 @@ public final class PermissionPolicyService extends SystemService { this::synchronizeUidPermissionsAndAppOpsAsync); mAppOpsCallback = new IAppOpsCallback.Stub() { - public void opChanged(int op, int uid, @Nullable String packageName, - String persistentDeviceId) { + public void opChanged(int op, int uid, String packageName, String persistentDeviceId) { if (!Objects.equals(persistentDeviceId, - VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) { + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT) || uid < 0) { return; } - if (packageName != null) { - synchronizeUidPermissionsAndAppOpsAsync(uid); - } + synchronizeUidPermissionsAndAppOpsAsync(uid); resetAppOpPermissionsIfNotRequestedForUidAsync(uid); } }; diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 102dc071c7b6..417b5c7ae62b 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -16,6 +16,7 @@ package com.android.server.power; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; @@ -36,8 +37,8 @@ import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Bundle; import android.os.Handler; -import android.os.IWakeLockCallback; import android.os.IScreenTimeoutPolicyListener; +import android.os.IWakeLockCallback; import android.os.Looper; import android.os.Message; import android.os.PowerManager; @@ -67,6 +68,7 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.input.InputManagerInternal; @@ -147,7 +149,8 @@ public class Notifier { @Nullable private final StatusBarManagerInternal mStatusBarManagerInternal; private final TrustManager mTrustManager; private final Vibrator mVibrator; - private final WakeLockLog mWakeLockLog; + @NonNull private final WakeLockLog mPartialWakeLockLog; + @NonNull private final WakeLockLog mFullWakeLockLog; private final DisplayManagerInternal mDisplayManagerInternal; private final NotifierHandler mHandler; @@ -250,7 +253,9 @@ public class Notifier { mShowWirelessChargingAnimationConfig = context.getResources().getBoolean( com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim); - mWakeLockLog = mInjector.getWakeLockLog(context); + mFullWakeLockLog = mInjector.getWakeLockLog(context); + mPartialWakeLockLog = mInjector.getWakeLockLog(context); + // Initialize interactive state for battery stats. try { mBatteryStats.noteInteractive(true); @@ -324,7 +329,8 @@ public class Notifier { // Ignore } } - mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, /*eventTime=*/ -1); + getWakeLockLog(flags).onWakeLockAcquired(tag, + getUidForWakeLockLog(ownerUid, workSource), flags, /*eventTime=*/ -1); } mWakefulnessSessionObserver.onWakeLockAcquired(flags); } @@ -473,7 +479,8 @@ public class Notifier { // Ignore } } - mWakeLockLog.onWakeLockReleased(tag, ownerUid, /*eventTime=*/ -1); + getWakeLockLog(flags).onWakeLockReleased(tag, + getUidForWakeLockLog(ownerUid, workSource), /*eventTime=*/ -1); } mWakefulnessSessionObserver.onWakeLockReleased(flags, releaseReason); } @@ -960,11 +967,18 @@ public class Notifier { * @param pw The stream to print to. */ public void dump(PrintWriter pw) { - if (mWakeLockLog != null) { - mWakeLockLog.dump(pw); - } + pw.println("Notifier:"); + + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + ipw.println("Partial Wakelock Log:"); + mPartialWakeLockLog.dump(ipw); + + ipw.println(""); + ipw.println("Full Wakelock Log:"); + mFullWakeLockLog.dump(ipw); - mWakefulnessSessionObserver.dump(pw); + ipw.println(""); + mWakefulnessSessionObserver.dump(ipw); } private void updatePendingBroadcastLocked() { @@ -1232,7 +1246,9 @@ public class Notifier { // Do Nothing } } - mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, currentTime); + + getWakeLockLog(flags).onWakeLockAcquired(tag, getUidForWakeLockLog(ownerUid, workSource), + flags, currentTime); } @SuppressLint("AndroidFrameworkRequiresPermission") @@ -1253,7 +1269,8 @@ public class Notifier { // Ignore } } - mWakeLockLog.onWakeLockReleased(tag, ownerUid, currentTime); + getWakeLockLog(flags).onWakeLockReleased(tag, getUidForWakeLockLog(ownerUid, workSource), + currentTime); } @SuppressLint("AndroidFrameworkRequiresPermission") @@ -1419,6 +1436,15 @@ public class Notifier { } } + private @NonNull WakeLockLog getWakeLockLog(int flags) { + return PowerManagerService.isScreenLock(flags) ? mFullWakeLockLog : mPartialWakeLockLog; + } + + private int getUidForWakeLockLog(int ownerUid, WorkSource workSource) { + int attributionUid = workSource != null ? workSource.getAttributionUid() : -1; + return attributionUid != -1 ? attributionUid : ownerUid; + } + private final class NotifierHandler extends Handler { public NotifierHandler(Looper looper) { @@ -1501,7 +1527,7 @@ public class Notifier { /** * Gets the WakeLockLog object */ - WakeLockLog getWakeLockLog(Context context); + @NonNull WakeLockLog getWakeLockLog(Context context); /** * Gets the AppOpsManager system service @@ -1522,7 +1548,7 @@ public class Notifier { } @Override - public WakeLockLog getWakeLockLog(Context context) { + public @NonNull WakeLockLog getWakeLockLog(Context context) { return new WakeLockLog(context); } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index dd454cd61b2c..3eac4b54cd2b 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -1723,9 +1723,16 @@ public final class PowerManagerService extends SystemService } } - @SuppressWarnings("deprecation") private static boolean isScreenLock(final WakeLock wakeLock) { - switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + return isScreenLock(wakeLock.mFlags); + } + + /** + * Returns if a wakelock flag corresponds to a screen wake lock. + */ + @SuppressWarnings("deprecation") + public static boolean isScreenLock(int flags) { + switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) { case PowerManager.FULL_WAKE_LOCK: case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: case PowerManager.SCREEN_DIM_WAKE_LOCK: diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java index eda222e71c9e..7f152d6fc9fa 100644 --- a/services/core/java/com/android/server/power/WakeLockLog.java +++ b/services/core/java/com/android/server/power/WakeLockLog.java @@ -81,11 +81,12 @@ final class WakeLockLog { private static final int TYPE_ACQUIRE = 0x1; private static final int TYPE_RELEASE = 0x2; private static final int MAX_LOG_ENTRY_BYTE_SIZE = 9; - private static final int LOG_SIZE = 1024 * 10; + private static final int LOG_SIZE = 1024 * 3; private static final int LOG_SIZE_MIN = MAX_LOG_ENTRY_BYTE_SIZE + 1; - private static final int TAG_DATABASE_SIZE = 128; + private static final int TAG_DATABASE_SIZE = 64; private static final int TAG_DATABASE_SIZE_MAX = 128; + private static final int TAG_DATABASE_STARTING_SIZE = 16; private static final int LEVEL_SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK = 0; private static final int LEVEL_PARTIAL_WAKE_LOCK = 1; @@ -182,7 +183,7 @@ final class WakeLockLog { * @param pw The {@code PrintWriter} to write to. */ public void dump(PrintWriter pw) { - dump(pw, false); + dump(pw, /* includeTagDb= */ true); } @VisibleForTesting @@ -1161,15 +1162,16 @@ final class WakeLockLog { */ static class TagDatabase { private final int mInvalidIndex; - private final TagData[] mArray; + private final int mMaxArraySize; + private TagData[] mArray; private Callback mCallback; TagDatabase(Injector injector) { - int size = Math.min(injector.getTagDatabaseSize(), TAG_DATABASE_SIZE_MAX); - - // Largest possible index used as "INVALID", hence the (size - 1) sizing. - mArray = new TagData[size - 1]; - mInvalidIndex = size - 1; + // Largest possible index used as "INVALID", hence the (size - 1) sizing + mMaxArraySize = Math.min(injector.getTagDatabaseSize(), TAG_DATABASE_SIZE_MAX - 1); + int startingSize = Math.min(mMaxArraySize, injector.getTagDatabaseStartingSize()); + mArray = new TagData[startingSize]; + mInvalidIndex = mMaxArraySize; } @Override @@ -1195,8 +1197,10 @@ final class WakeLockLog { sb.append(", entries: ").append(entries); sb.append(", Bytes used: ").append(byteEstimate); if (DEBUG) { - sb.append(", Avg tag size: ").append(tagSize / tags); - sb.append("\n ").append(Arrays.toString(mArray)); + sb.append(", Avg tag size: ").append(tags == 0 ? 0 : (tagSize / tags)); + for (int i = 0; i < mArray.length; i++) { + sb.append("\n [").append(i).append("] ").append(mArray[i]); + } } return sb.toString(); } @@ -1284,6 +1288,18 @@ final class WakeLockLog { return null; } + // We don't have a spot available, see if we can still increase the array size + if (firstAvailable == -1) { + if (mArray.length < mMaxArraySize) { + int oldSize = mArray.length; + int newSize = Math.min(oldSize * 2, mMaxArraySize); + TagData[] newArray = new TagData[newSize]; + System.arraycopy(mArray, 0, newArray, 0, oldSize); + mArray = newArray; + firstAvailable = oldSize; + } + } + // If we need to remove an index, report to listeners that we are removing an index. boolean useOldest = firstAvailable == -1; if (useOldest && mCallback != null) { @@ -1402,6 +1418,10 @@ final class WakeLockLog { return TAG_DATABASE_SIZE; } + public int getTagDatabaseStartingSize() { + return TAG_DATABASE_STARTING_SIZE; + } + public int getLogSize() { return LOG_SIZE; } diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java index ebc50fd85f24..52d4555248ce 100644 --- a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java +++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java @@ -67,6 +67,10 @@ public class PowerManagerFlags { new FlagState(Flags.FLAG_WAKELOCK_ATTRIBUTION_VIA_WORKCHAIN, Flags::wakelockAttributionViaWorkchain); + private final FlagState mDisableFrozenProcessWakelocks = + new FlagState(Flags.FLAG_DISABLE_FROZEN_PROCESS_WAKELOCKS, + Flags::disableFrozenProcessWakelocks); + /** Returns whether early-screen-timeout-detector is enabled on not. */ public boolean isEarlyScreenTimeoutDetectorEnabled() { return mEarlyScreenTimeoutDetectorFlagState.isEnabled(); @@ -121,6 +125,13 @@ public class PowerManagerFlags { } /** + * @return Whether the feature to disable the frozen process wakelocks is enabled + */ + public boolean isDisableFrozenProcessWakelocksEnabled() { + return mDisableFrozenProcessWakelocks.isEnabled(); + } + + /** * dumps all flagstates * @param pw printWriter */ @@ -132,6 +143,7 @@ public class PowerManagerFlags { pw.println(" " + mFrameworkWakelockInfo); pw.println(" " + mMoveWscLoggingToNotifier); pw.println(" " + mWakelockAttributionViaWorkchain); + pw.println(" " + mDisableFrozenProcessWakelocks); } private static class FlagState { diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig index fefe195dc337..ad8ec0354aa6 100644 --- a/services/core/java/com/android/server/power/feature/power_flags.aconfig +++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig @@ -70,3 +70,10 @@ flag { description: "Feature flag to move logging of WakelockStateChanged atoms from BatteryStatsImpl to Notifier." bug: "352602149" } + +flag { + name: "disable_frozen_process_wakelocks" + namespace: "power" + description: "Feature flag to disable/enable wakelocks of a process when it is frozen/unfrozen" + bug: "291115867" +} diff --git a/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java index 799157520ca5..800fc7c25de5 100644 --- a/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java +++ b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java @@ -23,6 +23,7 @@ import android.content.ComponentName; import android.content.Context; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.os.Binder; import android.os.Environment; import android.util.AtomicFile; import android.util.Slog; @@ -119,7 +120,7 @@ class CertificateRevocationStatusManager { } catch (IOException | JSONException ex) { Slog.d(TAG, "Fallback to check stored revocation status", ex); if (ex instanceof IOException && mShouldScheduleJob) { - scheduleJobToFetchRemoteRevocationJob(); + Binder.withCleanCallingIdentity(this::scheduleJobToFetchRemoteRevocationJob); } try { revocationList = getStoredRevocationList(); @@ -210,7 +211,7 @@ class CertificateRevocationStatusManager { return; } Slog.d(TAG, "Scheduling job to fetch remote CRL."); - jobScheduler.schedule( + jobScheduler.forNamespace(TAG).schedule( new JobInfo.Builder( JOB_ID, new ComponentName( 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 93fd2768d13e..6872ca9e46ee 100644 --- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java +++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java @@ -23,7 +23,9 @@ import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.SharedPreferences; import android.os.Binder; +import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -32,14 +34,21 @@ import android.os.PermissionEnforcer; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; +import android.os.UserHandle; import android.provider.Settings; import android.security.advancedprotection.AdvancedProtectionFeature; +import android.security.advancedprotection.AdvancedProtectionManager; +import android.security.advancedprotection.AdvancedProtectionManager.FeatureId; +import android.security.advancedprotection.AdvancedProtectionManager.SupportDialogType; import android.security.advancedprotection.IAdvancedProtectionCallback; import android.security.advancedprotection.IAdvancedProtectionService; +import android.security.advancedprotection.AdvancedProtectionProtoEnums; import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -51,7 +60,9 @@ import com.android.server.security.advancedprotection.features.DisallowInstallUn import com.android.server.security.advancedprotection.features.MemoryTaggingExtensionHook; import com.android.server.security.advancedprotection.features.UsbDataAdvancedProtectionHook; +import java.io.File; import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -61,6 +72,15 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub private static final int MODE_CHANGED = 0; private static final int CALLBACK_ADDED = 1; + // Shared preferences keys + private static final String PREFERENCE = "advanced_protection_preference"; + private static final String ENABLED_CHANGE_TIME = "enabled_change_time"; + private static final String LAST_DIALOG_FEATURE_ID = "last_dialog_feature_id"; + private static final String LAST_DIALOG_TYPE = "last_dialog_type"; + private static final String LAST_DIALOG_HOURS_SINCE_ENABLED = "last_dialog_hours_since_enabled"; + private static final String LAST_DIALOG_LEARN_MORE_CLICKED = "last_dialog_learn_more_clicked"; + private static final long MILLIS_PER_HOUR = 60 * 60 * 1000; + private final Context mContext; private final Handler mHandler; private final AdvancedProtectionStore mStore; @@ -72,6 +92,10 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub // For tracking only - not called on state change private final ArrayList<AdvancedProtectionProvider> mProviders = new ArrayList<>(); + // Used to store logging data + private SharedPreferences mSharedPreferences; + private boolean mEmitLogs = true; + private AdvancedProtectionService(@NonNull Context context) { super(PermissionEnforcer.fromContext(context)); mContext = context; @@ -126,6 +150,8 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub if (provider != null) { mProviders.add(provider); } + + mEmitLogs = false; } @Override @@ -178,7 +204,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub if (enabled != isAdvancedProtectionEnabledInternal()) { mStore.store(enabled); sendModeChanged(enabled); - Slog.i(TAG, "Advanced protection is " + (enabled ? "enabled" : "disabled")); + logAdvancedProtectionEnabled(enabled); } } } finally { @@ -188,6 +214,91 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub @Override @EnforcePermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE) + public void logDialogShown(@FeatureId int featureId, @SupportDialogType int type, + boolean learnMoreClicked) { + logDialogShown_enforcePermission(); + + if (!mEmitLogs) { + return; + } + + int hoursSinceEnabled = hoursSinceLastChange(); + FrameworkStatsLog.write(FrameworkStatsLog.ADVANCED_PROTECTION_SUPPORT_DIALOG_DISPLAYED, + /*feature_id*/ featureIdToLogEnum(featureId), + /*dialogue_type*/ dialogueTypeToLogEnum(type), + /*learn_more_clicked*/ learnMoreClicked, + /*hours_since_last_change*/ hoursSinceEnabled); + + getSharedPreferences().edit() + .putInt(LAST_DIALOG_FEATURE_ID, featureId) + .putInt(LAST_DIALOG_TYPE, type) + .putBoolean(LAST_DIALOG_LEARN_MORE_CLICKED, learnMoreClicked) + .putInt(LAST_DIALOG_HOURS_SINCE_ENABLED, hoursSinceEnabled) + .apply(); + } + + private int featureIdToLogEnum(@FeatureId int featureId) { + switch (featureId) { + case AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G: + return AdvancedProtectionProtoEnums.FEATURE_ID_DISALLOW_CELLULAR_2G; + case AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES: + return AdvancedProtectionProtoEnums.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES; + case AdvancedProtectionManager.FEATURE_ID_DISALLOW_USB: + return AdvancedProtectionProtoEnums.FEATURE_ID_DISALLOW_USB; + case AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP: + return AdvancedProtectionProtoEnums.FEATURE_ID_DISALLOW_WEP; + case AdvancedProtectionManager.FEATURE_ID_ENABLE_MTE: + return AdvancedProtectionProtoEnums.FEATURE_ID_ENABLE_MTE; + default: + return AdvancedProtectionProtoEnums.FEATURE_ID_UNKNOWN; + } + } + + private int dialogueTypeToLogEnum(@SupportDialogType int type) { + switch (type) { + case AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_UNKNOWN: + return AdvancedProtectionProtoEnums.DIALOGUE_TYPE_UNKNOWN; + case AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION: + return AdvancedProtectionProtoEnums.DIALOGUE_TYPE_BLOCKED_INTERACTION; + case AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_DISABLED_SETTING: + return AdvancedProtectionProtoEnums.DIALOGUE_TYPE_DISABLED_SETTING; + default: + return AdvancedProtectionProtoEnums.DIALOGUE_TYPE_UNKNOWN; + } + } + + private void logAdvancedProtectionEnabled(boolean enabled) { + if (!mEmitLogs) { + return; + } + + Slog.i(TAG, "Advanced protection has been " + (enabled ? "enabled" : "disabled")); + SharedPreferences prefs = getSharedPreferences(); + FrameworkStatsLog.write(FrameworkStatsLog.ADVANCED_PROTECTION_STATE_CHANGED, + /*enabled*/ enabled, + /*hours_since_enabled*/ hoursSinceLastChange(), + /*last_dialog_feature_id*/ featureIdToLogEnum( + prefs.getInt(LAST_DIALOG_FEATURE_ID, -1)), + /*_type*/ dialogueTypeToLogEnum(prefs.getInt(LAST_DIALOG_TYPE, -1)), + /*_learn_more_clicked*/ prefs.getBoolean(LAST_DIALOG_LEARN_MORE_CLICKED, false), + /*_hours_since_enabled*/ prefs.getInt(LAST_DIALOG_HOURS_SINCE_ENABLED, -1)); + prefs.edit() + .putLong(ENABLED_CHANGE_TIME, System.currentTimeMillis()) + .apply(); + } + + private int hoursSinceLastChange() { + int hoursSinceEnabled = -1; + long lastChangeTimeMillis = getSharedPreferences().getLong(ENABLED_CHANGE_TIME, -1); + if (lastChangeTimeMillis != -1) { + hoursSinceEnabled = (int) + ((System.currentTimeMillis() - lastChangeTimeMillis) / MILLIS_PER_HOUR); + } + return hoursSinceEnabled; + } + + @Override + @EnforcePermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE) public List<AdvancedProtectionFeature> getAdvancedProtectionFeatures() { getAdvancedProtectionFeatures_enforcePermission(); List<AdvancedProtectionFeature> features = new ArrayList<>(); @@ -213,6 +324,30 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub .exec(this, in, out, err, args, callback, resultReceiver); } + @Override + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; + writer.println("AdvancedProtectionService"); + writer.println(" isAdvancedProtectionEnabled: " + isAdvancedProtectionEnabledInternal()); + writer.println(" mHooks.size(): " + mHooks.size()); + writer.println(" mCallbacks.size(): " + mCallbacks.size()); + writer.println(" mProviders.size(): " + mProviders.size()); + + writer.println("Hooks: "); + mHooks.stream().forEach(hook -> { + writer.println(" " + hook.getClass().getSimpleName() + + " available: " + hook.isAvailable()); + }); + writer.println(" Providers: "); + mProviders.stream().forEach(provider -> { + writer.println(" " + provider.getClass().getSimpleName()); + provider.getFeatures().stream().forEach(feature -> { + writer.println(" " + feature.getClass().getSimpleName()); + }); + }); + writer.println(" mSharedPreferences: " + getSharedPreferences().getAll()); + } + void sendModeChanged(boolean enabled) { Message.obtain(mHandler, MODE_CHANGED, /*enabled*/ enabled ? 1 : 0, /*unused */ -1) .sendToTarget(); @@ -224,6 +359,22 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub .sendToTarget(); } + private SharedPreferences getSharedPreferences() { + if (mSharedPreferences == null) { + initSharedPreferences(); + } + return mSharedPreferences; + } + + private synchronized void initSharedPreferences() { + if (mSharedPreferences == null) { + Context deviceContext = mContext.createDeviceProtectedStorageContext(); + File sharedPrefs = new File(Environment.getDataSystemDirectory(), PREFERENCE); + mSharedPreferences = deviceContext.getSharedPreferences(sharedPrefs, + Context.MODE_PRIVATE); + } + } + public static final class Lifecycle extends SystemService { private final AdvancedProtectionService mService; diff --git a/services/core/java/com/android/server/sensors/OWNERS b/services/core/java/com/android/server/sensors/OWNERS new file mode 100644 index 000000000000..6b2247331a33 --- /dev/null +++ b/services/core/java/com/android/server/sensors/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/native:/services/sensorservice/OWNERS diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java index 7ff4ade1101c..9636cc6c77a7 100644 --- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java +++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java @@ -17,6 +17,7 @@ package com.android.server.sensors; import android.annotation.NonNull; +import android.annotation.Nullable; import android.hardware.SensorDirectChannel; import android.os.ParcelFileDescriptor; @@ -71,7 +72,7 @@ public abstract class SensorManagerInternal { /** * Sends an event for the runtime sensor with the given handle to the framework. * - * Only relevant for sending runtime sensor events. @see #createRuntimeSensor. + * <p>Only relevant for sending runtime sensor events. @see #createRuntimeSensor.</p> * * @param handle The sensor handle. * @param type The type of the sensor. @@ -83,6 +84,21 @@ public abstract class SensorManagerInternal { @NonNull float[] values); /** + * Sends an additional info event for the runtime sensor with the given handle to the framework. + * + * <p>Only relevant for runtime sensors. @see #createRuntimeSensor.</p> + * + * @param handle The sensor handle. + * @param type The type of payload data. + * @param serial The sequence number of this frame for this type. + * @param timestampNanos Timestamp of the event. + * @param values The payload data represented in float values. + * @return Whether the event injection was successful. + */ + public abstract boolean sendSensorAdditionalInfo(int handle, int type, int serial, + long timestampNanos, @Nullable float[] values); + + /** * Listener for proximity sensor state changes. */ public interface ProximityActiveListener { diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java index 3de191030d71..0d31b22e2020 100644 --- a/services/core/java/com/android/server/sensors/SensorService.java +++ b/services/core/java/com/android/server/sensors/SensorService.java @@ -19,6 +19,7 @@ package com.android.server.sensors; import static com.android.server.sensors.SensorManagerInternal.ProximityActiveListener; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.util.ArrayMap; @@ -62,6 +63,9 @@ public class SensorService extends SystemService { private static native void unregisterRuntimeSensorNative(long ptr, int handle); private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type, long timestampNanos, float[] values); + private static native boolean sendRuntimeSensorAdditionalInfoNative(long ptr, int handle, + int type, int serial, long timestampNanos, float[] values); + public SensorService(Context ctx) { super(ctx); @@ -129,6 +133,18 @@ public class SensorService extends SystemService { } @Override + public boolean sendSensorAdditionalInfo(int handle, int type, int serial, + long timestampNanos, @Nullable float[] values) { + synchronized (mLock) { + if (!mRuntimeSensorHandles.contains(handle)) { + return false; + } + return sendRuntimeSensorAdditionalInfoNative(mPtr, handle, type, serial, + timestampNanos, values); + } + } + + @Override public void addProximityActiveListener(@NonNull Executor executor, @NonNull ProximityActiveListener listener) { Objects.requireNonNull(executor, "executor must not be null"); diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java index 27ca83addec8..126b3a7c721f 100644 --- a/services/core/java/com/android/server/storage/StorageUserConnection.java +++ b/services/core/java/com/android/server/storage/StorageUserConnection.java @@ -351,18 +351,38 @@ public final class StorageUserConnection { } } + private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception { + waitForAsyncVoid(asyncCall, /*bindIfNotConnected*/ true, + DEFAULT_REMOTE_TIMEOUT_SECONDS); + } + + private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall, + boolean bindIfNotConnected, int timeoutSeconds) throws Exception { CompletableFuture<Void> opFuture = new CompletableFuture<>(); RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture)); - waitForAsync(asyncCall, callback, opFuture, mOutstandingOps, - DEFAULT_REMOTE_TIMEOUT_SECONDS); + waitForAsync(asyncCall, callback, opFuture, mOutstandingOps, bindIfNotConnected, + timeoutSeconds); } private <T> T waitForAsync(AsyncStorageServiceCall asyncCall, RemoteCallback callback, CompletableFuture<T> opFuture, ArrayList<CompletableFuture<T>> outstandingOps, - long timeoutSeconds) throws Exception { - CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded(); + boolean bindIfNotConnected, long timeoutSeconds) throws Exception { + + CompletableFuture<IExternalStorageService> serviceFuture; + if (bindIfNotConnected) { + serviceFuture = connectIfNeeded(); + } else { + synchronized (mLock) { + if (mRemoteFuture == null || mRemoteFuture.getNow(null) == null) { + Slog.w(TAG, "Dropping async request as service is not connected" + + "and request doesn't require connecting"); + return null; + } + serviceFuture = mRemoteFuture; + } + } try { synchronized (mLock) { @@ -404,7 +424,11 @@ public final class StorageUserConnection { public void endSession(Session session) throws ExternalStorageServiceException { try { waitForAsyncVoid((service, callback) -> - service.endSession(session.sessionId, callback)); + service.endSession(session.sessionId, callback), + // endSession shouldn't be trying to bind to remote service if the service + // isn't connected already as this means that no previous mounting has been + // completed. + /*bindIfNotConnected*/ false, /*timeoutSeconds*/ 10); } catch (Exception e) { throw new ExternalStorageServiceException("Failed to end session: " + session, e); } @@ -415,7 +439,11 @@ public final class StorageUserConnection { ExternalStorageServiceException { try { waitForAsyncVoid((service, callback) -> - service.notifyVolumeStateChanged(sessionId, vol, callback)); + service.notifyVolumeStateChanged(sessionId, vol, callback), + // notifyVolumeStateChanged shouldn't be trying to bind to remote service + // if the service isn't connected already as this means that + // no previous mounting has been completed + /*bindIfNotConnected*/ false, /*timeoutSeconds*/ 10); } catch (Exception e) { throw new ExternalStorageServiceException("Failed to notify volume state changed " + "for vol : " + vol, e); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java index 6e640d890fb8..424439df3c4b 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java @@ -856,10 +856,14 @@ public class WallpaperCropper { BitmapFactory.decodeFile(wallpaperFile.getAbsolutePath(), options); wallpaperImageSize.set(options.outWidth, options.outHeight); } + boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) + == View.LAYOUT_DIRECTION_RTL; + Rect croppedImageBound = getCrop(displaySize, mDefaultDisplayInfo, wallpaperImageSize, + getRelativeCropHints(wallpaperData), isRtl); - double maxDisplayToImageRatio = Math.max((double) displaySize.x / wallpaperImageSize.x, - (double) displaySize.y / wallpaperImageSize.y); - if (maxDisplayToImageRatio > 1.5) { + double maxDisplayToImageRatio = Math.max((double) displaySize.x / croppedImageBound.width(), + (double) displaySize.y / croppedImageBound.height()); + if (maxDisplayToImageRatio > 1.3) { return false; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index e91d88901751..df00fa195f03 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -625,7 +625,7 @@ final class ActivityRecord extends WindowToken { @VisibleForTesting final TaskFragment.ConfigOverrideHint mResolveConfigHint; - private final boolean mOptOutEdgeToEdge; + final boolean mOptOutEdgeToEdge; private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig; @@ -5590,6 +5590,14 @@ final class ActivityRecord extends WindowToken { commitVisibility(visible, performLayout, false /* fromTransition */); } + /** + * Sets whether safe region bounds are needed for the Activity. This is called from + * {@link ActivityStarter} after the source record is created. + */ + void setNeedsSafeRegionBounds(boolean needsSafeRegionBounds) { + mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(needsSafeRegionBounds); + } + /** Updates draw state and shows drawn windows. */ void commitFinishDrawing(SurfaceControl.Transaction t) { boolean committed = false; @@ -7753,9 +7761,11 @@ final class ActivityRecord extends WindowToken { final AppCompatAspectRatioPolicy aspectRatioPolicy = mAppCompatController.getAspectRatioPolicy(); aspectRatioPolicy.reset(); + final AppCompatSafeRegionPolicy safeRegionPolicy = + mAppCompatController.getSafeRegionPolicy(); mAppCompatController.getLetterboxPolicy().resetFixedOrientationLetterboxEligibility(); mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration, - isFixedRotationTransforming()); + isFixedRotationTransforming(), safeRegionPolicy.getLatestSafeRegionBounds()); // Can't use resolvedConfig.windowConfiguration.getWindowingMode() because it can be // different from windowing mode of the task (PiP) during transition from fullscreen to PiP @@ -7801,6 +7811,13 @@ final class ActivityRecord extends WindowToken { } } + // If activity can be letterboxed due to a safe region only, use the safe region bounds + // as the resolved bounds. We ignore cases where the letterboxing can happen due to other + // app compat conditions and a safe region since the safe region app compat is sandboxed + // earlier in TaskFragment.ConfigOverrideHint.resolveTmpOverrides. + mAppCompatController.getSafeRegionPolicy().resolveSafeRegionBoundsConfigurationIfNeeded( + resolvedConfig, newParentConfiguration); + if (isFixedOrientationLetterboxAllowed || scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance() // In fullscreen, can be letterboxed for aspect ratio. @@ -7980,7 +7997,7 @@ final class ActivityRecord extends WindowToken { mAppCompatController.getSizeCompatModePolicy(); final Rect screenResolvedBounds = scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds); final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride; - final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); + final Rect parentBounds = mResolveConfigHint.mParentBoundsOverride; final float screenResolvedBoundsWidth = screenResolvedBounds.width(); final float parentAppBoundsWidth = parentAppBounds.width(); final boolean isImmersiveMode = isImmersiveMode(parentBounds); @@ -8167,7 +8184,7 @@ final class ActivityRecord extends WindowToken { * in this method. */ private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) { - final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); + final Rect parentBounds = mResolveConfigHint.mParentBoundsOverride; final Rect stableBounds = new Rect(); final Rect outNonDecorBounds = mTmpBounds; // If orientation is respected when insets are applied, then stableBounds will be empty. diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index a84a008f66eb..92f51bed419f 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2203,6 +2203,9 @@ class ActivityStarter { ? mLaunchParams.mPreferredTaskDisplayArea : mRootWindowContainer.getDefaultTaskDisplayArea(); mPreferredWindowingMode = mLaunchParams.mWindowingMode; + if (mLaunchParams.mNeedsSafeRegionBounds != null) { + r.setNeedsSafeRegionBounds(mLaunchParams.mNeedsSafeRegionBounds); + } } private TaskDisplayArea computeSuggestedLaunchDisplayArea( diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index b0563128870a..b7ef1057388c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -36,6 +36,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.ACTION_VIEW; @@ -1634,16 +1635,19 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } } - private void moveHomeRootTaskToFrontIfNeeded(int flags, TaskDisplayArea taskDisplayArea, + @VisibleForTesting + void moveHomeRootTaskToFrontIfNeeded(int flags, TaskDisplayArea taskDisplayArea, String reason) { final Task focusedRootTask = taskDisplayArea.getFocusedRootTask(); if ((taskDisplayArea.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && (flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) - || (focusedRootTask != null && focusedRootTask.isActivityTypeRecents())) { + || (focusedRootTask != null && focusedRootTask.isActivityTypeRecents() + && focusedRootTask.getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW)) { // We move root home task to front when we are on a fullscreen display area and // caller has requested the home activity to move with it. Or the previous root task - // is recents. + // is recents and we are not on multi-window mode. + taskDisplayArea.moveHomeRootTaskToFront(reason); } } @@ -2830,6 +2834,13 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { "startActivityFromRecents: Task " + taskId + " not found."); } + + if (task.getRootTask() != null + && task.getRootTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) { + // Don't move home forward if task is in multi window mode + moveHomeTaskForward = false; + } + if (moveHomeTaskForward) { // We always want to return to the home activity instead of the recents // activity from whatever is started from the recents activity, so move diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java index 3535a96d9c45..d6f058a34367 100644 --- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java @@ -240,7 +240,7 @@ class AppCompatAspectRatioPolicy { final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration(); final Rect parentAppBounds = mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride; - final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); + final Rect parentBounds = mActivityRecord.mResolveConfigHint.mParentBoundsOverride; final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); // Use tmp bounds to calculate aspect ratio so we can know whether the activity should // use restricted size (resolved bounds may be the requested override bounds). diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index c479591a5e0d..28f9a33d65d3 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -33,6 +33,8 @@ class AppCompatController { @NonNull private final AppCompatAspectRatioPolicy mAspectRatioPolicy; @NonNull + private final AppCompatSafeRegionPolicy mSafeRegionPolicy; + @NonNull private final AppCompatReachabilityPolicy mReachabilityPolicy; @NonNull private final DesktopAppCompatAspectRatioPolicy mDesktopAspectRatioPolicy; @@ -62,6 +64,7 @@ class AppCompatController { mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides); mAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord, mTransparentPolicy, mAppCompatOverrides); + mSafeRegionPolicy = new AppCompatSafeRegionPolicy(activityRecord, packageManager); mReachabilityPolicy = new AppCompatReachabilityPolicy(activityRecord, wmService.mAppCompatConfiguration); mLetterboxPolicy = new AppCompatLetterboxPolicy(activityRecord, @@ -90,6 +93,11 @@ class AppCompatController { } @NonNull + AppCompatSafeRegionPolicy getSafeRegionPolicy() { + return mSafeRegionPolicy; + } + + @NonNull DesktopAppCompatAspectRatioPolicy getDesktopAspectRatioPolicy() { return mDesktopAspectRatioPolicy; } @@ -163,6 +171,6 @@ class AppCompatController { getTransparentPolicy().dump(pw, prefix); getLetterboxPolicy().dump(pw, prefix); getSizeCompatModePolicy().dump(pw, prefix); + getSafeRegionPolicy().dump(pw, prefix); } - } diff --git a/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java new file mode 100644 index 000000000000..959609309da1 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java @@ -0,0 +1,167 @@ +/* + * 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.wm; + +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING; + +import android.annotation.NonNull; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.graphics.Rect; + +import java.io.PrintWriter; +import java.util.function.BooleanSupplier; + +/** + * Encapsulate app compat policy logic related to a safe region. + */ +class AppCompatSafeRegionPolicy { + @NonNull + private final ActivityRecord mActivityRecord; + @NonNull + final PackageManager mPackageManager; + // Whether the Activity needs to be in the safe region bounds. + private boolean mNeedsSafeRegionBounds = false; + // Denotes the latest safe region bounds. Can be empty if the activity or the ancestors do + // not have any safe region bounds. + @NonNull + private final Rect mLatestSafeRegionBounds = new Rect(); + // Whether the activity has allowed safe region letterboxing. This can be set through the + // manifest and the default value is true. + @NonNull + private final BooleanSupplier mAllowSafeRegionLetterboxing; + + AppCompatSafeRegionPolicy(@NonNull ActivityRecord activityRecord, + @NonNull PackageManager packageManager) { + mActivityRecord = activityRecord; + mPackageManager = packageManager; + mAllowSafeRegionLetterboxing = AppCompatUtils.asLazy(() -> { + // Application level property. + if (allowSafeRegionLetterboxing(packageManager)) { + return true; + } + // Activity level property. + try { + return packageManager.getPropertyAsUser( + PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING, + mActivityRecord.mActivityComponent.getPackageName(), + mActivityRecord.mActivityComponent.getClassName(), + mActivityRecord.mUserId).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + return true; + } + }); + } + + private boolean allowSafeRegionLetterboxing(PackageManager pm) { + try { + return pm.getPropertyAsUser( + PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING, + mActivityRecord.packageName, + /* className */ null, + mActivityRecord.mUserId).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + return true; + } + } + + /** + * Computes the latest safe region bounds in + * {@link ActivityRecord#resolveOverrideConfiguration(Configuration)} since the activity has not + * been attached to the parent container when the ActivityRecord is instantiated. Note that the + * latest safe region bounds will be empty if activity has not allowed safe region letterboxing. + * + * @return latest safe region bounds as set on an ancestor window container. + */ + public Rect getLatestSafeRegionBounds() { + if (!allowSafeRegionLetterboxing()) { + mLatestSafeRegionBounds.setEmpty(); + return null; + } + // Get the latest safe region bounds since the bounds could have changed + final Rect latestSafeRegionBounds = mActivityRecord.getSafeRegionBounds(); + if (latestSafeRegionBounds != null) { + mLatestSafeRegionBounds.set(latestSafeRegionBounds); + } else { + mLatestSafeRegionBounds.setEmpty(); + } + return latestSafeRegionBounds; + } + + /** + * Computes bounds when letterboxing is required only for the safe region bounds if needed. + */ + public void resolveSafeRegionBoundsConfigurationIfNeeded(@NonNull Configuration resolvedConfig, + @NonNull Configuration newParentConfig) { + if (mLatestSafeRegionBounds.isEmpty()) { + return; + } + // If activity can not be letterboxed for a safe region only or it has not been attached + // to a WindowContainer yet. + if (!isLetterboxedForSafeRegionOnlyAllowed() || mActivityRecord.getParent() == null) { + return; + } + resolvedConfig.windowConfiguration.setBounds(mLatestSafeRegionBounds); + mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfig); + } + + /** + * Safe region bounds can either be applied along with size compat, fixed orientation or + * aspect ratio conditions by sandboxing them to the safe region bounds. Or it can be applied + * independently when no other letterboxing condition is triggered. This method helps detecting + * the latter case. + * + * @return {@code true} if this application or activity has allowed safe region letterboxing and + * can be letterboxed only due to the safe region being set on the current or ancestor window + * container. + */ + boolean isLetterboxedForSafeRegionOnlyAllowed() { + return !mActivityRecord.areBoundsLetterboxed() && getNeedsSafeRegionBounds() + && getLatestSafeRegionBounds() != null; + } + + /** + * Set {@code true} if this activity needs to be within the safe region bounds, else false. + */ + public void setNeedsSafeRegionBounds(boolean needsSafeRegionBounds) { + mNeedsSafeRegionBounds = needsSafeRegionBounds; + } + + /** + * @return {@code true} if this activity needs to be within the safe region bounds. + */ + public boolean getNeedsSafeRegionBounds() { + return mNeedsSafeRegionBounds; + } + + /** @see android.view.WindowManager#PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING */ + boolean allowSafeRegionLetterboxing() { + return mAllowSafeRegionLetterboxing.getAsBoolean(); + } + + void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + if (mNeedsSafeRegionBounds) { + pw.println(prefix + " mNeedsSafeRegionBounds=true"); + } + if (isLetterboxedForSafeRegionOnlyAllowed()) { + pw.println(prefix + " isLetterboxForSafeRegionOnlyAllowed=true"); + } + if (!allowSafeRegionLetterboxing()) { + pw.println(prefix + " allowSafeRegionLetterboxing=false"); + } + } +} diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index b91a12598e01..f872286726c3 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -216,6 +216,7 @@ final class AppCompatUtils { AppCompatCameraPolicy.getCameraCompatFreeformMode(top); appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController .getDesktopAspectRatioPolicy().hasMinAspectRatioOverride(task)); + appCompatTaskInfo.setOptOutEdgeToEdge(top.mOptOutEdgeToEdge); } /** @@ -241,6 +242,10 @@ final class AppCompatUtils { if (aspectRatioPolicy.isLetterboxedForAspectRatioOnly()) { return "ASPECT_RATIO"; } + if (activityRecord.mAppCompatController.getSafeRegionPolicy() + .isLetterboxedForSafeRegionOnlyAllowed()) { + return "SAFE_REGION"; + } return "UNKNOWN_REASON"; } diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index c26acec19743..1c2569251581 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -38,6 +38,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.view.ContentRecordingSession; import android.view.ContentRecordingSession.RecordContent; +import android.window.DesktopExperienceFlags; import android.view.Display; import android.view.DisplayInfo; import android.view.SurfaceControl; @@ -353,6 +354,13 @@ final class ContentRecorder implements WindowContainerListener { return; } + // Recording should not be started on displays that are eligible for hosting tasks. + // See android.view.Display#canHostTasks(). + if (DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue() + && mDisplayContent.mDisplay.canHostTasks()) { + return; + } + if (mContentRecordingSession.isWaitingForConsent() || !isDisplayReadyForMirroring()) { ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: waiting to record, so do " + "nothing"); diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java index fcc697242ff6..b8027546fdbd 100644 --- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java +++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java @@ -38,7 +38,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.protolog.ProtoLog; import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater; -import com.android.window.flags.Flags; import java.util.Arrays; import java.util.Objects; @@ -336,7 +335,6 @@ class DeferredDisplayUpdater { /** Returns {@code true} if the transition will control when to turn on the screen. */ boolean waitForTransition(@NonNull Message screenUnblocker) { - if (!Flags.waitForTransitionOnDisplaySwitch()) return false; if (!mShouldWaitForTransitionWhenScreenOn) { return false; } diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java index 7a959c14fbd2..d9354323ae7c 100644 --- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java +++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java @@ -24,6 +24,8 @@ import static android.content.pm.ActivityInfo.isFixedOrientationPortrait; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static com.android.internal.policy.SystemBarUtils.getDesktopViewAppHeaderHeightPx; +import static com.android.internal.policy.DesktopModeCompatUtils.shouldExcludeCaptionFromAppBounds; import static com.android.server.wm.LaunchParamsUtil.applyLayoutGravity; import static com.android.server.wm.LaunchParamsUtil.calculateLayoutBounds; @@ -64,49 +66,61 @@ public final class DesktopModeBoundsCalculator { */ static void updateInitialBounds(@NonNull Task task, @Nullable WindowLayout layout, @Nullable ActivityRecord activity, @Nullable ActivityOptions options, - @NonNull Rect outBounds, @NonNull Consumer<String> logger) { + @NonNull LaunchParamsController.LaunchParams outParams, + @NonNull Consumer<String> logger) { // Use stable frame instead of raw frame to avoid launching freeform windows on top of // stable insets, which usually are system widgets such as sysbar & navbar. final Rect stableBounds = new Rect(); task.getDisplayArea().getStableRect(stableBounds); - // If the options bounds size is flexible, update size with calculated desired size. + final boolean hasFullscreenOverride = activity != null + && activity.mAppCompatController.getAspectRatioOverrides().hasFullscreenOverride(); + // If the options bounds size is flexible and no fullscreen override has been applied, + // update size with calculated desired size. final boolean updateOptionBoundsSize = options != null - && options.getFlexibleLaunchSize(); + && options.getFlexibleLaunchSize() && !hasFullscreenOverride; // If cascading is also enabled, the position of the options bounds must be respected // during the size update. final boolean shouldRespectOptionPosition = updateOptionBoundsSize && DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue(); + final int captionHeight = activity != null && shouldExcludeCaptionFromAppBounds( + activity.info, task.isResizeable(), activity.mOptOutEdgeToEdge) + ? getDesktopViewAppHeaderHeightPx(activity.mWmService.mContext) : 0; if (options != null && options.getLaunchBounds() != null && !updateOptionBoundsSize) { - outBounds.set(options.getLaunchBounds()); - logger.accept("inherit-from-options=" + outBounds); + outParams.mBounds.set(options.getLaunchBounds()); + logger.accept("inherit-from-options=" + outParams.mBounds); } else if (layout != null) { final int verticalGravity = layout.gravity & Gravity.VERTICAL_GRAVITY_MASK; final int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; if (layout.hasSpecifiedSize()) { - calculateLayoutBounds(stableBounds, layout, outBounds, + calculateLayoutBounds(stableBounds, layout, outParams.mBounds, calculateIdealSize(stableBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE)); - applyLayoutGravity(verticalGravity, horizontalGravity, outBounds, + applyLayoutGravity(verticalGravity, horizontalGravity, outParams.mBounds, stableBounds); logger.accept("layout specifies sizes, inheriting size and applying gravity"); } else if (verticalGravity > 0 || horizontalGravity > 0) { - outBounds.set(calculateInitialBounds(task, activity, stableBounds, options, - shouldRespectOptionPosition)); - applyLayoutGravity(verticalGravity, horizontalGravity, outBounds, + outParams.mBounds.set(calculateInitialBounds(task, activity, stableBounds, options, + shouldRespectOptionPosition, captionHeight)); + applyLayoutGravity(verticalGravity, horizontalGravity, outParams.mBounds, stableBounds); logger.accept("layout specifies gravity, applying desired bounds and gravity"); logger.accept("respecting option bounds cascaded position=" + shouldRespectOptionPosition); } } else { - outBounds.set(calculateInitialBounds(task, activity, stableBounds, options, - shouldRespectOptionPosition)); + outParams.mBounds.set(calculateInitialBounds(task, activity, stableBounds, options, + shouldRespectOptionPosition, captionHeight)); logger.accept("layout not specified, applying desired bounds"); logger.accept("respecting option bounds cascaded position=" + shouldRespectOptionPosition); } + if (updateOptionBoundsSize && captionHeight != 0) { + outParams.mAppBounds.set(outParams.mBounds); + outParams.mAppBounds.top += captionHeight; + logger.accept("excluding caption height from app bounds"); + } } /** @@ -119,7 +133,8 @@ public final class DesktopModeBoundsCalculator { @NonNull private static Rect calculateInitialBounds(@NonNull Task task, @NonNull ActivityRecord activity, @NonNull Rect stableBounds, - @Nullable ActivityOptions options, boolean shouldRespectOptionPosition + @Nullable ActivityOptions options, boolean shouldRespectOptionPosition, + int captionHeight ) { // Display bounds not taking into account insets. final TaskDisplayArea displayArea = task.getDisplayArea(); @@ -160,7 +175,8 @@ public final class DesktopModeBoundsCalculator { } // If activity is unresizeable, regardless of orientation, calculate maximum size // (within the ideal size) maintaining original aspect ratio. - yield maximizeSizeGivenAspectRatio(activityOrientation, idealSize, appAspectRatio); + yield maximizeSizeGivenAspectRatio(activityOrientation, idealSize, appAspectRatio, + captionHeight); } case ORIENTATION_PORTRAIT -> { // Device in portrait orientation. @@ -188,11 +204,12 @@ public final class DesktopModeBoundsCalculator { // ratio. yield maximizeSizeGivenAspectRatio(activityOrientation, new Size(customPortraitWidthForLandscapeApp, idealSize.getHeight()), - appAspectRatio); + appAspectRatio, captionHeight); } // For portrait unresizeable activities, calculate maximum size (within the ideal // size) maintaining original aspect ratio. - yield maximizeSizeGivenAspectRatio(activityOrientation, idealSize, appAspectRatio); + yield maximizeSizeGivenAspectRatio(activityOrientation, idealSize, appAspectRatio, + captionHeight); } default -> idealSize; }; @@ -232,13 +249,15 @@ public final class DesktopModeBoundsCalculator { * Calculates the largest size that can fit in a given area while maintaining a specific aspect * ratio. */ + // TODO(b/400617906): Merge duplicate initial bounds calculations to shared class. @NonNull private static Size maximizeSizeGivenAspectRatio( @ScreenOrientation int orientation, @NonNull Size targetArea, - float aspectRatio + float aspectRatio, + int captionHeight ) { - final int targetHeight = targetArea.getHeight(); + final int targetHeight = targetArea.getHeight() - captionHeight; final int targetWidth = targetArea.getWidth(); final int finalHeight; final int finalWidth; @@ -275,7 +294,7 @@ public final class DesktopModeBoundsCalculator { finalHeight = (int) (finalWidth / aspectRatio); } } - return new Size(finalWidth, finalHeight); + return new Size(finalWidth, finalHeight + captionHeight); } /** diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java index ddcb5eccb1d8..03ba1a51ad7b 100644 --- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java @@ -113,14 +113,23 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { // Copy over any values outParams.set(currentParams); - // In Proto2, trampoline task launches of an existing background task can result in the - // previous windowing mode to be restored even if the desktop mode state has changed. - // Let task launches inherit the windowing mode from the source task if available, which - // should have the desired windowing mode set by WM Shell. See b/286929122. if (source != null && source.getTask() != null) { final Task sourceTask = source.getTask(); - outParams.mWindowingMode = sourceTask.getWindowingMode(); - appendLog("inherit-from-source=" + outParams.mWindowingMode); + if (DesktopModeFlags.DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX.isTrue() + && isEnteringDesktopMode(sourceTask, options, currentParams)) { + // If trampoline source is not freeform but we are entering or in desktop mode, + // ignore the source windowing mode and set the windowing mode to freeform + outParams.mWindowingMode = WINDOWING_MODE_FREEFORM; + appendLog("freeform window mode applied to task trampoline"); + } else { + // In Proto2, trampoline task launches of an existing background task can result in + // the previous windowing mode to be restored even if the desktop mode state has + // changed. Let task launches inherit the windowing mode from the source task if + // available, which should have the desired windowing mode set by WM Shell. + // See b/286929122. + outParams.mWindowingMode = sourceTask.getWindowingMode(); + appendLog("inherit-from-source=" + outParams.mWindowingMode); + } } if (phase == PHASE_WINDOWING_MODE) { @@ -133,6 +142,11 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { } if ((options == null || options.getLaunchBounds() == null) && task.hasOverrideBounds()) { + if (DesktopModeFlags.DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX.isTrue()) { + // We are in desktop, return result done to prevent other modifiers from modifying + // exiting task bounds or resolved windowing mode. + return RESULT_DONE; + } appendLog("current task has bounds set, not overriding"); return RESULT_SKIP; } @@ -150,7 +164,7 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { } DesktopModeBoundsCalculator.updateInitialBounds(task, layout, activity, options, - outParams.mBounds, this::appendLog); + outParams, this::appendLog); appendLog("final desktop mode task bounds set to %s", outParams.mBounds); if (options != null && options.getFlexibleLaunchSize()) { // Return result done to prevent other modifiers from respecting option bounds and diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index 25fdf89afad1..2798e843d6dd 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -211,14 +211,17 @@ class Dimmer { * child should call setAppearance again to request the Dim to continue. * If multiple containers call this method, only the changes relative to the topmost will be * applied. + * The creation of the dim layer is delayed if the requested dim and blur are 0. * @param dimmingContainer Container requesting the dim * @param alpha Dim amount * @param blurRadius Blur amount */ protected void adjustAppearance(@NonNull WindowState dimmingContainer, float alpha, int blurRadius) { - final DimState d = obtainDimState(dimmingContainer); - d.prepareLookChange(alpha, blurRadius); + if (mDimState != null || (alpha != 0 || blurRadius != 0)) { + final DimState d = obtainDimState(dimmingContainer); + d.prepareLookChange(alpha, blurRadius); + } } /** diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 42b63d125d6b..59a042981375 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -41,6 +41,7 @@ import static android.util.TypedValue.COMPLEX_UNIT_DIP; import static android.util.TypedValue.COMPLEX_UNIT_MASK; import static android.util.TypedValue.COMPLEX_UNIT_SHIFT; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.FLAG_ALLOWS_CONTENT_MODE_SWITCH; import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; import static android.view.Display.FLAG_PRIVATE; import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; @@ -2161,7 +2162,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Re-show the previously hidden windows if all seamless rotated windows are done. */ void finishAsyncRotationIfPossible() { final AsyncRotationController controller = mAsyncRotationController; - if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) { + if (controller != null) { controller.completeAll(); mAsyncRotationController = null; } @@ -2238,11 +2239,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private void applyRotation(final int oldRotation, final int rotation) { mDisplayRotation.applyCurrentRotation(rotation); - final boolean shellTransitions = mTransitionController.getTransitionPlayer() != null; - final boolean rotateSeamlessly = - mDisplayRotation.isRotatingSeamlessly() && !shellTransitions; - final Transaction transaction = - shellTransitions ? getSyncTransaction() : getPendingTransaction(); + // We need to update our screen size information to match the new rotation. If the rotation // has actually changed then this method will return true and, according to the comment at // the top of the method, the caller is obligated to call computeNewConfigurationLocked(). @@ -2250,25 +2247,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // #computeScreenConfiguration() later. updateDisplayAndOrientation(null /* outConfig */); - if (!shellTransitions) { - forAllWindows(w -> { - w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly); - }, true /* traverseTopToBottom */); - mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation); - if (!mDisplayRotation.hasSeamlessRotatingWindow()) { - // Make sure DisplayRotation#isRotatingSeamlessly() will return false. - mDisplayRotation.cancelSeamlessRotation(); - } - } + // Before setDisplayProjection is applied by the start transaction of transition, + // set the transform hint to avoid using surface in old rotation. + setFixedTransformHint(getPendingTransaction(), mSurfaceControl, rotation); + // The sync transaction should already contains setDisplayProjection, so unset the + // hint to restore the natural state when the transaction is applied. + getSyncTransaction().unsetFixedTransformHint(mSurfaceControl); - if (shellTransitions) { - // Before setDisplayProjection is applied by the start transaction of transition, - // set the transform hint to avoid using surface in old rotation. - setFixedTransformHint(getPendingTransaction(), mSurfaceControl, rotation); - // The sync transaction should already contains setDisplayProjection, so unset the - // hint to restore the natural state when the transaction is applied. - transaction.unsetFixedTransformHint(mSurfaceControl); - } scheduleAnimation(); mWmService.mRotationWatcherController.dispatchDisplayRotationChange(mDisplayId, rotation); @@ -2870,8 +2855,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // If the transition finished callback cannot match the token for some reason, make sure the // rotated state is cleared if it is already invisible. if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.isVisibleRequested() - && !mFixedRotationLaunchingApp.isVisible() - && !mDisplayRotation.isRotatingSeamlessly()) { + && !mFixedRotationLaunchingApp.isVisible()) { clearFixedRotationLaunchingApp(); } // If there won't be a transition to notify the launch is done, then it should be ready to @@ -3283,41 +3267,40 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /* inTopology= */ shouldShowContent); } - /** - * Whether the display is allowed to switch the content mode between extended and mirroring. - * If the content mode is extended, the display will start home activity and show system - * decorations, such as wallpapaer, status bar and navigation bar. - * If the content mode is mirroring, the display will not show home activity or system - * decorations. - * The content mode is switched when {@link Display#canHostTasks()} changes. - * - * Note that we only allow displays that are able to show system decorations to use the content - * mode switch; however, not all displays that are able to show system decorations are allowed - * to use the content mode switch. - */ - boolean allowContentModeSwitch() { - // The default display should always show system decorations. - if (isDefaultDisplay) { + /** + * Whether the display is allowed to switch the content mode between extended and mirroring. + * If the content mode is extended, the display will start home activity and show system + * decorations, such as wallpapaer, status bar and navigation bar. + * If the content mode is mirroring, the display will not show home activity or system + * decorations. + * The content mode is switched when {@link Display#canHostTasks()} changes. + * + * Note that we only allow displays that are able to show system decorations to use the content + * mode switch; however, not all displays that are able to show system decorations are allowed + * to use the content mode switch. + */ + boolean allowContentModeSwitch() { + if ((mDisplay.getFlags() & FLAG_ALLOWS_CONTENT_MODE_SWITCH) == 0) { return false; } - // Private display should never show system decorations. - if (isPrivate()) { + // The default display should always show system decorations. + if (isDefaultDisplay) { return false; } - if (shouldNeverShowSystemDecorations()) { + // Private or untrusted display should never show system decorations. + if (isPrivate() || !isTrusted()) { return false; } - // TODO(b/391965805): Remove this after introducing FLAG_ALLOW_CONTENT_MODE_SWITCH. - if ((mDisplay.getFlags() & Display.FLAG_REAR) != 0) { + if (shouldNeverShowSystemDecorations()) { return false; } - // TODO(b/391965805): Remove this after introducing FLAG_ALLOW_CONTENT_MODE_SWITCH. - // Virtual displays cannot add or remove system decorations during their lifecycle. - if (mDisplay.getType() == Display.TYPE_VIRTUAL) { + // Display with FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS enabled should always show system + // decorations, and should not switch the content mode. + if ((mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) { return false; } @@ -5072,7 +5055,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent; - if (!inTransition() && !mDisplayRotation.isRotatingSeamlessly()) { + if (!inTransition()) { mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId, mLastHasContent, mTmpApplySurfaceChangesTransactionState.preferredRefreshRate, @@ -5698,7 +5681,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // This display is configured to show system decorations. return true; } - if (isPublicSecondaryDisplayWithDesktopModeForceEnabled()) { + if (!DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue() + && isPublicSecondaryDisplayWithDesktopModeForceEnabled()) { if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) { // System decorations should not be forced on a rear display due to security // policies. diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 9cf792d82f56..9edbb70c3b74 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -168,19 +168,6 @@ public class DisplayRotation { private int mDeferredRotationPauseCount; /** - * A count of the windows which are 'seamlessly rotated', e.g. a surface at an old orientation - * is being transformed. We freeze orientation updates while any windows are seamlessly rotated, - * so we need to track when this hits zero so we can apply deferred orientation updates. - */ - private int mSeamlessRotationCount; - - /** - * True in the interval from starting seamless rotation until the last rotated window draws in - * the new orientation. - */ - private boolean mRotatingSeamlessly; - - /** * Behavior of rotation suggestions. * * @see Settings.Secure#SHOW_ROTATION_SUGGESTIONS @@ -630,15 +617,6 @@ public class DisplayRotation { return true; } - if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) { - // The screen rotation animation uses a screenshot to freeze the screen while windows - // resize underneath. When we are rotating seamlessly, we allow the elements to - // transition to their rotated state independently and without a freeze required. - prepareSeamlessRotation(); - } else { - cancelSeamlessRotation(); - } - // Give a remote handler (system ui) some time to reposition things. startRemoteRotation(oldRotation, mRotation); @@ -677,42 +655,6 @@ public class DisplayRotation { } } - /** - * This ensures that normal rotation animation is used. E.g. {@link #mRotatingSeamlessly} was - * set by previous {@link #updateRotationUnchecked}, but another orientation change happens - * before calling {@link DisplayContent#sendNewConfiguration} (remote rotation hasn't finished) - * and it doesn't choose seamless rotation. - */ - void cancelSeamlessRotation() { - if (!mRotatingSeamlessly) { - return; - } - mDisplayContent.forAllWindows(w -> { - if (w.mSeamlesslyRotated) { - w.cancelSeamlessRotation(); - w.mSeamlesslyRotated = false; - } - }, true /* traverseTopToBottom */); - mSeamlessRotationCount = 0; - mRotatingSeamlessly = false; - mDisplayContent.finishAsyncRotationIfPossible(); - } - - private void prepareSeamlessRotation() { - // We are careful to reset this in case a window was removed before it finished - // seamless rotation. - mSeamlessRotationCount = 0; - mRotatingSeamlessly = true; - } - - boolean isRotatingSeamlessly() { - return mRotatingSeamlessly; - } - - boolean hasSeamlessRotatingWindow() { - return mSeamlessRotationCount > 0; - } - @VisibleForTesting boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) { // Display doesn't need to be frozen because application has been started in correct @@ -750,13 +692,6 @@ public class DisplayRotation { return false; } - // We can't rotate (seamlessly or not) while waiting for the last seamless rotation to - // complete (that is, waiting for windows to redraw). It's tempting to check - // mSeamlessRotationCount but that could be incorrect in the case of window-removal. - if (!forceUpdate && mDisplayContent.getWindow(win -> win.mSeamlesslyRotated) != null) { - return false; - } - return true; } @@ -774,28 +709,6 @@ public class DisplayRotation { return oldRotation != Surface.ROTATION_180 && newRotation != Surface.ROTATION_180; } - void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) { - if (seamlesslyRotated == w.mSeamlesslyRotated || w.mForceSeamlesslyRotate) { - return; - } - - w.mSeamlesslyRotated = seamlesslyRotated; - if (seamlesslyRotated) { - mSeamlessRotationCount++; - } else { - mSeamlessRotationCount--; - } - if (mSeamlessRotationCount == 0) { - ProtoLog.i(WM_DEBUG_ORIENTATION, - "Performing post-rotate rotation after seamless rotation"); - // Finish seamless rotation. - mRotatingSeamlessly = false; - mDisplayContent.finishAsyncRotationIfPossible(); - - updateRotationAndSendNewConfigIfChanged(); - } - } - void restoreSettings(int userRotationMode, int userRotation, int fixedToUserRotation) { mFixedToUserRotation = fixedToUserRotation; diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 040bbe46c3aa..234fbd2ea87a 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -82,6 +82,13 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { */ private boolean mGivenInsetsReady = false; + /** + * The last state of the windowContainer. This is used to reset server visibility, in case of + * the IME (temporarily) redrawing (e.g. during a rotation), to dispatch the control with + * leash again after it has finished drawing. + */ + private boolean mLastDrawn = false; + ImeInsetsSourceProvider(@NonNull InsetsSource source, @NonNull InsetsStateController stateController, @NonNull DisplayContent displayContent) { @@ -97,6 +104,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { final WindowState ws = mWindowContainer != null ? mWindowContainer.asWindowState() : null; final boolean givenInsetsPending = ws != null && ws.mGivenInsetsPending; + mLastDrawn = ws != null && ws.isDrawn(); // isLeashReadyForDispatching (used to dispatch the leash of the control) is // depending on mGivenInsetsReady. Therefore, triggering notifyControlChanged here @@ -158,6 +166,35 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { } } + /** + * This is used to determine the desired serverVisibility state. For the IME, just having a + * window state that would be visible by policy is not enough. + */ + @Override + protected boolean isSurfaceVisible() { + final boolean isSurfaceVisible = super.isSurfaceVisible(); + if (android.view.inputmethod.Flags.refactorInsetsController()) { + final WindowState windowState = mWindowContainer.asWindowState(); + if (mControl != null && windowState != null) { + final boolean isDrawn = windowState.isDrawn(); + if (!isServerVisible() && isSurfaceVisible) { + // In case the IME becomes visible, we need to check if it is already drawn and + // does not have given insets pending. If it's not yet drawn, we do not set + // server visibility + return isDrawn && !windowState.mGivenInsetsPending; + } else if (mLastDrawn && !isDrawn) { + // If the IME was drawn before, but is not drawn anymore, we need to reset + // server visibility, which will also reset {@link + // ImeInsetsSourceProvider#mGivenInsetsReady}. Otherwise, the new control + // with leash won't be dispatched after the surface has redrawn. + return false; + } + } + } + return isSurfaceVisible; + } + + @Nullable @Override InsetsSourceControl getControl(InsetsControlTarget target) { @@ -779,6 +816,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { pw.print(prefix); pw.print("mImeShowing="); pw.print(mImeShowing); + pw.print(" mLastDrawn="); + pw.print(mLastDrawn); if (mImeRequester != null) { pw.print(prefix); pw.print("showImePostLayout pending for mImeRequester="); diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 1b693fc05b21..dcbb7816a0d7 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -176,6 +176,16 @@ class InsetsSourceProvider { } /** + * @return Whether the current window container has a visible surface. + */ + protected boolean isSurfaceVisible() { + final WindowState windowState = mWindowContainer.asWindowState(); + return windowState != null + ? windowState.wouldBeVisibleIfPolicyIgnored() && windowState.isVisibleByPolicy() + : mWindowContainer.isVisibleRequested(); + } + + /** * Updates the window container that currently backs this source. * * @param windowContainer The window container that links to this source. @@ -368,20 +378,9 @@ class InsetsSourceProvider { if (mWindowContainer == null) { return; } - WindowState windowState = mWindowContainer.asWindowState(); - boolean isServerVisible = windowState != null - ? windowState.wouldBeVisibleIfPolicyIgnored() && windowState.isVisibleByPolicy() - : mWindowContainer.isVisibleRequested(); + final WindowState windowState = mWindowContainer.asWindowState(); + final boolean isServerVisible = isSurfaceVisible(); - if (android.view.inputmethod.Flags.refactorInsetsController()) { - if (mControl != null && mControl.getType() == WindowInsets.Type.ime() && !mServerVisible - && isServerVisible && windowState != null) { - // in case the IME becomes visible, we need to check if it is already drawn and - // does not have given insets pending. If it's not yet drawn, we do not set - // server visibility - isServerVisible = windowState.isDrawn() && !windowState.mGivenInsetsPending; - } - } final boolean serverVisibleChanged = mServerVisible != isServerVisible; setServerVisible(isServerVisible); if (mControl != null && mControlTarget != null) { diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java index fa65bda7104d..2406178e46fe 100644 --- a/services/core/java/com/android/server/wm/LaunchParamsController.java +++ b/services/core/java/com/android/server/wm/LaunchParamsController.java @@ -26,6 +26,7 @@ import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier. import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.WindowConfiguration.WindowingMode; @@ -36,6 +37,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * {@link LaunchParamsController} calculates the {@link LaunchParams} by coordinating between @@ -96,18 +98,18 @@ class LaunchParamsController { mTmpResult.reset(); final LaunchParamsModifier modifier = mModifiers.get(i); - switch(modifier.onCalculate(task, layout, activity, source, options, request, phase, + switch (modifier.onCalculate(task, layout, activity, source, options, request, phase, mTmpCurrent, mTmpResult)) { case RESULT_SKIP: // Do not apply any results when we are told to skip continue; case RESULT_DONE: // Set result and return immediately. - result.set(mTmpResult); + result.merge(mTmpResult); return; case RESULT_CONTINUE: // Set result and continue - result.set(mTmpResult); + result.merge(mTmpResult); break; } } @@ -138,6 +140,10 @@ class LaunchParamsController { mService.deferWindowLayout(); try { if (task.getRootTask().inMultiWindowMode()) { + if (!mTmpParams.mAppBounds.isEmpty()) { + task.getRequestedOverrideConfiguration().windowConfiguration.setAppBounds( + mTmpParams.mAppBounds); + } task.setBounds(mTmpParams.mBounds); return true; } @@ -168,7 +174,11 @@ class LaunchParamsController { */ static class LaunchParams { /** The bounds within the parent container. */ + @NonNull final Rect mBounds = new Rect(); + /** The bounds within the parent container respecting insets. Usually empty. */ + @NonNull + final Rect mAppBounds = new Rect(); /** The display area the {@link Task} would prefer to be on. */ @Nullable @@ -178,24 +188,45 @@ class LaunchParamsController { @WindowingMode int mWindowingMode; + /** Whether the Activity needs the safe region bounds. A {@code null} value means unset. */ + @Nullable + Boolean mNeedsSafeRegionBounds = null; + /** Sets values back to default. {@link #isEmpty} will return {@code true} once called. */ void reset() { mBounds.setEmpty(); + mAppBounds.setEmpty(); mPreferredTaskDisplayArea = null; mWindowingMode = WINDOWING_MODE_UNDEFINED; + mNeedsSafeRegionBounds = null; } /** Copies the values set on the passed in {@link LaunchParams}. */ void set(LaunchParams params) { mBounds.set(params.mBounds); + mAppBounds.set(params.mAppBounds); + mPreferredTaskDisplayArea = params.mPreferredTaskDisplayArea; + mWindowingMode = params.mWindowingMode; + mNeedsSafeRegionBounds = params.mNeedsSafeRegionBounds; + } + + /** Merges the values set on the passed in {@link LaunchParams}. */ + void merge(LaunchParams params) { + mBounds.set(params.mBounds); + mAppBounds.set(params.mAppBounds); mPreferredTaskDisplayArea = params.mPreferredTaskDisplayArea; mWindowingMode = params.mWindowingMode; + // Only update mNeedsSafeRegionBounds if a modifier updates it by setting a non null + // value. Otherwise, carry over from previous modifiers + if (params.mNeedsSafeRegionBounds != null) { + mNeedsSafeRegionBounds = params.mNeedsSafeRegionBounds; + } } /** Returns {@code true} if no values have been explicitly set. */ boolean isEmpty() { - return mBounds.isEmpty() && mPreferredTaskDisplayArea == null - && mWindowingMode == WINDOWING_MODE_UNDEFINED; + return mBounds.isEmpty() && mAppBounds.isEmpty() && mPreferredTaskDisplayArea == null + && mWindowingMode == WINDOWING_MODE_UNDEFINED && mNeedsSafeRegionBounds == null; } boolean hasWindowingMode() { @@ -215,15 +246,20 @@ class LaunchParamsController { if (mPreferredTaskDisplayArea != that.mPreferredTaskDisplayArea) return false; if (mWindowingMode != that.mWindowingMode) return false; - return mBounds != null ? mBounds.equals(that.mBounds) : that.mBounds == null; + if (!mAppBounds.equals(that.mAppBounds)) return false; + if (!Objects.equals(mNeedsSafeRegionBounds, that.mNeedsSafeRegionBounds)) return false; + return !mBounds.isEmpty() ? mBounds.equals(that.mBounds) : that.mBounds.isEmpty(); } @Override public int hashCode() { - int result = mBounds != null ? mBounds.hashCode() : 0; + int result = !mBounds.isEmpty() ? mBounds.hashCode() : 0; + result = 31 * result + mAppBounds.hashCode(); result = 31 * result + (mPreferredTaskDisplayArea != null ? mPreferredTaskDisplayArea.hashCode() : 0); result = 31 * result + mWindowingMode; + result = 31 * result + (mNeedsSafeRegionBounds != null + ? Boolean.hashCode(mNeedsSafeRegionBounds) : 0); return result; } } diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java index 6dd7d35856df..6e59828c8ff2 100644 --- a/services/core/java/com/android/server/wm/PinnedTaskController.java +++ b/services/core/java/com/android/server/wm/PinnedTaskController.java @@ -21,20 +21,13 @@ import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import android.app.PictureInPictureParams; import android.content.res.Resources; -import android.graphics.Insets; -import android.graphics.Matrix; import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; -import android.util.RotationUtils; import android.util.Slog; import android.view.IPinnedTaskListener; -import android.view.Surface; -import android.view.SurfaceControl; -import android.window.PictureInPictureSurfaceTransaction; import java.io.PrintWriter; @@ -71,11 +64,7 @@ class PinnedTaskController { * based on the new rotation. */ private Rect mDestRotatedBounds; - /** - * Non-null if the entering PiP task from recents animation will cause display rotation to - * change. The transaction is based on the old rotation. - */ - private PictureInPictureSurfaceTransaction mPipTransaction; + /** Whether to skip task configuration change once. */ private boolean mFreezingTaskConfig; /** Defer display orientation change if the PiP task is animating across orientations. */ @@ -212,14 +201,12 @@ class PinnedTaskController { } /** - * Sets the transaction for {@link #startSeamlessRotationIfNeeded} if the orientation of display - * will be changed. This is only called when finishing recents animation with pending - * orientation change that will be handled by - * {@link DisplayContent.FixedRotationTransitionListener#onFinishRecentsAnimation}. + * Sets a hint if the orientation of display will be changed. This is only called when + * finishing recents animation with pending orientation change that will be handled by + * {@link DisplayContent.FixedRotationTransitionListener}. */ - void setEnterPipTransaction(PictureInPictureSurfaceTransaction tx) { + void setEnterPipWithRotatedTransientLaunch() { mFreezingTaskConfig = true; - mPipTransaction = tx; } /** Called when the activity in PiP task has PiP windowing mode (at the end of animation). */ @@ -233,81 +220,6 @@ class PinnedTaskController { } /** - * Resets rotation and applies scale and position to PiP task surface to match the current - * rotation of display. The final surface matrix will be replaced by PiPTaskOrganizer after it - * receives the callback of fixed rotation completion. - */ - void startSeamlessRotationIfNeeded(SurfaceControl.Transaction t, - int oldRotation, int newRotation) { - final Rect bounds = mDestRotatedBounds; - final PictureInPictureSurfaceTransaction pipTx = mPipTransaction; - final boolean emptyPipPositionTx = pipTx == null || pipTx.mPosition == null; - if (bounds == null && emptyPipPositionTx) { - return; - } - final TaskDisplayArea taskArea = mDisplayContent.getDefaultTaskDisplayArea(); - final Task pinnedTask = taskArea.getRootPinnedTask(); - if (pinnedTask == null) { - return; - } - - mDestRotatedBounds = null; - mPipTransaction = null; - final Rect areaBounds = taskArea.getBounds(); - if (!emptyPipPositionTx) { - // The transaction from recents animation is in old rotation. So the position needs to - // be rotated. - float dx = pipTx.mPosition.x; - float dy = pipTx.mPosition.y; - final Matrix matrix = pipTx.getMatrix(); - if (pipTx.mRotation == 90) { - dx = pipTx.mPosition.y; - dy = areaBounds.right - pipTx.mPosition.x; - matrix.postRotate(-90); - } else if (pipTx.mRotation == -90) { - dx = areaBounds.bottom - pipTx.mPosition.y; - dy = pipTx.mPosition.x; - matrix.postRotate(90); - } - matrix.postTranslate(dx, dy); - final SurfaceControl leash = pinnedTask.getSurfaceControl(); - t.setMatrix(leash, matrix, new float[9]); - if (pipTx.hasCornerRadiusSet()) { - t.setCornerRadius(leash, pipTx.mCornerRadius); - } - Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy); - return; - } - - final PictureInPictureParams params = pinnedTask.getPictureInPictureParams(); - final Rect sourceHintRect = params != null && params.hasSourceBoundsHint() - ? params.getSourceRectHint() - : null; - Slog.i(TAG, "Seamless rotation PiP bounds=" + bounds + " hintRect=" + sourceHintRect); - final int rotationDelta = RotationUtils.deltaRotation(oldRotation, newRotation); - // Adjust for display cutout if applicable. - if (sourceHintRect != null && rotationDelta == Surface.ROTATION_270) { - if (pinnedTask.getDisplayCutoutInsets() != null) { - final int rotationBackDelta = RotationUtils.deltaRotation(newRotation, oldRotation); - final Rect displayCutoutInsets = RotationUtils.rotateInsets( - Insets.of(pinnedTask.getDisplayCutoutInsets()), rotationBackDelta).toRect(); - sourceHintRect.offset(displayCutoutInsets.left, displayCutoutInsets.top); - } - } - final Rect contentBounds = sourceHintRect != null && areaBounds.contains(sourceHintRect) - ? sourceHintRect : areaBounds; - final int w = contentBounds.width(); - final int h = contentBounds.height(); - final float scale = w <= h ? (float) bounds.width() / w : (float) bounds.height() / h; - final int insetLeft = (int) ((contentBounds.left - areaBounds.left) * scale + .5f); - final int insetTop = (int) ((contentBounds.top - areaBounds.top) * scale + .5f); - final Matrix matrix = new Matrix(); - matrix.setScale(scale, scale); - matrix.postTranslate(bounds.left - insetLeft, bounds.top - insetTop); - t.setMatrix(pinnedTask.getSurfaceControl(), matrix, new float[9]); - } - - /** * Returns {@code true} to skip {@link Task#onConfigurationChanged} because it is expected that * there will be a orientation change and a PiP configuration change. */ @@ -321,7 +233,6 @@ class PinnedTaskController { mFreezingTaskConfig = false; mDeferOrientationChanging = false; mDestRotatedBounds = null; - mPipTransaction = null; } /** @@ -381,9 +292,6 @@ class PinnedTaskController { if (mDestRotatedBounds != null) { pw.println(prefix + " mPendingBounds=" + mDestRotatedBounds); } - if (mPipTransaction != null) { - pw.println(prefix + " mPipTransaction=" + mPipTransaction); - } pw.println(prefix + " mIsImeShowing=" + mIsImeShowing); pw.println(prefix + " mImeHeight=" + mImeHeight); pw.println(prefix + " mMinAspectRatio=" + mMinAspectRatio); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 609302ce3f56..39d10624469c 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -79,8 +79,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; -import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION; -import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING; import static java.lang.Integer.MAX_VALUE; @@ -655,9 +653,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final int count = mChildren.size(); for (int i = 0; i < count; ++i) { final int pendingChanges = mChildren.get(i).pendingLayoutChanges; - if ((pendingChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) { - animator.mBulkUpdateParams |= SET_WALLPAPER_ACTION_PENDING; - } if (pendingChanges != 0) { hasChanges = true; } @@ -858,7 +853,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Post these on a handler such that we don't call into power manager service while // holding the window manager lock to avoid lock contention with power manager lock. - mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides) + // Send a copy of the brightness overrides as they may be cleared before being sent out. + mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides.clone()) .sendToTarget(); mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget(); @@ -1024,18 +1020,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return changed; } - boolean copyAnimToLayoutParams() { - boolean doRequest = false; - - final int bulkUpdateParams = mWmService.mAnimator.mBulkUpdateParams; - if ((bulkUpdateParams & SET_UPDATE_ROTATION) != 0) { - mUpdateRotation = true; - doRequest = true; - } - - return doRequest; - } - private final class MyHandler extends Handler { public MyHandler(Looper looper) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 960f5beae2b3..70dabf8d23c0 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2333,15 +2333,24 @@ class TaskFragment extends WindowContainer<WindowContainer> { @Nullable DisplayInfo mTmpOverrideDisplayInfo; @Nullable AppCompatDisplayInsets mTmpCompatInsets; @Nullable Rect mParentAppBoundsOverride; + @Nullable Rect mParentBoundsOverride; int mTmpOverrideConfigOrientation; boolean mUseOverrideInsetsForConfig; void resolveTmpOverrides(DisplayContent dc, Configuration parentConfig, - boolean isFixedRotationTransforming) { - mParentAppBoundsOverride = new Rect(parentConfig.windowConfiguration.getAppBounds()); + boolean isFixedRotationTransforming, @Nullable Rect safeRegionBounds) { + mParentAppBoundsOverride = safeRegionBounds != null ? safeRegionBounds : new Rect( + parentConfig.windowConfiguration.getAppBounds()); + mParentBoundsOverride = safeRegionBounds != null ? safeRegionBounds : new Rect( + parentConfig.windowConfiguration.getBounds()); mTmpOverrideConfigOrientation = parentConfig.orientation; - final Insets insets; - if (mUseOverrideInsetsForConfig && dc != null + Insets insets = Insets.NONE; + if (safeRegionBounds != null) { + // Modify orientation based on the parent app bounds if safe region bounds are set. + mTmpOverrideConfigOrientation = + mParentAppBoundsOverride.height() >= mParentAppBoundsOverride.width() + ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; + } else if (mUseOverrideInsetsForConfig && dc != null && !isFloating(parentConfig.windowConfiguration.getWindowingMode())) { // Insets are decoupled from configuration by default from V+, use legacy // compatibility behaviour for apps targeting SDK earlier than 35 @@ -2357,10 +2366,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { .getDecorInsetsInfo(rotation, dw, dh); final Rect stableBounds = decorInsets.mOverrideConfigFrame; mTmpOverrideConfigOrientation = stableBounds.width() > stableBounds.height() - ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; + ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; insets = Insets.of(decorInsets.mOverrideNonDecorInsets); - } else { - insets = Insets.NONE; } mParentAppBoundsOverride.inset(insets); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 803c21ccab6e..30313fc63857 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1249,7 +1249,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Skip dispatching the change for PiP task to avoid its activity drawing for the // intermediate state which will cause flickering. The final PiP bounds in new // rotation will be applied by PipTransition. - ar.mDisplayContent.mPinnedTaskController.setEnterPipTransaction(null); + ar.mDisplayContent.mPinnedTaskController.setEnterPipWithRotatedTransientLaunch(); } return inPip; } diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 80137a298ac2..3f2b40c1d7c9 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -65,9 +65,6 @@ public class WindowAnimator { /** Time of current animation step. Reset on each iteration */ long mCurrentTime; - int mBulkUpdateParams = 0; - Object mLastWindowFreezeSource; - private boolean mInitialized = false; private Choreographer mChoreographer; @@ -145,7 +142,6 @@ public class WindowAnimator { final int animationFlags = useShellTransition ? CHILDREN : (TRANSITION | CHILDREN); boolean rootAnimating = false; mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS; - mBulkUpdateParams = 0; if (DEBUG_WINDOW_TRACE) { Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime); } @@ -202,8 +198,7 @@ public class WindowAnimator { } final boolean hasPendingLayoutChanges = root.hasPendingLayoutChanges(this); - final boolean doRequest = mBulkUpdateParams != 0 && root.copyAnimToLayoutParams(); - if (hasPendingLayoutChanges || doRequest) { + if (hasPendingLayoutChanges) { mService.mWindowPlacerLocked.requestTraversal(); } @@ -245,7 +240,6 @@ public class WindowAnimator { if (DEBUG_WINDOW_TRACE) { Slog.i(TAG, "!!! animate: exit" - + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams) + " hasPendingLayoutChanges=" + hasPendingLayoutChanges); } } @@ -265,17 +259,6 @@ public class WindowAnimator { mRunningExpensiveAnimations = runningExpensiveAnimations; } - private static String bulkUpdateParamsToString(int bulkUpdateParams) { - StringBuilder builder = new StringBuilder(128); - if ((bulkUpdateParams & WindowSurfacePlacer.SET_UPDATE_ROTATION) != 0) { - builder.append(" UPDATE_ROTATION"); - } - if ((bulkUpdateParams & WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING) != 0) { - builder.append(" SET_WALLPAPER_ACTION_PENDING"); - } - return builder.toString(); - } - public void dumpLocked(PrintWriter pw, String prefix, boolean dumpAll) { final String subPrefix = " " + prefix; @@ -292,11 +275,6 @@ public class WindowAnimator { pw.print(prefix); pw.print("mCurrentTime="); pw.println(TimeUtils.formatUptime(mCurrentTime)); } - if (mBulkUpdateParams != 0) { - pw.print(prefix); pw.print("mBulkUpdateParams=0x"); - pw.print(Integer.toHexString(mBulkUpdateParams)); - pw.println(bulkUpdateParamsToString(mBulkUpdateParams)); - } } void scheduleAnimation() { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 772a7fdfc684..5cbba355a06f 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -97,6 +97,7 @@ import com.android.server.wm.SurfaceAnimator.Animatable; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import com.android.server.wm.utils.AlwaysTruePredicate; +import com.android.window.flags.Flags; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -161,6 +162,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< protected @InsetsType int mMergedExcludeInsetsTypes = 0; private @InsetsType int mExcludeInsetsTypes = 0; + /** + * Bounds for the safe region for this window container which control the + * {@link AppCompatSafeRegionPolicy}. These bounds can be passed on to the subtree if the + * subtree has no other bounds for the safe region. The value will be null if there are no safe + * region bounds for the window container. + */ + @Nullable + private Rect mSafeRegionBounds; + @Nullable private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap; @@ -556,6 +566,38 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mParent != null ? mParent.mMergedExcludeInsetsTypes : 0); } + /** + * Returns the safe region bounds on the window container. If the window container has no safe + * region bounds set, the safe region bounds as set on the nearest ancestor is returned. + */ + @Nullable + Rect getSafeRegionBounds() { + if (mSafeRegionBounds != null) { + return mSafeRegionBounds; + } + if (mParent == null) { + return null; + } + return mParent.getSafeRegionBounds(); + } + + /** + * Sets the safe region bounds on the window container. Set bounds to {@code null} to reset. + * + * @param safeRegionBounds the safe region {@link Rect} that should be set on this + * WindowContainer + */ + void setSafeRegionBounds(@Nullable Rect safeRegionBounds) { + if (!Flags.safeRegionLetterboxing()) { + Slog.i(TAG, "Feature safe region letterboxing is not available"); + return; + } + mSafeRegionBounds = safeRegionBounds; + // Trigger a config change whenever this method is called since the safe region bounds + // can be modified (including a reset). + onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); + } + private void mergeExcludeInsetsTypesAndNotifyInsetsChanged( @InsetsType int excludeInsetsTypesFromParent) { final ArraySet<WindowState> changedWindows = new ArraySet<>(); @@ -3230,6 +3272,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mLocalInsetsSources.valueAt(i).dump(childPrefix, pw); } } + pw.println(prefix + mSafeRegionBounds + " SafeRegionBounds"); } final void updateSurfacePositionNonOrganized() { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c078d67b6cc6..d7626f00bca8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -198,6 +198,8 @@ import android.graphics.Rect; import android.graphics.Region; import android.hardware.configstore.V1_0.OptionalBool; import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs; +import android.hardware.devicestate.DeviceState; +import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.input.InputSettings; @@ -1032,6 +1034,21 @@ public class WindowManagerService extends IWindowManager.Stub PowerManager mPowerManager; PowerManagerInternal mPowerManagerInternal; + private DeviceStateManager mDeviceStateManager; + private DeviceStateCallback mDeviceStateCallback; + private class DeviceStateCallback implements DeviceStateManager.DeviceStateCallback { + private DeviceState mCurrentDeviceState; + @Override + public void onDeviceStateChanged(@NonNull DeviceState state) { + mCurrentDeviceState = state; + } + + boolean isInRearDisplayOuterDefaultState() { + return mCurrentDeviceState != null && mCurrentDeviceState + .hasProperties(DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT); + } + } + private float mWindowAnimationScaleSetting = 1.0f; private float mTransitionAnimationScaleSetting = 1.0f; private float mAnimatorDurationScaleSetting = 1.0f; @@ -1317,6 +1334,10 @@ public class WindowManagerService extends IWindowManager.Stub mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); + mDeviceStateManager = context.getSystemService(DeviceStateManager.class); + mDeviceStateCallback = new DeviceStateCallback(); + mDeviceStateManager.registerCallback(new HandlerExecutor(mH), mDeviceStateCallback); + if (mPowerManagerInternal != null) { mPowerManagerInternal.registerLowPowerModeObserver( new PowerManagerInternal.LowPowerModeListener() { @@ -2132,7 +2153,6 @@ public class WindowManagerService extends IWindowManager.Stub } final DisplayContent dc = win.getDisplayContent(); - dc.getDisplayRotation().markForSeamlessRotation(win, false /* seamlesslyRotated */); win.resetAppOpsState(); @@ -7594,6 +7614,26 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public boolean isEligibleForDesktopMode(int displayId) { + if (!checkCallingPermission(INTERNAL_SYSTEM_WINDOW, "isEligibleForDesktopMode()")) { + throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission"); + } + + synchronized (mGlobalLock) { + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent == null) { + ProtoLog.e(WM_ERROR, "Attempted to check isEligibleForDesktopMode() " + + "for a display that does not exist: %d", displayId); + return false; + } + if (!displayContent.isSystemDecorationsSupported()) { + return false; + } + return displayContent.isDefaultDisplay || displayContent.allowContentModeSwitch(); + } + } + + @Override public void setShouldShowSystemDecors(int displayId, boolean shouldShow) { if (!checkCallingPermission(INTERNAL_SYSTEM_WINDOW, "setShouldShowSystemDecors()")) { throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission"); @@ -8950,6 +8990,17 @@ public class WindowManagerService extends IWindowManager.Stub } } + if (mDeviceStateCallback.isInRearDisplayOuterDefaultState()) { + final Display[] rearDisplays = mDisplayManager + .getDisplays(DisplayManager.DISPLAY_CATEGORY_REAR); + if (rearDisplays.length > 0 && rearDisplays[0].getDisplayId() == t.getDisplayId()) { + // Do not change display focus to the inner display if we're in this mode. Note that + // in this mode, the inner display is configured as a rear display. + Slog.w(TAG, "Ignoring focus change because device is in RDM."); + return; + } + } + clearPointerDownOutsideFocusRunnable(); final InputTarget focusedInputTarget = mFocusedInputTarget; diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a012ec137892..c19fa8c03e0a 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -78,6 +78,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT; import static android.window.WindowContainerTransaction.HierarchyOp.REACHABILITY_EVENT_X; import static android.window.WindowContainerTransaction.HierarchyOp.REACHABILITY_EVENT_Y; @@ -472,35 +473,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub transition.setAllReady(); } - // TODO(b/365884835): remove this method and callers. - @Override - public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter, - @NonNull IWindowContainerTransactionCallback callback, - @NonNull WindowContainerTransaction t) { - enforceTaskPermission("startLegacyTransition()"); - final CallerInfo caller = new CallerInfo(); - final long ident = Binder.clearCallingIdentity(); - int syncId; - try { - synchronized (mGlobalLock) { - if (type < 0) { - throw new IllegalArgumentException("Can't create transition with no type"); - } - if (mTransitionController.getTransitionPlayer() != null) { - throw new IllegalArgumentException("Can't use legacy transitions in" - + " when shell transitions are enabled."); - } - syncId = startSyncWithOrganizer(callback); - applyTransaction(t, syncId, mService.mChainTracker.startLegacy("legacyTransit"), - caller); - setSyncReady(syncId); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - return syncId; - } - @Override public void finishTransition(@NonNull IBinder transitionToken, @Nullable WindowContainerTransaction t) { @@ -1544,6 +1516,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub container.setExcludeInsetsTypes(hop.getExcludeInsetsTypes()); break; } + case HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS: { + final WindowContainer container = WindowContainer.fromBinder(hop.getContainer()); + if (container == null || !container.isAttached()) { + Slog.e(TAG, + "Attempt to operate on unknown or detached container: " + container); + break; + } + if (chain.mTransition != null) { + chain.mTransition.collect(container); + } + container.setSafeRegionBounds(hop.getSafeRegionBounds()); + effects |= TRANSACT_EFFECTS_CLIENT_CONFIG; + } } return effects; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index ce91fc5baba1..a270af56cbcd 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -100,7 +100,6 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; -import static com.android.input.flags.Flags.removeInputChannelFromWindowstate; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS; @@ -170,7 +169,6 @@ import static com.android.server.wm.WindowStateProto.IS_READY_FOR_DISPLAY; import static com.android.server.wm.WindowStateProto.IS_VISIBLE; import static com.android.server.wm.WindowStateProto.KEEP_CLEAR_AREAS; import static com.android.server.wm.WindowStateProto.MERGED_LOCAL_INSETS_SOURCES; -import static com.android.server.wm.WindowStateProto.PENDING_SEAMLESS_ROTATION; import static com.android.server.wm.WindowStateProto.REMOVED; import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT; import static com.android.server.wm.WindowStateProto.REQUESTED_HEIGHT; @@ -232,7 +230,6 @@ import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.Surface; -import android.view.Surface.Rotation; import android.view.SurfaceControl; import android.view.View; import android.view.ViewDebug; @@ -400,7 +397,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * rotation. */ final boolean mForceSeamlesslyRotate; - SeamlessRotator mPendingSeamlessRotate; private RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks; @@ -594,13 +590,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** The time when the window was last requested to redraw for orientation change. */ private long mOrientationChangeRedrawRequestTime; - /** - * The orientation during the last visible call to relayout. If our - * current orientation is different, the window can't be ready - * to be shown. - */ - int mLastVisibleLayoutRotation = -1; - /** Is this window now (or just being) removed? */ boolean mRemoved; @@ -613,10 +602,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Input channel and input window handle used by the input dispatcher. final InputWindowHandleWrapper mInputWindowHandle; - /** - * Only populated if flag REMOVE_INPUT_CHANNEL_FROM_WINDOWSTATE is disabled. - */ - private InputChannel mInputChannel; /** * The token will be assigned to {@link InputWindowHandle#token} if this window can receive @@ -660,15 +645,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean mIsSurfacePositionPaused; /** - * During seamless rotation we have two phases, first the old window contents - * are rotated to look as if they didn't move in the new coordinate system. Then we - * have to freeze updates to this layer (to preserve the transformation) until - * the resize actually occurs. This is true from when the transformation is set - * and false until the transaction to resize is sent. - */ - boolean mSeamlesslyRotated = false; - - /** * Whether the IME insets have been consumed. If {@code true}, this window won't be able to * receive visible IME insets; {@code false}, otherwise. */ @@ -783,11 +759,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ private boolean mInsetsAnimationRunning; - private final Consumer<SurfaceControl.Transaction> mSeamlessRotationFinishedConsumer = t -> { - finishSeamlessRotation(t); - updateSurfacePosition(t); - }; - private final Consumer<SurfaceControl.Transaction> mSetSurfacePositionConsumer = t -> { // Only apply the position to the surface when there's no leash created. if (mSurfaceControl != null && mSurfaceControl.isValid() && !mSurfaceAnimator.hasLeash()) { @@ -904,69 +875,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return visible && mFrozenInsetsState == null; } - void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation, - @Rotation int rotation, boolean requested) { - // Invisible windows and the wallpaper do not participate in the seamless rotation animation - if (!isVisibleNow() || mIsWallpaper) { - return; - } - - if (mToken.hasFixedRotationTransform()) { - // The transform of its surface is handled by fixed rotation. - return; - } - final Task task = getTask(); - if (task != null && task.inPinnedWindowingMode()) { - // It is handled by PinnedTaskController. Note that the windowing mode of activity - // and windows may still be fullscreen. - return; - } - - if (mPendingSeamlessRotate != null) { - oldRotation = mPendingSeamlessRotate.getOldRotation(); - } - - // Skip performing seamless rotation when the controlled insets is IME with visible state. - if (mControllableInsetProvider != null - && mControllableInsetProvider.getSource().getType() == WindowInsets.Type.ime()) { - return; - } - - if (mForceSeamlesslyRotate || requested) { - if (mControllableInsetProvider != null) { - mControllableInsetProvider.startSeamlessRotation(); - } - mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo(), - false /* applyFixedTransformationHint */); - // The surface position is going to be unrotated according to the last position. - // Make sure the source position is up-to-date. - mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y); - mPendingSeamlessRotate.unrotate(transaction, this); - getDisplayContent().getDisplayRotation().markForSeamlessRotation(this, - true /* seamlesslyRotated */); - applyWithNextDraw(mSeamlessRotationFinishedConsumer); - } - } - - void cancelSeamlessRotation() { - finishSeamlessRotation(getPendingTransaction()); - } - - void finishSeamlessRotation(SurfaceControl.Transaction t) { - if (mPendingSeamlessRotate == null) { - return; - } - - mPendingSeamlessRotate.finish(t, this); - mPendingSeamlessRotate = null; - - getDisplayContent().getDisplayRotation().markForSeamlessRotation(this, - false /* seamlesslyRotated */); - if (mControllableInsetProvider != null) { - mControllableInsetProvider.finishSeamlessRotation(); - } - } - List<Rect> getSystemGestureExclusion() { return mExclusionRects; } @@ -1830,12 +1738,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * Input Manager uses when discarding windows from input consideration. */ boolean isPotentialDragTarget(boolean targetInterceptsGlobalDrag) { - if (removeInputChannelFromWindowstate()) { - return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved - && mInputChannelToken != null && mInputWindowHandle != null; - } return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved - && mInputChannel != null && mInputWindowHandle != null; + && mInputChannelToken != null && mInputWindowHandle != null; } /** @@ -2185,8 +2089,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0 && !isDragResizing() && hasMovementAnimation - && !mWinAnimator.mLastHidden - && !mSeamlesslyRotated; + && !mWinAnimator.mLastHidden; } /** @@ -2583,25 +2486,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mInputChannelToken != null) { throw new IllegalStateException("Window already has an input channel token."); } - if (removeInputChannelFromWindowstate()) { - String name = getName(); - InputChannel channel = mWmService.mInputManager.createInputChannel(name); - mInputChannelToken = channel.getToken(); - mInputWindowHandle.setToken(mInputChannelToken); - mWmService.mInputToWindowMap.put(mInputChannelToken, this); - channel.copyTo(outInputChannel); - channel.dispose(); - return; - } - if (mInputChannel != null) { - throw new IllegalStateException("Window already has an input channel."); - } String name = getName(); - mInputChannel = mWmService.mInputManager.createInputChannel(name); - mInputChannelToken = mInputChannel.getToken(); + InputChannel channel = mWmService.mInputManager.createInputChannel(name); + mInputChannelToken = channel.getToken(); mInputWindowHandle.setToken(mInputChannelToken); mWmService.mInputToWindowMap.put(mInputChannelToken, this); - mInputChannel.copyTo(outInputChannel); + channel.copyTo(outInputChannel); + channel.dispose(); } /** @@ -2624,12 +2515,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mInputChannelToken = null; } - if (!removeInputChannelFromWindowstate()) { - if (mInputChannel != null) { - mInputChannel.dispose(); - mInputChannel = null; - } - } mInputWindowHandle.setToken(null); } @@ -3886,14 +3771,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } /** - * Returns {@code true} if activity bounds are letterboxed or letterboxed for display cutout. + * Returns {@code true} if activity bounds are letterboxed or letterboxed for display cutout or + * letterboxed for a safe region. * * <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link - * AppCompatLetterboxOverrides#shouldShowLetterboxUi} for more context. + * AppCompatLetterboxPolicy#shouldShowLetterboxUi} for more context. */ boolean areAppWindowBoundsLetterboxed() { return mActivityRecord != null && !isStartingWindowAssociatedToTask() - && (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout()); + && (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout() + || mActivityRecord.mAppCompatController + .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed()); } /** Returns {@code true} if the window is letterboxed for the display cutout. */ @@ -4022,7 +3910,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP proto.write(REMOVED, mRemoved); proto.write(IS_ON_SCREEN, isOnScreen()); proto.write(IS_VISIBLE, isVisible); - proto.write(PENDING_SEAMLESS_ROTATION, mPendingSeamlessRotate != null); proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate); proto.write(HAS_COMPAT_SCALE, hasCompatScale()); proto.write(GLOBAL_SCALE, mGlobalScale); @@ -4168,14 +4055,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " mDestroying=" + mDestroying + " mRemoved=" + mRemoved); } - pw.print(prefix + "mForceSeamlesslyRotate=" + mForceSeamlesslyRotate - + " seamlesslyRotate: pending="); - if (mPendingSeamlessRotate != null) { - mPendingSeamlessRotate.dump(pw); - } else { - pw.print("null"); - } - pw.println(); if (mXOffset != 0 || mYOffset != 0) { pw.println(prefix + "mXOffset=" + mXOffset + " mYOffset=" + mYOffset); @@ -4907,8 +4786,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWinAnimator.mEnterAnimationPending = true; } - mLastVisibleLayoutRotation = getDisplayContent().getRotation(); - mWinAnimator.mEnteringAnimation = true; Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareToDisplay"); @@ -5306,9 +5183,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final AsyncRotationController asyncRotationController = mDisplayContent.getAsyncRotationController(); - if ((asyncRotationController != null - && asyncRotationController.hasSeamlessOperation(mToken)) - || mPendingSeamlessRotate != null) { + if (asyncRotationController != null + && asyncRotationController.hasSeamlessOperation(mToken)) { // Freeze position while un-rotating the window, so its surface remains at the position // corresponding to the original rotation. return; diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index a34b5115faf9..4fb74ef00914 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -42,9 +42,6 @@ class WindowSurfacePlacer { /** Only do a maximum of 6 repeated layouts. After that quit */ private int mLayoutRepeatCount; - static final int SET_UPDATE_ROTATION = 1 << 0; - static final int SET_WALLPAPER_ACTION_PENDING = 1 << 1; - private boolean mTraversalScheduled; private int mDeferDepth = 0; /** The number of layout requests when deferring. */ diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 66d04df8095b..adfabe1e54fd 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -169,7 +169,7 @@ cc_defaults { "android.hardware.broadcastradio@1.1", "android.hardware.contexthub@1.0", "android.hardware.common.fmq-V1-ndk", - "android.hardware.gnss-V3-cpp", + "android.hardware.gnss-V5-cpp", "android.hardware.gnss@1.0", "android.hardware.gnss@1.1", "android.hardware.gnss@2.0", @@ -204,6 +204,7 @@ cc_defaults { "android.system.suspend.control-V1-cpp", "android.system.suspend.control.internal-cpp", "android.system.suspend-V1-ndk", + "android_location_flags_c_lib", "server_configurable_flags", "service.incremental", ], diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index a8c49e11e4e9..ec8794f8073f 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -671,9 +671,13 @@ void NativeInputManager::setDisplayTopology(JNIEnv* env, jobject topologyGraph) return; } - // TODO(b/383092013): Add topology validation const DisplayTopologyGraph displayTopology = android_hardware_display_DisplayTopologyGraph_toNative(env, topologyGraph); + if (input_flags::enable_display_topology_validation() && !displayTopology.isValid()) { + LOG(ERROR) << "Ignoring Invalid DisplayTopology"; + return; + } + mInputManager->getDispatcher().setDisplayTopology(displayTopology); mInputManager->getChoreographer().setDisplayTopology(displayTopology); } @@ -2309,13 +2313,6 @@ static jint nativeGetKeyCodeForKeyLocation(JNIEnv* env, jobject nativeImplObj, j locationKeyCode); } -static void handleInputChannelDisposed(JNIEnv* env, jobject /* inputChannelObj */, - const std::shared_ptr<InputChannel>& inputChannel, - void* data) { - NativeInputManager* im = static_cast<NativeInputManager*>(data); - im->removeInputChannel(inputChannel->getConnectionToken()); -} - static jobject nativeCreateInputChannel(JNIEnv* env, jobject nativeImplObj, jstring nameObj) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); @@ -2337,8 +2334,6 @@ static jobject nativeCreateInputChannel(JNIEnv* env, jobject nativeImplObj, jstr return nullptr; } - android_view_InputChannel_setDisposeCallback(env, inputChannelObj, - handleInputChannelDisposed, im); return inputChannelObj; } diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 9c033e25c04e..93f6e95b6d5c 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -36,6 +36,7 @@ #include <android/hardware/gnss/BnGnssMeasurementCallback.h> #include <android/hardware/gnss/BnGnssPowerIndicationCallback.h> #include <android/hardware/gnss/BnGnssPsdsCallback.h> +#include <android_location_flags.h> #include <binder/IServiceManager.h> #include <nativehelper/JNIHelp.h> #include <pthread.h> @@ -53,6 +54,8 @@ #include "gnss/Gnss.h" #include "gnss/GnssAntennaInfo.h" #include "gnss/GnssAntennaInfoCallback.h" +#include "gnss/GnssAssistance.h" +#include "gnss/GnssAssistanceCallback.h" #include "gnss/GnssBatching.h" #include "gnss/GnssConfiguration.h" #include "gnss/GnssDebug.h" @@ -114,6 +117,7 @@ using android::hardware::gnss::GnssConstellationType; using android::hardware::gnss::GnssPowerStats; using android::hardware::gnss::IGnssPowerIndication; using android::hardware::gnss::IGnssPowerIndicationCallback; +using android::hardware::gnss::gnss_assistance::IGnssAssistanceCallback; using IGnssAidl = android::hardware::gnss::IGnss; using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching; @@ -140,6 +144,9 @@ std::unique_ptr<android::gnss::GnssPsdsInterface> gnssPsdsIface = nullptr; std::unique_ptr<android::gnss::GnssVisibilityControlInterface> gnssVisibilityControlIface = nullptr; std::unique_ptr<android::gnss::MeasurementCorrectionsInterface> gnssMeasurementCorrectionsIface = nullptr; +std::unique_ptr<android::gnss::GnssAssistanceInterface> gnssAssistanceIface = nullptr; + +namespace location_flags = android::location::flags; namespace android { @@ -229,6 +236,9 @@ static void android_location_gnss_hal_GnssNative_class_init_once(JNIEnv* env, jc gnss::GnssVisibilityControl_class_init_once(env, clazz); gnss::MeasurementCorrections_class_init_once(env, clazz); gnss::MeasurementCorrectionsCallback_class_init_once(env, clazz); + if (location_flags::gnss_assistance_interface_jni()) { + gnss::GnssAssistance_class_init_once(env, clazz); + } gnss::Utils_class_init_once(env); } @@ -266,7 +276,9 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject gnssBatchingIface = gnssHal->getGnssBatchingInterface(); gnssVisibilityControlIface = gnssHal->getGnssVisibilityControlInterface(); gnssPowerIndicationIface = gnssHal->getGnssPowerIndicationInterface(); - + if (location_flags::gnss_assistance_interface_jni()) { + gnssAssistanceIface = gnssHal->getGnssAssistanceInterface(); + } if (mCallbacksObj) { ALOGE("Callbacks already initialized"); } else { @@ -355,13 +367,22 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl // Set IGnssPowerIndication.hal callback. if (gnssPowerIndicationIface != nullptr) { sp<IGnssPowerIndicationCallback> gnssPowerIndicationCallback = - new GnssPowerIndicationCallback(); + sp<GnssPowerIndicationCallback>::make(); auto status = gnssPowerIndicationIface->setCallback(gnssPowerIndicationCallback); if (!checkAidlStatus(status, "IGnssPowerIndication setCallback() failed.")) { gnssPowerIndicationIface = nullptr; } } + // Set IGnssAssistance callback. + if (gnssAssistanceIface != nullptr) { + sp<IGnssAssistanceCallback> gnssAssistanceCallback = + sp<gnss::GnssAssistanceCallback>::make(); + if (!gnssAssistanceIface->setCallback(gnssAssistanceCallback)) { + ALOGI("IGnssAssistanceInterface setCallback() failed"); + } + } + return JNI_TRUE; } @@ -493,6 +514,15 @@ static void android_location_gnss_hal_GnssNative_inject_psds_data(JNIEnv* env, j gnssPsdsIface->injectPsdsData(data, length, psdsType); } +static void android_location_gnss_hal_GnssNative_inject_gnss_assistance(JNIEnv* env, jclass, + jobject gnssAssistanceObj) { + if (gnssAssistanceIface == nullptr) { + ALOGE("%s: IGnssAssistance interface not available.", __func__); + return; + } + gnssAssistanceIface->injectGnssAssistance(env, gnssAssistanceObj); +} + static void android_location_GnssNetworkConnectivityHandler_agps_data_conn_open( JNIEnv* env, jobject /* obj */, jlong networkHandle, jstring apn, jint apnIpType) { if (apn == nullptr) { @@ -937,6 +967,8 @@ static const JNINativeMethod sLocationProviderMethods[] = { {"native_stop_nmea_message_collection", "()Z", reinterpret_cast<void*>( android_location_gnss_hal_GnssNative_stop_nmea_message_collection)}, + {"native_inject_gnss_assistance", "(Landroid/location/GnssAssistance;)V", + reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_gnss_assistance)}, }; static const JNINativeMethod sBatchingMethods[] = { diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp index eb729de6afd4..0bee7181c2d5 100644 --- a/services/core/jni/com_android_server_sensor_SensorService.cpp +++ b/services/core/jni/com_android_server_sensor_SensorService.cpp @@ -60,6 +60,8 @@ public: void unregisterRuntimeSensor(jint handle); jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp, jfloatArray values); + jboolean sendRuntimeSensorAdditionalInfo(JNIEnv* env, jint handle, jint type, jint serial, + jlong timestamp, jfloatArray values); private: sp<SensorService> mService; @@ -172,9 +174,9 @@ jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, j sensors_event_t event{ .version = sizeof(sensors_event_t), - .timestamp = timestamp, .sensor = handle, .type = type, + .timestamp = timestamp, }; int valuesLength = env->GetArrayLength(values); @@ -234,6 +236,42 @@ jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, j return err == OK; } +jboolean NativeSensorService::sendRuntimeSensorAdditionalInfo(JNIEnv* env, jint handle, jint type, + jint serial, jlong timestamp, + jfloatArray values) { + if (mService == nullptr) { + ALOGD("Dropping sendRuntimeSensorAdditionalInfo, sensor service not available."); + return false; + } + + sensors_event_t event{ + .version = sizeof(sensors_event_t), + .sensor = handle, + .type = SENSOR_TYPE_ADDITIONAL_INFO, + .timestamp = timestamp, + .additional_info = + (additional_info_event_t){ + .type = type, + .serial = serial, + }, + }; + + if (values != nullptr) { + int valuesLength = env->GetArrayLength(values); + if (valuesLength > 14) { + ALOGD("Dropping sendRuntimeSensorAdditionalInfo, number of values exceeds maximum."); + return false; + } + if (valuesLength > 0) { + jfloat* sensorValues = env->GetFloatArrayElements(values, nullptr); + memcpy(event.additional_info.data_float, sensorValues, valuesLength * sizeof(float)); + } + } + + status_t err = mService->sendRuntimeSensorEvent(event); + return err == OK; +} + NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate( JNIEnv* env, jobject listener) : mListener(env->NewGlobalRef(listener)) {} @@ -326,6 +364,13 @@ static jboolean sendRuntimeSensorEventNative(JNIEnv* env, jclass, jlong ptr, jin return service->sendRuntimeSensorEvent(env, handle, type, timestamp, values); } +static jboolean sendRuntimeSensorAdditionalInfoNative(JNIEnv* env, jclass, jlong ptr, jint handle, + jint type, jint serial, jlong timestamp, + jfloatArray values) { + auto* service = reinterpret_cast<NativeSensorService*>(ptr); + return service->sendRuntimeSensorAdditionalInfo(env, handle, type, serial, timestamp, values); +} + static const JNINativeMethod methods[] = { {"startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J", reinterpret_cast<void*>(startSensorServiceNative)}, @@ -340,6 +385,8 @@ static const JNINativeMethod methods[] = { reinterpret_cast<void*>(unregisterRuntimeSensorNative)}, {"sendRuntimeSensorEventNative", "(JIIJ[F)Z", reinterpret_cast<void*>(sendRuntimeSensorEventNative)}, + {"sendRuntimeSensorAdditionalInfoNative", "(JIIIJ[F)Z", + reinterpret_cast<void*>(sendRuntimeSensorAdditionalInfoNative)}, }; int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env) { diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp index e72259f094bc..562e82f90bfa 100644 --- a/services/core/jni/gnss/Android.bp +++ b/services/core/jni/gnss/Android.bp @@ -17,7 +17,6 @@ cc_library_shared { "-Werror", "-Wno-unused-parameter", "-Wthread-safety", - "-DEGL_EGLEXT_PROTOTYPES", "-DGL_GLEXT_PROTOTYPES", ], @@ -41,6 +40,8 @@ cc_library_shared { "GnssMeasurementCallback.cpp", "GnssNavigationMessage.cpp", "GnssNavigationMessageCallback.cpp", + "GnssAssistance.cpp", + "GnssAssistanceCallback.cpp", "GnssPsds.cpp", "GnssPsdsCallback.cpp", "GnssVisibilityControl.cpp", @@ -61,7 +62,7 @@ cc_defaults { "libnativehelper", "libhardware_legacy", "libutils", - "android.hardware.gnss-V3-cpp", + "android.hardware.gnss-V5-cpp", "android.hardware.gnss@1.0", "android.hardware.gnss@1.1", "android.hardware.gnss@2.0", diff --git a/services/core/jni/gnss/Gnss.cpp b/services/core/jni/gnss/Gnss.cpp index da8928b5f97f..a3fd9aa79cfb 100644 --- a/services/core/jni/gnss/Gnss.cpp +++ b/services/core/jni/gnss/Gnss.cpp @@ -765,4 +765,15 @@ sp<hardware::gnss::V1_0::IGnssNi> GnssHal::getGnssNiInterface() { return nullptr; } +std::unique_ptr<GnssAssistanceInterface> GnssHal::getGnssAssistanceInterface() { + if (gnssHalAidl != nullptr) { + sp<hardware::gnss::gnss_assistance::IGnssAssistanceInterface> gnssAssistance; + auto status = gnssHalAidl->getExtensionGnssAssistanceInterface(&gnssAssistance); + if (checkAidlStatus(status, "Unable to get a handle to GnssAssistance")) { + return std::make_unique<GnssAssistanceInterface>(gnssAssistance); + } + } + return nullptr; +} + } // namespace android::gnss diff --git a/services/core/jni/gnss/Gnss.h b/services/core/jni/gnss/Gnss.h index 458da8a6e514..2b6b7513a231 100644 --- a/services/core/jni/gnss/Gnss.h +++ b/services/core/jni/gnss/Gnss.h @@ -34,6 +34,7 @@ #include "AGnss.h" #include "AGnssRil.h" #include "GnssAntennaInfo.h" +#include "GnssAssistance.h" #include "GnssBatching.h" #include "GnssCallback.h" #include "GnssConfiguration.h" @@ -115,6 +116,7 @@ public: std::unique_ptr<GnssVisibilityControlInterface> getGnssVisibilityControlInterface(); std::unique_ptr<GnssAntennaInfoInterface> getGnssAntennaInfoInterface(); std::unique_ptr<GnssPsdsInterface> getGnssPsdsInterface(); + std::unique_ptr<GnssAssistanceInterface> getGnssAssistanceInterface(); sp<hardware::gnss::IGnssPowerIndication> getGnssPowerIndicationInterface(); sp<hardware::gnss::V1_0::IGnssNi> getGnssNiInterface(); diff --git a/services/core/jni/gnss/GnssAssistance.cpp b/services/core/jni/gnss/GnssAssistance.cpp new file mode 100644 index 000000000000..fff396ea126a --- /dev/null +++ b/services/core/jni/gnss/GnssAssistance.cpp @@ -0,0 +1,2047 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Define LOG_TAG before <log/log.h> to overwrite the default value. + +#define LOG_TAG "GnssAssistanceJni" + +#include "GnssAssistance.h" + +#include <utils/String16.h> + +#include "GnssAssistanceCallback.h" +#include "Utils.h" + +namespace android::gnss { + +using GnssConstellationType = android::hardware::gnss::GnssConstellationType; +using GnssCorrectionComponent = android::hardware::gnss::gnss_assistance::GnssCorrectionComponent; +using GnssInterval = + android::hardware::gnss::gnss_assistance::GnssCorrectionComponent::GnssInterval; +using GnssSatelliteAlmanac = + android::hardware::gnss::gnss_assistance::GnssAlmanac::GnssSatelliteAlmanac; +using IonosphericCorrection = android::hardware::gnss::gnss_assistance::IonosphericCorrection; +using PseudorangeCorrection = + android::hardware::gnss::gnss_assistance::GnssCorrectionComponent::PseudorangeCorrection; +using GalileoSatelliteClockModel = android::hardware::gnss::gnss_assistance:: + GalileoSatelliteEphemeris::GalileoSatelliteClockModel; +using GalileoSvHealth = + android::hardware::gnss::gnss_assistance::GalileoSatelliteEphemeris::GalileoSvHealth; +using GlonassSatelliteAlmanac = + android::hardware::gnss::gnss_assistance::GlonassAlmanac::GlonassSatelliteAlmanac; +using GlonassSatelliteClockModel = android::hardware::gnss::gnss_assistance:: + GlonassSatelliteEphemeris::GlonassSatelliteClockModel; +using GlonassSatelliteOrbitModel = android::hardware::gnss::gnss_assistance:: + GlonassSatelliteEphemeris::GlonassSatelliteOrbitModel; +using GnssSignalType = hardware::gnss::GnssSignalType; +using GnssConstellationType = hardware::gnss::GnssConstellationType; +using BeidouB1CSatelliteOrbitType = + android::hardware::gnss::gnss_assistance::AuxiliaryInformation::BeidouB1CSatelliteOrbitType; +using QzssSatelliteEphemeris = android::hardware::gnss::gnss_assistance::QzssSatelliteEphemeris; + +// Implementation of GnssAssistance (AIDL HAL) + +namespace { +jmethodID method_gnssAssistanceGetGpsAssistance; +jmethodID method_gnssAssistanceGetGlonassAssistance; +jmethodID method_gnssAssistanceGetGalileoAssistance; +jmethodID method_gnssAssistanceGetBeidouAssistance; +jmethodID method_gnssAssistanceGetQzssAssistance; + +jmethodID method_listSize; +jmethodID method_listGet; + +jmethodID method_gnssAlmanacGetIssueDateMillis; +jmethodID method_gnssAlmanacGetIoda; +jmethodID method_gnssAlmanacGetWeekNumber; +jmethodID method_gnssAlmanacGetToaSeconds; +jmethodID method_gnssAlmanacGetSatelliteAlmanacs; +jmethodID method_gnssAlmanacIsCompleteAlmanacProvided; +jmethodID method_satelliteAlmanacGetSvid; +jmethodID method_satelliteAlmanacGetSvHealth; +jmethodID method_satelliteAlmanacGetAf0; +jmethodID method_satelliteAlmanacGetAf1; +jmethodID method_satelliteAlmanacGetEccentricity; +jmethodID method_satelliteAlmanacGetInclination; +jmethodID method_satelliteAlmanacGetM0; +jmethodID method_satelliteAlmanacGetOmega; +jmethodID method_satelliteAlmanacGetOmega0; +jmethodID method_satelliteAlmanacGetOmegaDot; +jmethodID method_satelliteAlmanacGetRootA; + +jmethodID method_satelliteEphemerisTimeGetIode; +jmethodID method_satelliteEphemerisTimeGetToeSeconds; +jmethodID method_satelliteEphemerisTimeGetWeekNumber; + +jmethodID method_keplerianOrbitModelGetDeltaN; +jmethodID method_keplerianOrbitModelGetEccentricity; +jmethodID method_keplerianOrbitModelGetI0; +jmethodID method_keplerianOrbitModelGetIDot; +jmethodID method_keplerianOrbitModelGetM0; +jmethodID method_keplerianOrbitModelGetOmega; +jmethodID method_keplerianOrbitModelGetOmega0; +jmethodID method_keplerianOrbitModelGetOmegaDot; +jmethodID method_keplerianOrbitModelGetRootA; +jmethodID method_keplerianOrbitModelGetSecondOrderHarmonicPerturbation; +jmethodID method_secondOrderHarmonicPerturbationGetCic; +jmethodID method_secondOrderHarmonicPerturbationGetCis; +jmethodID method_secondOrderHarmonicPerturbationGetCrc; +jmethodID method_secondOrderHarmonicPerturbationGetCrs; +jmethodID method_secondOrderHarmonicPerturbationGetCuc; +jmethodID method_secondOrderHarmonicPerturbationGetCus; + +jmethodID method_klobucharIonosphericModelGetAlpha0; +jmethodID method_klobucharIonosphericModelGetAlpha1; +jmethodID method_klobucharIonosphericModelGetAlpha2; +jmethodID method_klobucharIonosphericModelGetAlpha3; +jmethodID method_klobucharIonosphericModelGetBeta0; +jmethodID method_klobucharIonosphericModelGetBeta1; +jmethodID method_klobucharIonosphericModelGetBeta2; +jmethodID method_klobucharIonosphericModelGetBeta3; + +jmethodID method_utcModelGetA0; +jmethodID method_utcModelGetA1; +jmethodID method_utcModelGetTimeOfWeek; +jmethodID method_utcModelGetWeekNumber; + +jmethodID method_leapSecondsModelGetDayNumberLeapSecondsFuture; +jmethodID method_leapSecondsModelGetLeapSeconds; +jmethodID method_leapSecondsModelGetLeapSecondsFuture; +jmethodID method_leapSecondsModelGetWeekNumberLeapSecondsFuture; + +jmethodID method_timeModelsGetTimeOfWeek; +jmethodID method_timeModelsGetToGnss; +jmethodID method_timeModelsGetWeekNumber; +jmethodID method_timeModelsGetA0; +jmethodID method_timeModelsGetA1; + +jmethodID method_realTimeIntegrityModelGetBadSvid; +jmethodID method_realTimeIntegrityModelGetBadSignalTypes; +jmethodID method_realTimeIntegrityModelGetStartDateSeconds; +jmethodID method_realTimeIntegrityModelGetEndDateSeconds; +jmethodID method_realTimeIntegrityModelGetPublishDateSeconds; +jmethodID method_realTimeIntegrityModelGetAdvisoryNumber; +jmethodID method_realTimeIntegrityModelGetAdvisoryType; + +jmethodID method_gnssSignalTypeGetConstellationType; +jmethodID method_gnssSignalTypeGetCarrierFrequencyHz; +jmethodID method_gnssSignalTypeGetCodeType; + +jmethodID method_auxiliaryInformationGetSvid; +jmethodID method_auxiliaryInformationGetAvailableSignalTypes; +jmethodID method_auxiliaryInformationGetFrequencyChannelNumber; +jmethodID method_auxiliaryInformationGetSatType; + +jmethodID method_satelliteCorrectionGetSvid; +jmethodID method_satelliteCorrectionGetIonosphericCorrections; +jmethodID method_ionosphericCorrectionGetCarrierFrequencyHz; +jmethodID method_ionosphericCorrectionGetIonosphericCorrection; +jmethodID method_gnssCorrectionComponentGetPseudorangeCorrection; +jmethodID method_gnssCorrectionComponentGetSourceKey; +jmethodID method_gnssCorrectionComponentGetValidityInterval; +jmethodID method_pseudorangeCorrectionGetCorrectionMeters; +jmethodID method_pseudorangeCorrectionGetCorrectionUncertaintyMeters; +jmethodID method_pseudorangeCorrectionGetCorrectionRateMetersPerSecond; +jmethodID method_gnssIntervalGetStartMillisSinceGpsEpoch; +jmethodID method_gnssIntervalGetEndMillisSinceGpsEpoch; + +jmethodID method_gpsAssistanceGetAlmanac; +jmethodID method_gpsAssistanceGetIonosphericModel; +jmethodID method_gpsAssistanceGetUtcModel; +jmethodID method_gpsAssistanceGetLeapSecondsModel; +jmethodID method_gpsAssistanceGetTimeModels; +jmethodID method_gpsAssistanceGetSatelliteEphemeris; +jmethodID method_gpsAssistanceGetRealTimeIntegrityModels; +jmethodID method_gpsAssistanceGetSatelliteCorrections; +jmethodID method_gpsSatelliteEphemerisGetSvid; +jmethodID method_gpsSatelliteEphemerisGetGpsL2Params; +jmethodID method_gpsSatelliteEphemerisGetSatelliteClockModel; +jmethodID method_gpsSatelliteEphemerisGetSatelliteOrbitModel; +jmethodID method_gpsSatelliteEphemerisGetSatelliteHealth; +jmethodID method_gpsSatelliteEphemerisGetSatelliteEphemerisTime; +jmethodID method_gpsL2ParamsGetL2Code; +jmethodID method_gpsL2ParamsGetL2Flag; +jmethodID method_gpsSatelliteClockModelGetAf0; +jmethodID method_gpsSatelliteClockModelGetAf1; +jmethodID method_gpsSatelliteClockModelGetAf2; +jmethodID method_gpsSatelliteClockModelGetTgd; +jmethodID method_gpsSatelliteClockModelGetIodc; +jmethodID method_gpsSatelliteClockModelGetTimeOfClockSeconds; +jmethodID method_gpsSatelliteHealthGetFitInt; +jmethodID method_gpsSatelliteHealthGetSvAccur; +jmethodID method_gpsSatelliteHealthGetSvHealth; + +jmethodID method_beidouAssistanceGetAlmanac; +jmethodID method_beidouAssistanceGetIonosphericModel; +jmethodID method_beidouAssistanceGetUtcModel; +jmethodID method_beidouAssistanceGetLeapSecondsModel; +jmethodID method_beidouAssistanceGetTimeModels; +jmethodID method_beidouAssistanceGetSatelliteEphemeris; +jmethodID method_beidouAssistanceGetSatelliteCorrections; +jmethodID method_beidouAssistanceGetRealTimeIntegrityModels; +jmethodID method_beidouSatelliteEphemerisGetSvid; +jmethodID method_beidouSatelliteEphemerisGetSatelliteClockModel; +jmethodID method_beidouSatelliteEphemerisGetSatelliteOrbitModel; +jmethodID method_beidouSatelliteEphemerisGetSatelliteHealth; +jmethodID method_beidouSatelliteEphemerisGetSatelliteEphemerisTime; +jmethodID method_beidouSatelliteClockModelGetAf0; +jmethodID method_beidouSatelliteClockModelGetAf1; +jmethodID method_beidouSatelliteClockModelGetAf2; +jmethodID method_beidouSatelliteClockModelGetAodc; +jmethodID method_beidouSatelliteClockModelGetTgd1; +jmethodID method_beidouSatelliteClockModelGetTgd2; +jmethodID method_beidouSatelliteClockModelGetTimeOfClockSeconds; +jmethodID method_beidouSatelliteHealthGetSatH1; +jmethodID method_beidouSatelliteHealthGetSvAccur; +jmethodID method_beidouSatelliteEphemerisTimeGetIode; +jmethodID method_beidouSatelliteEphemerisTimeGetBeidouWeekNumber; +jmethodID method_beidouSatelliteEphemerisTimeGetToeSeconds; + +jmethodID method_galileoAssistanceGetAlmanac; +jmethodID method_galileoAssistanceGetIonosphericModel; +jmethodID method_galileoAssistanceGetUtcModel; +jmethodID method_galileoAssistanceGetLeapSecondsModel; +jmethodID method_galileoAssistanceGetTimeModels; +jmethodID method_galileoAssistanceGetSatelliteEphemeris; +jmethodID method_galileoAssistanceGetSatelliteCorrections; +jmethodID method_galileoAssistanceGetRealTimeIntegrityModels; +jmethodID method_galileoSatelliteEphemerisGetSvid; +jmethodID method_galileoSatelliteEphemerisGetSatelliteClockModels; +jmethodID method_galileoSatelliteEphemerisGetSatelliteOrbitModel; +jmethodID method_galileoSatelliteEphemerisGetSatelliteHealth; +jmethodID method_galileoSatelliteEphemerisGetSatelliteEphemerisTime; +jmethodID method_galileoSatelliteClockModelGetAf0; +jmethodID method_galileoSatelliteClockModelGetAf1; +jmethodID method_galileoSatelliteClockModelGetAf2; +jmethodID method_galileoSatelliteClockModelGetBgdSeconds; +jmethodID method_galileoSatelliteClockModelGetSatelliteClockType; +jmethodID method_galileoSatelliteClockModelGetSisaMeters; +jmethodID method_galileoSatelliteClockModelGetTimeOfClockSeconds; +jmethodID method_galileoSvHealthGetDataValidityStatusE1b; +jmethodID method_galileoSvHealthGetDataValidityStatusE5a; +jmethodID method_galileoSvHealthGetDataValidityStatusE5b; +jmethodID method_galileoSvHealthGetSignalHealthStatusE1b; +jmethodID method_galileoSvHealthGetSignalHealthStatusE5a; +jmethodID method_galileoSvHealthGetSignalHealthStatusE5b; +jmethodID method_galileoIonosphericModelGetAi0; +jmethodID method_galileoIonosphericModelGetAi1; +jmethodID method_galileoIonosphericModelGetAi2; + +jmethodID method_glonassAssistanceGetAlmanac; +jmethodID method_glonassAssistanceGetUtcModel; +jmethodID method_glonassAssistanceGetTimeModels; +jmethodID method_glonassAssistanceGetSatelliteEphemeris; +jmethodID method_glonassAssistanceGetSatelliteCorrections; +jmethodID method_glonassAlmanacGetIssueDateMillis; +jmethodID method_glonassAlmanacGetSatelliteAlmanacs; +jmethodID method_glonassSatelliteAlmanacGetDeltaI; +jmethodID method_glonassSatelliteAlmanacGetDeltaT; +jmethodID method_glonassSatelliteAlmanacGetDeltaTDot; +jmethodID method_glonassSatelliteAlmanacGetEccentricity; +jmethodID method_glonassSatelliteAlmanacGetFrequencyChannelNumber; +jmethodID method_glonassSatelliteAlmanacGetLambda; +jmethodID method_glonassSatelliteAlmanacGetOmega; +jmethodID method_glonassSatelliteAlmanacGetSlotNumber; +jmethodID method_glonassSatelliteAlmanacGetHealthState; +jmethodID method_glonassSatelliteAlmanacGetTLambda; +jmethodID method_glonassSatelliteAlmanacGetTau; +jmethodID method_glonassSatelliteAlmanacGetIsGlonassM; +jmethodID method_glonassSatelliteAlmanacGetCalendarDayNumber; +jmethodID method_glonassSatelliteEphemerisGetAgeInDays; +jmethodID method_glonassSatelliteEphemerisGetSatelliteClockModel; +jmethodID method_glonassSatelliteEphemerisGetSatelliteOrbitModel; +jmethodID method_glonassSatelliteEphemerisGetHealthState; +jmethodID method_glonassSatelliteEphemerisGetSlotNumber; +jmethodID method_glonassSatelliteEphemerisGetFrameTimeSeconds; +jmethodID method_glonassSatelliteEphemerisGetUpdateIntervalMinutes; +jmethodID method_glonassSatelliteEphemerisGetIsGlonassM; +jmethodID method_glonassSatelliteEphemerisGetIsUpdateIntervalOdd; + +jmethodID method_glonassSatelliteOrbitModelGetX; +jmethodID method_glonassSatelliteOrbitModelGetY; +jmethodID method_glonassSatelliteOrbitModelGetZ; +jmethodID method_glonassSatelliteOrbitModelGetXAccel; +jmethodID method_glonassSatelliteOrbitModelGetYAccel; +jmethodID method_glonassSatelliteOrbitModelGetZAccel; +jmethodID method_glonassSatelliteOrbitModelGetXDot; +jmethodID method_glonassSatelliteOrbitModelGetYDot; +jmethodID method_glonassSatelliteOrbitModelGetZDot; +jmethodID method_glonassSatelliteClockModelGetClockBias; +jmethodID method_glonassSatelliteClockModelGetFrequencyBias; +jmethodID method_glonassSatelliteClockModelGetFrequencyChannelNumber; +jmethodID method_glonassSatelliteClockModelGetTimeOfClockSeconds; + +jmethodID method_qzssAssistanceGetAlmanac; +jmethodID method_qzssAssistanceGetIonosphericModel; +jmethodID method_qzssAssistanceGetUtcModel; +jmethodID method_qzssAssistanceGetLeapSecondsModel; +jmethodID method_qzssAssistanceGetTimeModels; +jmethodID method_qzssAssistanceGetSatelliteEphemeris; +jmethodID method_qzssAssistanceGetSatelliteCorrections; +jmethodID method_qzssAssistanceGetRealTimeIntegrityModels; +jmethodID method_qzssSatelliteEphemerisGetSvid; +jmethodID method_qzssSatelliteEphemerisGetGpsL2Params; +jmethodID method_qzssSatelliteEphemerisGetSatelliteClockModel; +jmethodID method_qzssSatelliteEphemerisGetSatelliteOrbitModel; +jmethodID method_qzssSatelliteEphemerisGetSatelliteHealth; +jmethodID method_qzssSatelliteEphemerisGetSatelliteEphemerisTime; +jmethodID method_qzssSatelliteClockModelGetAf0; +jmethodID method_qzssSatelliteClockModelGetAf1; +jmethodID method_qzssSatelliteClockModelGetAf2; +jmethodID method_qzssSatelliteClockModelGetAodc; +jmethodID method_qzssSatelliteClockModelGetTgd1; +jmethodID method_qzssSatelliteClockModelGetTgd2; +jmethodID method_qzssSatelliteClockModelGetTimeOfClockSeconds; +} // namespace + +void GnssAssistance_class_init_once(JNIEnv* env, jclass clazz) { + // Get the methods of GnssAssistance class. + jclass gnssAssistanceClass = env->FindClass("android/location/GnssAssistance"); + + method_gnssAssistanceGetGpsAssistance = + env->GetMethodID(gnssAssistanceClass, "getGpsAssistance", + "()Landroid/location/GpsAssistance;"); + method_gnssAssistanceGetGlonassAssistance = + env->GetMethodID(gnssAssistanceClass, "getGlonassAssistance", + "()Landroid/location/GlonassAssistance;"); + method_gnssAssistanceGetGalileoAssistance = + env->GetMethodID(gnssAssistanceClass, "getGalileoAssistance", + "()Landroid/location/GalileoAssistance;"); + method_gnssAssistanceGetBeidouAssistance = + env->GetMethodID(gnssAssistanceClass, "getBeidouAssistance", + "()Landroid/location/BeidouAssistance;"); + method_gnssAssistanceGetQzssAssistance = + env->GetMethodID(gnssAssistanceClass, "getQzssAssistance", + "()Landroid/location/QzssAssistance;"); + + // Get the methods of List class. + jclass listClass = env->FindClass("java/util/List"); + + method_listSize = env->GetMethodID(listClass, "size", "()I"); + method_listGet = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"); + + // Get the methods of GnssAlmanac class. + jclass gnssAlmanacClass = env->FindClass("android/location/GnssAlmanac"); + + method_gnssAlmanacGetIssueDateMillis = + env->GetMethodID(gnssAlmanacClass, "getIssueDateMillis", "()J"); + method_gnssAlmanacGetIoda = env->GetMethodID(gnssAlmanacClass, "getIoda", "()I"); + method_gnssAlmanacGetWeekNumber = env->GetMethodID(gnssAlmanacClass, "getWeekNumber", "()I"); + method_gnssAlmanacGetToaSeconds = env->GetMethodID(gnssAlmanacClass, "getToaSeconds", "()I"); + method_gnssAlmanacGetSatelliteAlmanacs = + env->GetMethodID(gnssAlmanacClass, "getGnssSatelliteAlmanacs", "()Ljava/util/List;"); + method_gnssAlmanacIsCompleteAlmanacProvided = + env->GetMethodID(gnssAlmanacClass, "isCompleteAlmanacProvided", "()Z"); + + // Get the methods of SatelliteAlmanac class. + jclass satelliteAlmanacClass = + env->FindClass("android/location/GnssAlmanac$GnssSatelliteAlmanac"); + + method_satelliteAlmanacGetSvid = env->GetMethodID(satelliteAlmanacClass, "getSvid", "()I"); + method_satelliteAlmanacGetSvHealth = + env->GetMethodID(satelliteAlmanacClass, "getSvHealth", "()I"); + method_satelliteAlmanacGetAf0 = env->GetMethodID(satelliteAlmanacClass, "getAf0", "()D"); + method_satelliteAlmanacGetAf1 = env->GetMethodID(satelliteAlmanacClass, "getAf1", "()D"); + method_satelliteAlmanacGetEccentricity = + env->GetMethodID(satelliteAlmanacClass, "getEccentricity", "()D"); + method_satelliteAlmanacGetInclination = + env->GetMethodID(satelliteAlmanacClass, "getInclination", "()D"); + method_satelliteAlmanacGetM0 = env->GetMethodID(satelliteAlmanacClass, "getM0", "()D"); + method_satelliteAlmanacGetOmega = env->GetMethodID(satelliteAlmanacClass, "getOmega", "()D"); + method_satelliteAlmanacGetOmega0 = env->GetMethodID(satelliteAlmanacClass, "getOmega0", "()D"); + method_satelliteAlmanacGetOmegaDot = + env->GetMethodID(satelliteAlmanacClass, "getOmegaDot", "()D"); + method_satelliteAlmanacGetRootA = env->GetMethodID(satelliteAlmanacClass, "getRootA", "()D"); + + // Get the mothods of SatelliteEphemerisTime class. + jclass satelliteEphemerisTimeClass = env->FindClass("android/location/SatelliteEphemerisTime"); + + method_satelliteEphemerisTimeGetIode = + env->GetMethodID(satelliteEphemerisTimeClass, "getIode", "()I"); + method_satelliteEphemerisTimeGetToeSeconds = + env->GetMethodID(satelliteEphemerisTimeClass, "getToeSeconds", "()I"); + method_satelliteEphemerisTimeGetWeekNumber = + env->GetMethodID(satelliteEphemerisTimeClass, "getWeekNumber", "()I"); + + // Get the mothods of KeplerianOrbitModel class. + jclass keplerianOrbitModelClass = env->FindClass("android/location/KeplerianOrbitModel"); + + method_keplerianOrbitModelGetDeltaN = + env->GetMethodID(keplerianOrbitModelClass, "getDeltaN", "()D"); + method_keplerianOrbitModelGetEccentricity = + env->GetMethodID(keplerianOrbitModelClass, "getEccentricity", "()D"); + method_keplerianOrbitModelGetI0 = env->GetMethodID(keplerianOrbitModelClass, "getI0", "()D"); + method_keplerianOrbitModelGetIDot = + env->GetMethodID(keplerianOrbitModelClass, "getIDot", "()D"); + method_keplerianOrbitModelGetM0 = env->GetMethodID(keplerianOrbitModelClass, "getM0", "()D"); + method_keplerianOrbitModelGetOmega = + env->GetMethodID(keplerianOrbitModelClass, "getOmega", "()D"); + method_keplerianOrbitModelGetOmega0 = + env->GetMethodID(keplerianOrbitModelClass, "getOmega0", "()D"); + method_keplerianOrbitModelGetOmegaDot = + env->GetMethodID(keplerianOrbitModelClass, "getOmegaDot", "()D"); + method_keplerianOrbitModelGetRootA = + env->GetMethodID(keplerianOrbitModelClass, "getRootA", "()D"); + method_keplerianOrbitModelGetSecondOrderHarmonicPerturbation = + env->GetMethodID(keplerianOrbitModelClass, "getSecondOrderHarmonicPerturbation", + "()Landroid/location/" + "KeplerianOrbitModel$SecondOrderHarmonicPerturbation;"); + + // Get the methods of SecondOrderHarmonicPerturbation class. + jclass secondOrderHarmonicPerturbationClass = + env->FindClass("android/location/KeplerianOrbitModel$SecondOrderHarmonicPerturbation"); + + method_secondOrderHarmonicPerturbationGetCic = + env->GetMethodID(secondOrderHarmonicPerturbationClass, "getCic", "()D"); + method_secondOrderHarmonicPerturbationGetCis = + env->GetMethodID(secondOrderHarmonicPerturbationClass, "getCis", "()D"); + method_secondOrderHarmonicPerturbationGetCrc = + env->GetMethodID(secondOrderHarmonicPerturbationClass, "getCrc", "()D"); + method_secondOrderHarmonicPerturbationGetCrs = + env->GetMethodID(secondOrderHarmonicPerturbationClass, "getCrs", "()D"); + method_secondOrderHarmonicPerturbationGetCuc = + env->GetMethodID(secondOrderHarmonicPerturbationClass, "getCuc", "()D"); + method_secondOrderHarmonicPerturbationGetCus = + env->GetMethodID(secondOrderHarmonicPerturbationClass, "getCus", "()D"); + + // Get the methods of KlobucharIonosphericModel class. + jclass klobucharIonosphericModelClass = + env->FindClass("android/location/KlobucharIonosphericModel"); + + method_klobucharIonosphericModelGetAlpha0 = + env->GetMethodID(klobucharIonosphericModelClass, "getAlpha0", "()D"); + method_klobucharIonosphericModelGetAlpha1 = + env->GetMethodID(klobucharIonosphericModelClass, "getAlpha1", "()D"); + method_klobucharIonosphericModelGetAlpha2 = + env->GetMethodID(klobucharIonosphericModelClass, "getAlpha2", "()D"); + method_klobucharIonosphericModelGetAlpha3 = + env->GetMethodID(klobucharIonosphericModelClass, "getAlpha3", "()D"); + method_klobucharIonosphericModelGetBeta0 = + env->GetMethodID(klobucharIonosphericModelClass, "getBeta0", "()D"); + method_klobucharIonosphericModelGetBeta1 = + env->GetMethodID(klobucharIonosphericModelClass, "getBeta1", "()D"); + method_klobucharIonosphericModelGetBeta2 = + env->GetMethodID(klobucharIonosphericModelClass, "getBeta2", "()D"); + method_klobucharIonosphericModelGetBeta3 = + env->GetMethodID(klobucharIonosphericModelClass, "getBeta3", "()D"); + + // Get the methods of UtcModel class. + jclass utcModelClass = env->FindClass("android/location/UtcModel"); + + method_utcModelGetA0 = env->GetMethodID(utcModelClass, "getA0", "()D"); + method_utcModelGetA1 = env->GetMethodID(utcModelClass, "getA1", "()D"); + method_utcModelGetTimeOfWeek = env->GetMethodID(utcModelClass, "getTimeOfWeek", "()I"); + method_utcModelGetWeekNumber = env->GetMethodID(utcModelClass, "getWeekNumber", "()I"); + + // Get the methods of LeapSecondsModel class. + jclass leapSecondsModelClass = env->FindClass("android/location/LeapSecondsModel"); + + method_leapSecondsModelGetDayNumberLeapSecondsFuture = + env->GetMethodID(leapSecondsModelClass, "getDayNumberLeapSecondsFuture", "()I"); + method_leapSecondsModelGetLeapSeconds = + env->GetMethodID(leapSecondsModelClass, "getLeapSeconds", "()I"); + method_leapSecondsModelGetLeapSecondsFuture = + env->GetMethodID(leapSecondsModelClass, "getLeapSecondsFuture", "()I"); + method_leapSecondsModelGetWeekNumberLeapSecondsFuture = + env->GetMethodID(leapSecondsModelClass, "getWeekNumberLeapSecondsFuture", "()I"); + + // Get the methods of TimeModel class. + jclass timeModelsClass = env->FindClass("android/location/TimeModel"); + + method_timeModelsGetTimeOfWeek = env->GetMethodID(timeModelsClass, "getTimeOfWeek", "()I"); + method_timeModelsGetToGnss = env->GetMethodID(timeModelsClass, "getToGnss", "()I"); + method_timeModelsGetWeekNumber = env->GetMethodID(timeModelsClass, "getWeekNumber", "()I"); + method_timeModelsGetA0 = env->GetMethodID(timeModelsClass, "getA0", "()D"); + method_timeModelsGetA1 = env->GetMethodID(timeModelsClass, "getA1", "()D"); + + // Get the methods of AuxiliaryInformation class. + jclass auxiliaryInformationClass = env->FindClass("android/location/AuxiliaryInformation"); + + method_auxiliaryInformationGetSvid = + env->GetMethodID(auxiliaryInformationClass, "getSvid", "()I"); + method_auxiliaryInformationGetAvailableSignalTypes = + env->GetMethodID(auxiliaryInformationClass, "getAvailableSignalTypes", + "()Ljava/util/List;"); + method_auxiliaryInformationGetFrequencyChannelNumber = + env->GetMethodID(auxiliaryInformationClass, "getFrequencyChannelNumber", "()I"); + method_auxiliaryInformationGetSatType = + env->GetMethodID(auxiliaryInformationClass, "getSatType", "()I"); + + // Get the methods of RealTimeIntegrityModel + jclass realTimeIntegrityModelClass = env->FindClass("android/location/RealTimeIntegrityModel"); + + method_realTimeIntegrityModelGetBadSvid = + env->GetMethodID(realTimeIntegrityModelClass, "getBadSvid", "()I"); + method_realTimeIntegrityModelGetBadSignalTypes = + env->GetMethodID(realTimeIntegrityModelClass, "getBadSignalTypes", + "()Ljava/util/List;"); + method_realTimeIntegrityModelGetStartDateSeconds = + env->GetMethodID(realTimeIntegrityModelClass, "getStartDateSeconds", "()J"); + method_realTimeIntegrityModelGetEndDateSeconds = + env->GetMethodID(realTimeIntegrityModelClass, "getEndDateSeconds", "()J"); + method_realTimeIntegrityModelGetPublishDateSeconds = + env->GetMethodID(realTimeIntegrityModelClass, "getPublishDateSeconds", "()J"); + method_realTimeIntegrityModelGetAdvisoryNumber = + env->GetMethodID(realTimeIntegrityModelClass, "getAdvisoryNumber", + "()Ljava/lang/String;"); + method_realTimeIntegrityModelGetAdvisoryType = + env->GetMethodID(realTimeIntegrityModelClass, "getAdvisoryType", + "()Ljava/lang/String;"); + + // Get the methods of GnssSignalType class. + jclass gnssSignalTypeClass = env->FindClass("android/location/GnssSignalType"); + + method_gnssSignalTypeGetConstellationType = + env->GetMethodID(gnssSignalTypeClass, "getConstellationType", "()I"); + method_gnssSignalTypeGetCarrierFrequencyHz = + env->GetMethodID(gnssSignalTypeClass, "getCarrierFrequencyHz", "()D"); + method_gnssSignalTypeGetCodeType = + env->GetMethodID(gnssSignalTypeClass, "getCodeType", "()Ljava/lang/String;"); + + // Get the methods of SatelliteCorrection class. + jclass satelliteCorrectionClass = + env->FindClass("android/location/GnssAssistance$GnssSatelliteCorrections"); + + method_satelliteCorrectionGetSvid = + env->GetMethodID(satelliteCorrectionClass, "getSvid", "()I"); + method_satelliteCorrectionGetIonosphericCorrections = + env->GetMethodID(satelliteCorrectionClass, "getIonosphericCorrections", + "()Ljava/util/List;"); + + // Get the methods of IonosphericCorrection class. + jclass ionosphericCorrectionClass = env->FindClass("android/location/IonosphericCorrection"); + + method_ionosphericCorrectionGetCarrierFrequencyHz = + env->GetMethodID(ionosphericCorrectionClass, "getCarrierFrequencyHz", "()J"); + method_ionosphericCorrectionGetIonosphericCorrection = + env->GetMethodID(ionosphericCorrectionClass, "getIonosphericCorrection", + "()Landroid/location/GnssCorrectionComponent;"); + + // Get the methods of GnssCorrectionComponent class. + jclass gnssCorrectionComponentClass = + env->FindClass("android/location/GnssCorrectionComponent"); + + method_gnssCorrectionComponentGetPseudorangeCorrection = + env->GetMethodID(gnssCorrectionComponentClass, "getPseudorangeCorrection", + "()Landroid/location/GnssCorrectionComponent$PseudorangeCorrection;"); + method_gnssCorrectionComponentGetSourceKey = + env->GetMethodID(gnssCorrectionComponentClass, "getSourceKey", "()Ljava/lang/String;"); + method_gnssCorrectionComponentGetValidityInterval = + env->GetMethodID(gnssCorrectionComponentClass, "getValidityInterval", + "()Landroid/location/GnssCorrectionComponent$GnssInterval;"); + + // Get the methods of PseudorangeCorrection class. + jclass pseudorangeCorrectionClass = + env->FindClass("android/location/GnssCorrectionComponent$PseudorangeCorrection"); + + method_pseudorangeCorrectionGetCorrectionMeters = + env->GetMethodID(pseudorangeCorrectionClass, "getCorrectionMeters", "()D"); + method_pseudorangeCorrectionGetCorrectionRateMetersPerSecond = + env->GetMethodID(pseudorangeCorrectionClass, "getCorrectionRateMetersPerSecond", "()D"); + method_pseudorangeCorrectionGetCorrectionUncertaintyMeters = + env->GetMethodID(pseudorangeCorrectionClass, "getCorrectionUncertaintyMeters", "()D"); + + // Get the methods of GnssInterval class. + jclass gnssIntervalClass = + env->FindClass("android/location/GnssCorrectionComponent$GnssInterval"); + + method_gnssIntervalGetStartMillisSinceGpsEpoch = + env->GetMethodID(gnssIntervalClass, "getStartMillisSinceGpsEpoch", "()J"); + method_gnssIntervalGetEndMillisSinceGpsEpoch = + env->GetMethodID(gnssIntervalClass, "getEndMillisSinceGpsEpoch", "()J"); + + // Get the methods of GpsAssistance class. + jclass gpsAssistanceClass = env->FindClass("android/location/GpsAssistance"); + + method_gpsAssistanceGetAlmanac = + env->GetMethodID(gpsAssistanceClass, "getAlmanac", "()Landroid/location/GnssAlmanac;"); + method_gpsAssistanceGetIonosphericModel = + env->GetMethodID(gpsAssistanceClass, "getIonosphericModel", + "()Landroid/location/KlobucharIonosphericModel;"); + method_gpsAssistanceGetUtcModel = + env->GetMethodID(gpsAssistanceClass, "getUtcModel", "()Landroid/location/UtcModel;"); + method_gpsAssistanceGetLeapSecondsModel = + env->GetMethodID(gpsAssistanceClass, "getLeapSecondsModel", + "()Landroid/location/LeapSecondsModel;"); + method_gpsAssistanceGetTimeModels = + env->GetMethodID(gpsAssistanceClass, "getTimeModels", "()Ljava/util/List;"); + method_gpsAssistanceGetSatelliteEphemeris = + env->GetMethodID(gpsAssistanceClass, "getSatelliteEphemeris", "()Ljava/util/List;"); + method_gpsAssistanceGetRealTimeIntegrityModels = + env->GetMethodID(gpsAssistanceClass, "getRealTimeIntegrityModels", + "()Ljava/util/List;"); + method_gpsAssistanceGetSatelliteCorrections = + env->GetMethodID(gpsAssistanceClass, "getSatelliteCorrections", "()Ljava/util/List;"); + + // Get the methods of GpsSatelliteEphemeris class. + jclass gpsSatelliteEphemerisClass = env->FindClass("android/location/GpsSatelliteEphemeris"); + + method_gpsSatelliteEphemerisGetSvid = + env->GetMethodID(gpsSatelliteEphemerisClass, "getSvid", "()I"); + method_gpsSatelliteEphemerisGetGpsL2Params = + env->GetMethodID(gpsSatelliteEphemerisClass, "getGpsL2Params", + "()Landroid/location/GpsSatelliteEphemeris$GpsL2Params;"); + method_gpsSatelliteEphemerisGetSatelliteClockModel = + env->GetMethodID(gpsSatelliteEphemerisClass, "getSatelliteClockModel", + "()Landroid/location/GpsSatelliteEphemeris$GpsSatelliteClockModel;"); + method_gpsSatelliteEphemerisGetSatelliteOrbitModel = + env->GetMethodID(gpsSatelliteEphemerisClass, "getSatelliteOrbitModel", + "()Landroid/location/KeplerianOrbitModel;"); + method_gpsSatelliteEphemerisGetSatelliteHealth = + env->GetMethodID(gpsSatelliteEphemerisClass, "getSatelliteHealth", + "()Landroid/location/GpsSatelliteEphemeris$GpsSatelliteHealth;"); + method_gpsSatelliteEphemerisGetSatelliteEphemerisTime = + env->GetMethodID(gpsSatelliteEphemerisClass, "getSatelliteEphemerisTime", + "()Landroid/location/SatelliteEphemerisTime;"); + + // Get the methods of GpsL2Params class. + jclass gpsL2ParamsClass = env->FindClass("android/location/GpsSatelliteEphemeris$GpsL2Params"); + method_gpsL2ParamsGetL2Code = env->GetMethodID(gpsL2ParamsClass, "getL2Code", "()I"); + method_gpsL2ParamsGetL2Flag = env->GetMethodID(gpsL2ParamsClass, "getL2Flag", "()I"); + + // Get the methods of GpsSatelliteClockModel class. + jclass gpsSatelliteClockModelClass = + env->FindClass("android/location/GpsSatelliteEphemeris$GpsSatelliteClockModel"); + method_gpsSatelliteClockModelGetAf0 = + env->GetMethodID(gpsSatelliteClockModelClass, "getAf0", "()D"); + method_gpsSatelliteClockModelGetAf1 = + env->GetMethodID(gpsSatelliteClockModelClass, "getAf1", "()D"); + method_gpsSatelliteClockModelGetAf2 = + env->GetMethodID(gpsSatelliteClockModelClass, "getAf2", "()D"); + method_gpsSatelliteClockModelGetTgd = + env->GetMethodID(gpsSatelliteClockModelClass, "getTgd", "()D"); + method_gpsSatelliteClockModelGetIodc = + env->GetMethodID(gpsSatelliteClockModelClass, "getIodc", "()I"); + method_gpsSatelliteClockModelGetTimeOfClockSeconds = + env->GetMethodID(gpsSatelliteClockModelClass, "getTimeOfClockSeconds", "()J"); + + // Get the methods of GpsSatelliteHealth class. + jclass gpsSatelliteHealthClass = + env->FindClass("android/location/GpsSatelliteEphemeris$GpsSatelliteHealth"); + method_gpsSatelliteHealthGetFitInt = + env->GetMethodID(gpsSatelliteHealthClass, "getFitInt", "()D"); + method_gpsSatelliteHealthGetSvAccur = + env->GetMethodID(gpsSatelliteHealthClass, "getSvAccur", "()D"); + method_gpsSatelliteHealthGetSvHealth = + env->GetMethodID(gpsSatelliteHealthClass, "getSvHealth", "()I"); + + // Get the methods of BeidouAssistance class. + jclass beidouAssistanceClass = env->FindClass("android/location/BeidouAssistance"); + method_beidouAssistanceGetAlmanac = env->GetMethodID(beidouAssistanceClass, "getAlmanac", + "()Landroid/location/GnssAlmanac;"); + method_beidouAssistanceGetIonosphericModel = + env->GetMethodID(beidouAssistanceClass, "getIonosphericModel", + "()Landroid/location/KlobucharIonosphericModel;"); + method_beidouAssistanceGetUtcModel = + env->GetMethodID(beidouAssistanceClass, "getUtcModel", "()Landroid/location/UtcModel;"); + method_beidouAssistanceGetLeapSecondsModel = + env->GetMethodID(beidouAssistanceClass, "getLeapSecondsModel", + "()Landroid/location/LeapSecondsModel;"); + method_beidouAssistanceGetTimeModels = + env->GetMethodID(beidouAssistanceClass, "getTimeModels", "()Ljava/util/List;"); + method_beidouAssistanceGetSatelliteEphemeris = + env->GetMethodID(beidouAssistanceClass, "getSatelliteEphemeris", "()Ljava/util/List;"); + method_beidouAssistanceGetSatelliteCorrections = + env->GetMethodID(beidouAssistanceClass, "getSatelliteCorrections", + "()Ljava/util/List;"); + method_beidouAssistanceGetRealTimeIntegrityModels = + env->GetMethodID(beidouAssistanceClass, "getRealTimeIntegrityModels", + "()Ljava/util/List;"); + + // Get the methods of BeidouSatelliteEphemeris class. + jclass beidouSatelliteEphemerisClass = + env->FindClass("android/location/BeidouSatelliteEphemeris"); + method_beidouSatelliteEphemerisGetSvid = + env->GetMethodID(beidouSatelliteEphemerisClass, "getSvid", "()I"); + method_beidouSatelliteEphemerisGetSatelliteClockModel = + env->GetMethodID(beidouSatelliteEphemerisClass, "getSatelliteClockModel", + "()Landroid/location/" + "BeidouSatelliteEphemeris$BeidouSatelliteClockModel;"); + method_beidouSatelliteEphemerisGetSatelliteOrbitModel = + env->GetMethodID(beidouSatelliteEphemerisClass, "getSatelliteOrbitModel", + "()Landroid/location/KeplerianOrbitModel;"); + method_beidouSatelliteEphemerisGetSatelliteHealth = + env->GetMethodID(beidouSatelliteEphemerisClass, "getSatelliteHealth", + "()Landroid/location/BeidouSatelliteEphemeris$BeidouSatelliteHealth;"); + method_beidouSatelliteEphemerisGetSatelliteEphemerisTime = + env->GetMethodID(beidouSatelliteEphemerisClass, "getSatelliteEphemerisTime", + "()Landroid/location/" + "BeidouSatelliteEphemeris$BeidouSatelliteEphemerisTime;"); + + // Get the methods of BeidouSatelliteClockModel + jclass beidouSatelliteClockModelClass = + env->FindClass("android/location/BeidouSatelliteEphemeris$BeidouSatelliteClockModel"); + method_beidouSatelliteClockModelGetAf0 = + env->GetMethodID(beidouSatelliteClockModelClass, "getAf0", "()D"); + method_beidouSatelliteClockModelGetAf1 = + env->GetMethodID(beidouSatelliteClockModelClass, "getAf1", "()D"); + method_beidouSatelliteClockModelGetAf2 = + env->GetMethodID(beidouSatelliteClockModelClass, "getAf2", "()D"); + method_beidouSatelliteClockModelGetAodc = + env->GetMethodID(beidouSatelliteClockModelClass, "getAodc", "()I"); + method_beidouSatelliteClockModelGetTgd1 = + env->GetMethodID(beidouSatelliteClockModelClass, "getTgd1", "()D"); + method_beidouSatelliteClockModelGetTgd2 = + env->GetMethodID(beidouSatelliteClockModelClass, "getTgd2", "()D"); + method_beidouSatelliteClockModelGetTimeOfClockSeconds = + env->GetMethodID(beidouSatelliteClockModelClass, "getTimeOfClockSeconds", "()J"); + + // Get the methods of BeidouSatelliteHealth + jclass beidouSatelliteHealthClass = + env->FindClass("android/location/BeidouSatelliteEphemeris$BeidouSatelliteHealth"); + method_beidouSatelliteHealthGetSatH1 = + env->GetMethodID(beidouSatelliteHealthClass, "getSatH1", "()I"); + method_beidouSatelliteHealthGetSvAccur = + env->GetMethodID(beidouSatelliteHealthClass, "getSvAccur", "()D"); + + // Get the methods of BeidouSatelliteEphemerisTime + jclass beidouSatelliteEphemerisTimeClass = env->FindClass( + "android/location/BeidouSatelliteEphemeris$BeidouSatelliteEphemerisTime"); + method_beidouSatelliteEphemerisTimeGetIode = + env->GetMethodID(beidouSatelliteEphemerisTimeClass, "getIode", "()I"); + method_beidouSatelliteEphemerisTimeGetBeidouWeekNumber = + env->GetMethodID(beidouSatelliteEphemerisTimeClass, "getBeidouWeekNumber", "()I"); + method_beidouSatelliteEphemerisTimeGetToeSeconds = + env->GetMethodID(beidouSatelliteEphemerisTimeClass, "getToeSeconds", "()I"); + + // Get the methods of GalileoAssistance class. + jclass galileoAssistanceClass = env->FindClass("android/location/GalileoAssistance"); + method_galileoAssistanceGetAlmanac = env->GetMethodID(galileoAssistanceClass, "getAlmanac", + "()Landroid/location/GnssAlmanac;"); + method_galileoAssistanceGetIonosphericModel = + env->GetMethodID(galileoAssistanceClass, "getIonosphericModel", + "()Landroid/location/KlobucharIonosphericModel;"); + method_galileoAssistanceGetUtcModel = env->GetMethodID(galileoAssistanceClass, "getUtcModel", + "()Landroid/location/UtcModel;"); + method_galileoAssistanceGetLeapSecondsModel = + env->GetMethodID(galileoAssistanceClass, "getLeapSecondsModel", + "()Landroid/location/LeapSecondsModel;"); + method_galileoAssistanceGetTimeModels = + env->GetMethodID(galileoAssistanceClass, "getTimeModels", "()Ljava/util/List;"); + method_galileoAssistanceGetSatelliteEphemeris = + env->GetMethodID(galileoAssistanceClass, "getSatelliteEphemeris", "()Ljava/util/List;"); + method_galileoAssistanceGetSatelliteCorrections = + env->GetMethodID(galileoAssistanceClass, "getSatelliteCorrections", + "()Ljava/util/List;"); + method_galileoAssistanceGetRealTimeIntegrityModels = + env->GetMethodID(galileoAssistanceClass, "getRealTimeIntegrityModels", + "()Ljava/util/List;"); + + // Get the methods of GalileoSatelliteEphemeris class + jclass galileoSatelliteEphemerisClass = + env->FindClass("android/location/GalileoSatelliteEphemeris"); + method_galileoSatelliteEphemerisGetSatelliteClockModels = + env->GetMethodID(galileoSatelliteEphemerisClass, "getSatelliteClockModels", + "()Ljava/util/List;"); + method_galileoSatelliteEphemerisGetSvid = + env->GetMethodID(galileoSatelliteEphemerisClass, "getSvid", "()I"); + method_galileoSatelliteEphemerisGetSatelliteEphemerisTime = + env->GetMethodID(galileoSatelliteEphemerisClass, "getSatelliteEphemerisTime", + "()Landroid/location/SatelliteEphemerisTime;"); + method_galileoSatelliteEphemerisGetSatelliteHealth = + env->GetMethodID(galileoSatelliteEphemerisClass, "getSatelliteHealth", + "()Landroid/location/GalileoSatelliteEphemeris$GalileoSvHealth;"); + method_galileoSatelliteEphemerisGetSatelliteOrbitModel = + env->GetMethodID(galileoSatelliteEphemerisClass, "getSatelliteOrbitModel", + "()Landroid/location/KeplerianOrbitModel;"); + + // Get the methods of GalileoSatelliteClockModel class. + jclass galileoSatelliteClockModelClass = + env->FindClass("android/location/GalileoSatelliteEphemeris$GalileoSatelliteClockModel"); + method_galileoSatelliteClockModelGetAf0 = + env->GetMethodID(galileoSatelliteClockModelClass, "getAf0", "()D"); + method_galileoSatelliteClockModelGetAf1 = + env->GetMethodID(galileoSatelliteClockModelClass, "getAf1", "()D"); + method_galileoSatelliteClockModelGetAf2 = + env->GetMethodID(galileoSatelliteClockModelClass, "getAf2", "()D"); + method_galileoSatelliteClockModelGetBgdSeconds = + env->GetMethodID(galileoSatelliteClockModelClass, "getBgdSeconds", "()D"); + method_galileoSatelliteClockModelGetSatelliteClockType = + env->GetMethodID(galileoSatelliteClockModelClass, "getSatelliteClockType", "()I"); + method_galileoSatelliteClockModelGetSisaMeters = + env->GetMethodID(galileoSatelliteClockModelClass, "getSisaMeters", "()D"); + method_galileoSatelliteClockModelGetTimeOfClockSeconds = + env->GetMethodID(galileoSatelliteClockModelClass, "getTimeOfClockSeconds", "()J"); + + // Get the methods of GalileoSvHealth class. + jclass galileoSvHealthClass = + env->FindClass("android/location/GalileoSatelliteEphemeris$GalileoSvHealth"); + method_galileoSvHealthGetDataValidityStatusE1b = + env->GetMethodID(galileoSvHealthClass, "getDataValidityStatusE1b", "()I"); + method_galileoSvHealthGetDataValidityStatusE5a = + env->GetMethodID(galileoSvHealthClass, "getDataValidityStatusE5a", "()I"); + method_galileoSvHealthGetDataValidityStatusE5b = + env->GetMethodID(galileoSvHealthClass, "getDataValidityStatusE5b", "()I"); + method_galileoSvHealthGetSignalHealthStatusE1b = + env->GetMethodID(galileoSvHealthClass, "getSignalHealthStatusE1b", "()I"); + method_galileoSvHealthGetSignalHealthStatusE5a = + env->GetMethodID(galileoSvHealthClass, "getSignalHealthStatusE5a", "()I"); + method_galileoSvHealthGetSignalHealthStatusE5b = + env->GetMethodID(galileoSvHealthClass, "getSignalHealthStatusE5b", "()I"); + + // Get the methods of GalileoIonosphericModel class. + jclass galileoIonosphericModelClass = + env->FindClass("android/location/GalileoIonosphericModel"); + method_galileoIonosphericModelGetAi0 = + env->GetMethodID(galileoIonosphericModelClass, "getAi0", "()D"); + method_galileoIonosphericModelGetAi1 = + env->GetMethodID(galileoIonosphericModelClass, "getAi1", "()D"); + method_galileoIonosphericModelGetAi2 = + env->GetMethodID(galileoIonosphericModelClass, "getAi2", "()D"); + + // Get the methods of GlonassAssistance class. + jclass glonassAssistanceClass = env->FindClass("android/location/GlonassAssistance"); + method_glonassAssistanceGetAlmanac = env->GetMethodID(glonassAssistanceClass, "getAlmanac", + "()Landroid/location/GlonassAlmanac;"); + method_glonassAssistanceGetUtcModel = env->GetMethodID(glonassAssistanceClass, "getUtcModel", + "()Landroid/location/UtcModel;"); + method_glonassAssistanceGetTimeModels = + env->GetMethodID(glonassAssistanceClass, "getTimeModels", "()Ljava/util/List;"); + method_glonassAssistanceGetSatelliteEphemeris = + env->GetMethodID(glonassAssistanceClass, "getSatelliteEphemeris", "()Ljava/util/List;"); + method_glonassAssistanceGetSatelliteCorrections = + env->GetMethodID(glonassAssistanceClass, "getSatelliteCorrections", + "()Ljava/util/List;"); + + // Get the methods of GlonassAlmanac class. + jclass glonassAlmanacClass = env->FindClass("android/location/GlonassAlmanac"); + method_glonassAlmanacGetIssueDateMillis = + env->GetMethodID(glonassAlmanacClass, "getIssueDateMillis", "()J"); + method_glonassAlmanacGetSatelliteAlmanacs = + env->GetMethodID(glonassAlmanacClass, "getSatelliteAlmanacs", "()Ljava/util/List;"); + + // Get the methods of GlonassSatelliteAlmanac class + jclass glonassSatelliteAlmanacClass = + env->FindClass("android/location/GlonassAlmanac$GlonassSatelliteAlmanac"); + method_glonassSatelliteAlmanacGetDeltaI = + env->GetMethodID(glonassSatelliteAlmanacClass, "getDeltaI", "()D"); + method_glonassSatelliteAlmanacGetDeltaT = + env->GetMethodID(glonassSatelliteAlmanacClass, "getDeltaT", "()D"); + method_glonassSatelliteAlmanacGetDeltaTDot = + env->GetMethodID(glonassSatelliteAlmanacClass, "getDeltaTDot", "()D"); + method_glonassSatelliteAlmanacGetEccentricity = + env->GetMethodID(glonassSatelliteAlmanacClass, "getEccentricity", "()D"); + method_glonassSatelliteAlmanacGetFrequencyChannelNumber = + env->GetMethodID(glonassSatelliteAlmanacClass, "getFrequencyChannelNumber", "()I"); + method_glonassSatelliteAlmanacGetLambda = + env->GetMethodID(glonassSatelliteAlmanacClass, "getLambda", "()D"); + method_glonassSatelliteAlmanacGetOmega = + env->GetMethodID(glonassSatelliteAlmanacClass, "getOmega", "()D"); + method_glonassSatelliteAlmanacGetSlotNumber = + env->GetMethodID(glonassSatelliteAlmanacClass, "getSlotNumber", "()I"); + method_glonassSatelliteAlmanacGetHealthState = + env->GetMethodID(glonassSatelliteAlmanacClass, "getHealthState", "()I"); + method_glonassSatelliteAlmanacGetTLambda = + env->GetMethodID(glonassSatelliteAlmanacClass, "getTLambda", "()D"); + method_glonassSatelliteAlmanacGetTau = + env->GetMethodID(glonassSatelliteAlmanacClass, "getTau", "()D"); + method_glonassSatelliteAlmanacGetCalendarDayNumber = + env->GetMethodID(glonassSatelliteAlmanacClass, "getCalendarDayNumber", "()I"); + method_glonassSatelliteAlmanacGetIsGlonassM = + env->GetMethodID(glonassSatelliteAlmanacClass, "isGlonassM", "()Z"); + + // Get the methods of GlonassSatelliteEphemeris + jclass glonassSatelliteEphemerisClass = + env->FindClass("android/location/GlonassSatelliteEphemeris"); + method_glonassSatelliteEphemerisGetAgeInDays = + env->GetMethodID(glonassSatelliteEphemerisClass, "getAgeInDays", "()I"); + method_glonassSatelliteEphemerisGetFrameTimeSeconds = + env->GetMethodID(glonassSatelliteEphemerisClass, "getFrameTimeSeconds", "()D"); + method_glonassSatelliteEphemerisGetHealthState = + env->GetMethodID(glonassSatelliteEphemerisClass, "getHealthState", "()I"); + method_glonassSatelliteEphemerisGetSlotNumber = + env->GetMethodID(glonassSatelliteEphemerisClass, "getSlotNumber", "()I"); + method_glonassSatelliteEphemerisGetSatelliteClockModel = + env->GetMethodID(glonassSatelliteEphemerisClass, "getSatelliteClockModel", + "()Landroid/location/" + "GlonassSatelliteEphemeris$GlonassSatelliteClockModel;"); + method_glonassSatelliteEphemerisGetSatelliteOrbitModel = + env->GetMethodID(glonassSatelliteEphemerisClass, "getSatelliteOrbitModel", + "()Landroid/location/" + "GlonassSatelliteEphemeris$GlonassSatelliteOrbitModel;"); + method_glonassSatelliteEphemerisGetUpdateIntervalMinutes = + env->GetMethodID(glonassSatelliteEphemerisClass, "getUpdateIntervalMinutes", "()I"); + method_glonassSatelliteEphemerisGetIsGlonassM = + env->GetMethodID(glonassSatelliteEphemerisClass, "isGlonassM", "()Z"); + method_glonassSatelliteEphemerisGetIsUpdateIntervalOdd = + env->GetMethodID(glonassSatelliteEphemerisClass, "isUpdateIntervalOdd", "()Z"); + + // Get the methods of GlonassSatelliteOrbitModel + jclass glonassSatelliteOrbitModelClass = + env->FindClass("android/location/GlonassSatelliteEphemeris$GlonassSatelliteOrbitModel"); + method_glonassSatelliteOrbitModelGetX = + env->GetMethodID(glonassSatelliteOrbitModelClass, "getX", "()D"); + method_glonassSatelliteOrbitModelGetXAccel = + env->GetMethodID(glonassSatelliteOrbitModelClass, "getXAccel", "()D"); + method_glonassSatelliteOrbitModelGetXDot = + env->GetMethodID(glonassSatelliteOrbitModelClass, "getXDot", "()D"); + method_glonassSatelliteOrbitModelGetY = + env->GetMethodID(glonassSatelliteOrbitModelClass, "getY", "()D"); + method_glonassSatelliteOrbitModelGetYAccel = + env->GetMethodID(glonassSatelliteOrbitModelClass, "getYAccel", "()D"); + method_glonassSatelliteOrbitModelGetYDot = + env->GetMethodID(glonassSatelliteOrbitModelClass, "getYDot", "()D"); + method_glonassSatelliteOrbitModelGetZ = + env->GetMethodID(glonassSatelliteOrbitModelClass, "getZ", "()D"); + method_glonassSatelliteOrbitModelGetZAccel = + env->GetMethodID(glonassSatelliteOrbitModelClass, "getZAccel", "()D"); + method_glonassSatelliteOrbitModelGetZDot = + env->GetMethodID(glonassSatelliteOrbitModelClass, "getZDot", "()D"); + + // Get the methods of GlonassSatelliteClockModel + jclass glonassSatelliteClockModelClass = + env->FindClass("android/location/GlonassSatelliteEphemeris$GlonassSatelliteClockModel"); + method_glonassSatelliteClockModelGetClockBias = + env->GetMethodID(glonassSatelliteClockModelClass, "getClockBias", "()D"); + method_glonassSatelliteClockModelGetFrequencyBias = + env->GetMethodID(glonassSatelliteClockModelClass, "getFrequencyBias", "()D"); + method_glonassSatelliteClockModelGetFrequencyChannelNumber = + env->GetMethodID(glonassSatelliteClockModelClass, "getFrequencyChannelNumber", "()I"); + method_glonassSatelliteClockModelGetTimeOfClockSeconds = + env->GetMethodID(glonassSatelliteClockModelClass, "getTimeOfClockSeconds", "()J"); + + // Get the methods of QzssAssistance class. + jclass qzssAssistanceClass = env->FindClass("android/location/QzssAssistance"); + method_qzssAssistanceGetAlmanac = + env->GetMethodID(qzssAssistanceClass, "getAlmanac", "()Landroid/location/GnssAlmanac;"); + method_qzssAssistanceGetIonosphericModel = + env->GetMethodID(qzssAssistanceClass, "getIonosphericModel", + "()Landroid/location/KlobucharIonosphericModel;"); + method_qzssAssistanceGetUtcModel = + env->GetMethodID(qzssAssistanceClass, "getUtcModel", "()Landroid/location/UtcModel;"); + method_qzssAssistanceGetLeapSecondsModel = + env->GetMethodID(qzssAssistanceClass, "getLeapSecondsModel", + "()Landroid/location/LeapSecondsModel;"); + method_qzssAssistanceGetTimeModels = + env->GetMethodID(qzssAssistanceClass, "getTimeModels", "()Ljava/util/List;"); + method_qzssAssistanceGetSatelliteEphemeris = + env->GetMethodID(qzssAssistanceClass, "getSatelliteEphemeris", "()Ljava/util/List;"); + method_qzssAssistanceGetSatelliteCorrections = + env->GetMethodID(qzssAssistanceClass, "getSatelliteCorrections", "()Ljava/util/List;"); + + // Get the methods of QzssSatelliteEphemeris class. + jclass qzssSatelliteEphemerisClass = env->FindClass("android/location/QzssSatelliteEphemeris"); + method_qzssSatelliteEphemerisGetSvid = + env->GetMethodID(qzssSatelliteEphemerisClass, "getSvid", "()I"); + method_qzssSatelliteEphemerisGetGpsL2Params = + env->GetMethodID(qzssSatelliteEphemerisClass, "getGpsL2Params", + "()Landroid/location/GpsSatelliteEphemeris$GpsL2Params;"); + method_qzssSatelliteEphemerisGetSatelliteEphemerisTime = + env->GetMethodID(qzssSatelliteEphemerisClass, "getSatelliteEphemerisTime", + "()Landroid/location/SatelliteEphemerisTime;"); + method_qzssSatelliteEphemerisGetSatelliteHealth = + env->GetMethodID(qzssSatelliteEphemerisClass, "getSatelliteHealth", + "()Landroid/location/GpsSatelliteEphemeris$GpsSatelliteHealth;"); + method_qzssSatelliteEphemerisGetSatelliteOrbitModel = + env->GetMethodID(qzssSatelliteEphemerisClass, "getSatelliteOrbitModel", + "()Landroid/location/KeplerianOrbitModel;"); +} + +GnssAssistanceInterface::GnssAssistanceInterface( + const sp<IGnssAssistanceInterface>& iGnssAssistance) + : mGnssAssistanceInterface(iGnssAssistance) { + assert(mGnssAssistanceInterface != nullptr); +} + +jboolean GnssAssistanceInterface::injectGnssAssistance(JNIEnv* env, jobject gnssAssistanceObj) { + GnssAssistance gnssAssistance; + GnssAssistanceUtil::setGnssAssistance(env, gnssAssistanceObj, gnssAssistance); + auto status = mGnssAssistanceInterface->injectGnssAssistance(gnssAssistance); + return checkAidlStatus(status, "IGnssAssistanceInterface injectGnssAssistance() failed."); +} + +jboolean GnssAssistanceInterface::setCallback(const sp<IGnssAssistanceCallback>& callback) { + auto status = mGnssAssistanceInterface->setCallback(callback); + return checkAidlStatus(status, "IGnssAssistanceInterface setCallback() failed."); +} + +void GnssAssistanceUtil::setGnssAssistance(JNIEnv* env, jobject gnssAssistanceObj, + GnssAssistance& gnssAssistance) { + jobject gpsAssistanceObj = + env->CallObjectMethod(gnssAssistanceObj, method_gnssAssistanceGetGpsAssistance); + jobject glonassAssistanceObj = + env->CallObjectMethod(gnssAssistanceObj, method_gnssAssistanceGetGlonassAssistance); + jobject qzssAssistanceObj = + env->CallObjectMethod(gnssAssistanceObj, method_gnssAssistanceGetQzssAssistance); + jobject galileoAssistanceObj = + env->CallObjectMethod(gnssAssistanceObj, method_gnssAssistanceGetGalileoAssistance); + jobject beidouAssistanceObj = + env->CallObjectMethod(gnssAssistanceObj, method_gnssAssistanceGetBeidouAssistance); + GnssAssistanceUtil::setGpsAssistance(env, gpsAssistanceObj, gnssAssistance.gpsAssistance); + GnssAssistanceUtil::setGlonassAssistance(env, glonassAssistanceObj, + gnssAssistance.glonassAssistance); + GnssAssistanceUtil::setQzssAssistance(env, qzssAssistanceObj, gnssAssistance.qzssAssistance); + GnssAssistanceUtil::setGalileoAssistance(env, galileoAssistanceObj, + gnssAssistance.galileoAssistance); + GnssAssistanceUtil::setBeidouAssistance(env, beidouAssistanceObj, + gnssAssistance.beidouAssistance); + env->DeleteLocalRef(gpsAssistanceObj); + env->DeleteLocalRef(glonassAssistanceObj); + env->DeleteLocalRef(qzssAssistanceObj); + env->DeleteLocalRef(galileoAssistanceObj); + env->DeleteLocalRef(beidouAssistanceObj); +} + +void GnssAssistanceUtil::setQzssAssistance(JNIEnv* env, jobject qzssAssistanceObj, + QzssAssistance& qzssAssistance) { + jobject qzssAlmanacObj = + env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetAlmanac); + jobject qzssIonosphericModelObj = + env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetIonosphericModel); + jobject qzssUtcModelObj = + env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetUtcModel); + jobject qzssLeapSecondsModelObj = + env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetLeapSecondsModel); + jobject qzssTimeModelsObj = + env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetTimeModels); + jobject qzssSatelliteEphemerisObj = + env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetSatelliteEphemeris); + jobject qzssSatelliteCorrectionsObj = + env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetSatelliteCorrections); + setGnssAlmanac(env, qzssAlmanacObj, qzssAssistance.almanac); + setKlobucharIonosphericModel(env, qzssIonosphericModelObj, qzssAssistance.ionosphericModel); + setUtcModel(env, qzssUtcModelObj, qzssAssistance.utcModel); + setLeapSecondsModel(env, qzssLeapSecondsModelObj, qzssAssistance.leapSecondsModel); + setTimeModels(env, qzssTimeModelsObj, qzssAssistance.timeModels); + setGpsOrQzssSatelliteEphemeris<QzssSatelliteEphemeris>(env, qzssSatelliteEphemerisObj, + qzssAssistance.satelliteEphemeris); + setSatelliteCorrections(env, qzssSatelliteCorrectionsObj, qzssAssistance.satelliteCorrections); + env->DeleteLocalRef(qzssAlmanacObj); + env->DeleteLocalRef(qzssIonosphericModelObj); + env->DeleteLocalRef(qzssUtcModelObj); + env->DeleteLocalRef(qzssLeapSecondsModelObj); + env->DeleteLocalRef(qzssTimeModelsObj); + env->DeleteLocalRef(qzssSatelliteEphemerisObj); + env->DeleteLocalRef(qzssSatelliteCorrectionsObj); +} + +void GnssAssistanceUtil::setGlonassAssistance(JNIEnv* env, jobject glonassAssistanceObj, + GlonassAssistance& galileoAssistance) { + jobject glonassAlmanacObj = + env->CallObjectMethod(glonassAssistanceObj, method_glonassAssistanceGetAlmanac); + jobject utcModelObj = + env->CallObjectMethod(glonassAssistanceObj, method_glonassAssistanceGetUtcModel); + jobject timeModelsObj = + env->CallObjectMethod(glonassAssistanceObj, method_glonassAssistanceGetTimeModels); + jobject satelliteEphemerisObj = + env->CallObjectMethod(glonassAssistanceObj, + method_glonassAssistanceGetSatelliteEphemeris); + jobject satelliteCorrectionsObj = + env->CallObjectMethod(glonassAssistanceObj, + method_glonassAssistanceGetSatelliteCorrections); + setGlonassAlmanac(env, glonassAlmanacObj, galileoAssistance.almanac); + setUtcModel(env, utcModelObj, galileoAssistance.utcModel); + setTimeModels(env, timeModelsObj, galileoAssistance.timeModels); + setGlonassSatelliteEphemeris(env, satelliteEphemerisObj, galileoAssistance.satelliteEphemeris); + setSatelliteCorrections(env, satelliteCorrectionsObj, galileoAssistance.satelliteCorrections); + env->DeleteLocalRef(glonassAlmanacObj); + env->DeleteLocalRef(utcModelObj); + env->DeleteLocalRef(timeModelsObj); + env->DeleteLocalRef(satelliteEphemerisObj); + env->DeleteLocalRef(satelliteCorrectionsObj); +} + +void GnssAssistanceUtil::setGlonassAlmanac(JNIEnv* env, jobject glonassAlmanacObj, + GlonassAlmanac& glonassAlmanac) { + if (glonassAlmanacObj == nullptr) { + glonassAlmanac.issueDateMs = -1; + return; + } + jlong issueDateMillis = + env->CallLongMethod(glonassAlmanacObj, method_glonassAlmanacGetIssueDateMillis); + glonassAlmanac.issueDateMs = issueDateMillis; + jobject satelliteAlmanacsObj = + env->CallObjectMethod(glonassAlmanacObj, method_glonassAlmanacGetSatelliteAlmanacs); + if (satelliteAlmanacsObj == nullptr) return; + auto len = env->CallIntMethod(satelliteAlmanacsObj, method_listSize); + for (uint16_t i = 0; i < len; ++i) { + jobject glonassSatelliteAlmanacObj = + env->CallObjectMethod(satelliteAlmanacsObj, method_listGet, i); + if (glonassSatelliteAlmanacObj == nullptr) continue; + GlonassSatelliteAlmanac glonassSatelliteAlmanac; + jdouble deltaI = env->CallDoubleMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetDeltaI); + glonassSatelliteAlmanac.deltaI = deltaI; + jdouble deltaT = env->CallDoubleMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetDeltaT); + glonassSatelliteAlmanac.deltaT = deltaT; + jdouble deltaTDot = env->CallDoubleMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetDeltaTDot); + glonassSatelliteAlmanac.deltaTDot = deltaTDot; + jdouble eccentricity = env->CallDoubleMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetEccentricity); + glonassSatelliteAlmanac.eccentricity = eccentricity; + jint frequencyChannelNumber = + env->CallIntMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetFrequencyChannelNumber); + glonassSatelliteAlmanac.frequencyChannelNumber = + static_cast<int32_t>(frequencyChannelNumber); + jdouble lambda = env->CallDoubleMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetLambda); + glonassSatelliteAlmanac.lambda = lambda; + jdouble omega = env->CallDoubleMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetOmega); + glonassSatelliteAlmanac.omega = omega; + jint slotNumber = env->CallIntMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetSlotNumber); + glonassSatelliteAlmanac.slotNumber = static_cast<int32_t>(slotNumber); + jint healthState = env->CallIntMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetHealthState); + glonassSatelliteAlmanac.svHealth = static_cast<int32_t>(healthState); + jdouble tLambda = env->CallDoubleMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetTLambda); + glonassSatelliteAlmanac.tLambda = tLambda; + jdouble tau = env->CallDoubleMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetTau); + glonassSatelliteAlmanac.tau = tau; + jboolean isGlonassM = env->CallBooleanMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetIsGlonassM); + glonassSatelliteAlmanac.isGlonassM = isGlonassM; + jint calendarDayNumber = + env->CallIntMethod(glonassSatelliteAlmanacObj, + method_glonassSatelliteAlmanacGetCalendarDayNumber); + glonassSatelliteAlmanac.calendarDayNumber = static_cast<int32_t>(calendarDayNumber); + glonassAlmanac.satelliteAlmanacs.push_back(glonassSatelliteAlmanac); + env->DeleteLocalRef(glonassSatelliteAlmanacObj); + } + env->DeleteLocalRef(satelliteAlmanacsObj); +} + +void GnssAssistanceUtil::setGlonassSatelliteEphemeris( + JNIEnv* env, jobject glonassSatelliteEphemerisListObj, + std::vector<GlonassSatelliteEphemeris>& glonassSatelliteEphemerisList) { + if (glonassSatelliteEphemerisListObj == nullptr) return; + auto len = env->CallIntMethod(glonassSatelliteEphemerisListObj, method_listSize); + for (uint16_t i = 0; i < len; ++i) { + jobject glonassSatelliteEphemerisObj = + env->CallObjectMethod(glonassSatelliteEphemerisListObj, method_listGet, i); + if (glonassSatelliteEphemerisObj == nullptr) continue; + GlonassSatelliteEphemeris glonassSatelliteEphemeris; + jdouble ageInDays = env->CallDoubleMethod(glonassSatelliteEphemerisObj, + method_glonassSatelliteEphemerisGetAgeInDays); + glonassSatelliteEphemeris.ageInDays = ageInDays; + + // Set the GlonassSatelliteClockModel. + jobject glonassSatelliteClockModelObj = + env->CallObjectMethod(glonassSatelliteEphemerisObj, + method_glonassSatelliteEphemerisGetSatelliteClockModel); + GlonassSatelliteClockModel glonassSatelliteClockModel; + jdouble clockBias = env->CallDoubleMethod(glonassSatelliteClockModelObj, + method_glonassSatelliteClockModelGetClockBias); + glonassSatelliteClockModel.clockBias = clockBias; + jdouble frequencyBias = + env->CallDoubleMethod(glonassSatelliteClockModelObj, + method_glonassSatelliteClockModelGetFrequencyBias); + glonassSatelliteClockModel.frequencyBias = frequencyBias; + jint frequencyChannelNumber = + env->CallIntMethod(glonassSatelliteClockModelObj, + method_glonassSatelliteClockModelGetFrequencyChannelNumber); + glonassSatelliteClockModel.frequencyChannelNumber = + static_cast<int32_t>(frequencyChannelNumber); + jdouble timeOfClockSeconds = + env->CallDoubleMethod(glonassSatelliteClockModelObj, + method_glonassSatelliteClockModelGetTimeOfClockSeconds); + glonassSatelliteClockModel.timeOfClockSeconds = timeOfClockSeconds; + glonassSatelliteEphemeris.satelliteClockModel = glonassSatelliteClockModel; + env->DeleteLocalRef(glonassSatelliteClockModelObj); + + // Set the GlonassSatelliteOrbitModel. + jobject glonassSatelliteOrbitModelObj = + env->CallObjectMethod(glonassSatelliteEphemerisObj, + method_glonassSatelliteEphemerisGetSatelliteOrbitModel); + GlonassSatelliteOrbitModel glonassSatelliteOrbitModel; + jdouble x = env->CallDoubleMethod(glonassSatelliteOrbitModelObj, + method_glonassSatelliteOrbitModelGetX); + glonassSatelliteOrbitModel.x = x; + jdouble y = env->CallDoubleMethod(glonassSatelliteOrbitModelObj, + method_glonassSatelliteOrbitModelGetY); + glonassSatelliteOrbitModel.y = y; + jdouble z = env->CallDoubleMethod(glonassSatelliteOrbitModelObj, + method_glonassSatelliteOrbitModelGetZ); + glonassSatelliteOrbitModel.z = z; + jdouble xAccel = env->CallDoubleMethod(glonassSatelliteOrbitModelObj, + method_glonassSatelliteOrbitModelGetXAccel); + glonassSatelliteOrbitModel.xAccel = xAccel; + jdouble yAccel = env->CallDoubleMethod(glonassSatelliteOrbitModelObj, + method_glonassSatelliteOrbitModelGetYAccel); + glonassSatelliteOrbitModel.yAccel = yAccel; + jdouble zAccel = env->CallDoubleMethod(glonassSatelliteOrbitModelObj, + method_glonassSatelliteOrbitModelGetZAccel); + glonassSatelliteOrbitModel.zAccel = zAccel; + jdouble xDot = env->CallDoubleMethod(glonassSatelliteOrbitModelObj, + method_glonassSatelliteOrbitModelGetXDot); + glonassSatelliteOrbitModel.xDot = xDot; + jdouble yDot = env->CallDoubleMethod(glonassSatelliteOrbitModelObj, + method_glonassSatelliteOrbitModelGetYDot); + glonassSatelliteOrbitModel.yDot = yDot; + jdouble zDot = env->CallDoubleMethod(glonassSatelliteOrbitModelObj, + method_glonassSatelliteOrbitModelGetZDot); + glonassSatelliteOrbitModel.zDot = zDot; + glonassSatelliteEphemeris.satelliteOrbitModel = glonassSatelliteOrbitModel; + env->DeleteLocalRef(glonassSatelliteOrbitModelObj); + + jint healthState = env->CallIntMethod(glonassSatelliteEphemerisObj, + method_glonassSatelliteEphemerisGetHealthState); + glonassSatelliteEphemeris.svHealth = static_cast<int32_t>(healthState); + jint slotNumber = env->CallIntMethod(glonassSatelliteEphemerisObj, + method_glonassSatelliteEphemerisGetSlotNumber); + glonassSatelliteEphemeris.slotNumber = static_cast<int32_t>(slotNumber); + jdouble frameTimeSeconds = + env->CallDoubleMethod(glonassSatelliteEphemerisObj, + method_glonassSatelliteEphemerisGetFrameTimeSeconds); + glonassSatelliteEphemeris.frameTimeSeconds = frameTimeSeconds; + jint updateIntervalMinutes = + env->CallIntMethod(glonassSatelliteEphemerisObj, + method_glonassSatelliteEphemerisGetUpdateIntervalMinutes); + glonassSatelliteEphemeris.updateIntervalMinutes = + static_cast<int32_t>(updateIntervalMinutes); + jboolean isGlonassM = env->CallBooleanMethod(glonassSatelliteEphemerisObj, + method_glonassSatelliteEphemerisGetIsGlonassM); + glonassSatelliteEphemeris.isGlonassM = isGlonassM; + jboolean isUpdateIntervalOdd = + env->CallBooleanMethod(glonassSatelliteEphemerisObj, + method_glonassSatelliteEphemerisGetIsUpdateIntervalOdd); + glonassSatelliteEphemeris.isOddUpdateInterval = isUpdateIntervalOdd; + glonassSatelliteEphemerisList.push_back(glonassSatelliteEphemeris); + env->DeleteLocalRef(glonassSatelliteEphemerisObj); + } +} + +void GnssAssistanceUtil::setGalileoAssistance(JNIEnv* env, jobject galileoAssistanceObj, + GalileoAssistance& galileoAssistance) { + jobject galileoAlmanacObj = + env->CallObjectMethod(galileoAssistanceObj, method_galileoAssistanceGetAlmanac); + jobject ionosphericModelObj = + env->CallObjectMethod(galileoAssistanceObj, + method_galileoAssistanceGetIonosphericModel); + jobject utcModelObj = + env->CallObjectMethod(galileoAssistanceObj, method_galileoAssistanceGetUtcModel); + jobject leapSecondsModelObj = + env->CallObjectMethod(galileoAssistanceObj, + method_galileoAssistanceGetLeapSecondsModel); + jobject timeModelsObj = + env->CallObjectMethod(galileoAssistanceObj, method_galileoAssistanceGetTimeModels); + jobject satelliteEphemerisObj = + env->CallObjectMethod(galileoAssistanceObj, + method_galileoAssistanceGetSatelliteEphemeris); + jobject realTimeIntegrityModelsObj = + env->CallObjectMethod(galileoAssistanceObj, + method_galileoAssistanceGetRealTimeIntegrityModels); + jobject satelliteCorrectionsObj = + env->CallObjectMethod(galileoAssistanceObj, + method_galileoAssistanceGetSatelliteCorrections); + setGnssAlmanac(env, galileoAlmanacObj, galileoAssistance.almanac); + setGaliloKlobucharIonosphericModel(env, ionosphericModelObj, + galileoAssistance.ionosphericModel); + setUtcModel(env, utcModelObj, galileoAssistance.utcModel); + setLeapSecondsModel(env, leapSecondsModelObj, galileoAssistance.leapSecondsModel); + setTimeModels(env, timeModelsObj, galileoAssistance.timeModels); + setGalileoSatelliteEphemeris(env, satelliteEphemerisObj, galileoAssistance.satelliteEphemeris); + setRealTimeIntegrityModels(env, realTimeIntegrityModelsObj, + galileoAssistance.realTimeIntegrityModels); + setSatelliteCorrections(env, satelliteCorrectionsObj, galileoAssistance.satelliteCorrections); + env->DeleteLocalRef(galileoAlmanacObj); + env->DeleteLocalRef(ionosphericModelObj); + env->DeleteLocalRef(utcModelObj); + env->DeleteLocalRef(leapSecondsModelObj); + env->DeleteLocalRef(timeModelsObj); + env->DeleteLocalRef(satelliteEphemerisObj); + env->DeleteLocalRef(realTimeIntegrityModelsObj); + env->DeleteLocalRef(satelliteCorrectionsObj); +} + +void GnssAssistanceUtil::setGaliloKlobucharIonosphericModel( + JNIEnv* env, jobject galileoIonosphericModelObj, + GalileoIonosphericModel& ionosphericModel) { + if (galileoIonosphericModelObj == nullptr) return; + jdouble ai0 = + env->CallDoubleMethod(galileoIonosphericModelObj, method_galileoIonosphericModelGetAi0); + ionosphericModel.ai0 = ai0; + jdouble ai1 = + env->CallDoubleMethod(galileoIonosphericModelObj, method_galileoIonosphericModelGetAi1); + ionosphericModel.ai1 = ai1; + jdouble ai2 = + env->CallDoubleMethod(galileoIonosphericModelObj, method_galileoIonosphericModelGetAi2); + ionosphericModel.ai2 = ai2; +} + +void GnssAssistanceUtil::setGalileoSatelliteEphemeris( + JNIEnv* env, jobject galileoSatelliteEphemerisListObj, + std::vector<GalileoSatelliteEphemeris>& galileoSatelliteEphemerisList) { + if (galileoSatelliteEphemerisListObj == nullptr) return; + auto len = env->CallIntMethod(galileoSatelliteEphemerisListObj, method_listSize); + for (uint16_t i = 0; i < len; ++i) { + jobject galileoSatelliteEphemerisObj = + env->CallObjectMethod(galileoSatelliteEphemerisListObj, method_listGet, i); + GalileoSatelliteEphemeris galileoSatelliteEphemeris; + GalileoSvHealth galileoSvHealth; + // Set the svid of the satellite. + jint svid = env->CallLongMethod(galileoSatelliteEphemerisObj, + method_galileoSatelliteEphemerisGetSvid); + galileoSatelliteEphemeris.svid = svid; + + // Set the satellite clock models. + jobject galileoSatelliteClockModelListObj = + env->CallObjectMethod(galileoSatelliteEphemerisObj, + method_galileoSatelliteEphemerisGetSatelliteClockModels); + auto size = env->CallIntMethod(galileoSatelliteClockModelListObj, method_listSize); + for (uint16_t j = 0; j < size; ++j) { + jobject galileoSatelliteClockModelObj = + env->CallObjectMethod(galileoSatelliteClockModelListObj, method_listGet, j); + if (galileoSatelliteClockModelObj == nullptr) continue; + GalileoSatelliteClockModel galileoSatelliteClockModel; + jdouble af0 = env->CallDoubleMethod(galileoSatelliteClockModelObj, + method_galileoSatelliteClockModelGetAf0); + galileoSatelliteClockModel.af0 = af0; + jdouble af1 = env->CallDoubleMethod(galileoSatelliteClockModelObj, + method_galileoSatelliteClockModelGetAf1); + galileoSatelliteClockModel.af1 = af1; + jdouble af2 = env->CallDoubleMethod(galileoSatelliteClockModelObj, + method_galileoSatelliteClockModelGetAf2); + galileoSatelliteClockModel.af2 = af2; + jdouble bgdSeconds = + env->CallDoubleMethod(galileoSatelliteClockModelObj, + method_galileoSatelliteClockModelGetBgdSeconds); + galileoSatelliteClockModel.bgdSeconds = bgdSeconds; + jint satelliteClockType = + env->CallIntMethod(galileoSatelliteClockModelObj, + method_galileoSatelliteClockModelGetSatelliteClockType); + galileoSatelliteClockModel.satelliteClockType = + static_cast<GalileoSatelliteClockModel::SatelliteClockType>(satelliteClockType); + jdouble sisaMeters = + env->CallDoubleMethod(galileoSatelliteClockModelObj, + method_galileoSatelliteClockModelGetSisaMeters); + galileoSatelliteClockModel.sisaMeters = sisaMeters; + jdouble timeOfClockSeconds = + env->CallDoubleMethod(galileoSatelliteClockModelObj, + method_galileoSatelliteClockModelGetTimeOfClockSeconds); + galileoSatelliteClockModel.timeOfClockSeconds = timeOfClockSeconds; + galileoSatelliteEphemeris.satelliteClockModel.push_back(galileoSatelliteClockModel); + env->DeleteLocalRef(galileoSatelliteClockModelObj); + } + env->DeleteLocalRef(galileoSatelliteClockModelListObj); + + // Set the satelliteOrbitModel of the satellite. + jobject satelliteOrbitModelObj = + env->CallObjectMethod(galileoSatelliteEphemerisObj, + method_galileoSatelliteEphemerisGetSatelliteOrbitModel); + GnssAssistanceUtil::setKeplerianOrbitModel(env, satelliteOrbitModelObj, + galileoSatelliteEphemeris.satelliteOrbitModel); + env->DeleteLocalRef(satelliteOrbitModelObj); + + // Set the satellite health of the satellite clock model. + jobject galileoSvHealthObj = + env->CallObjectMethod(galileoSatelliteEphemerisObj, + method_galileoSatelliteEphemerisGetSatelliteHealth); + jint dataValidityStatusE1b = + env->CallIntMethod(galileoSvHealthObj, + method_galileoSvHealthGetDataValidityStatusE1b); + galileoSvHealth.dataValidityStatusE1b = + static_cast<GalileoSvHealth::GalileoHealthDataVaidityType>(dataValidityStatusE1b); + jint dataValidityStatusE5a = + env->CallIntMethod(galileoSvHealthObj, + method_galileoSvHealthGetDataValidityStatusE5a); + galileoSvHealth.dataValidityStatusE5a = + static_cast<GalileoSvHealth::GalileoHealthDataVaidityType>(dataValidityStatusE5a); + jint dataValidityStatusE5b = + env->CallIntMethod(galileoSvHealthObj, + method_galileoSvHealthGetDataValidityStatusE5b); + galileoSvHealth.dataValidityStatusE5b = + static_cast<GalileoSvHealth::GalileoHealthDataVaidityType>(dataValidityStatusE5b); + jint signalHealthStatusE1b = + env->CallIntMethod(galileoSvHealthObj, + method_galileoSvHealthGetSignalHealthStatusE1b); + galileoSvHealth.signalHealthStatusE1b = + static_cast<GalileoSvHealth::GalileoHealthStatusType>(signalHealthStatusE1b); + jint signalHealthStatusE5a = + env->CallIntMethod(galileoSvHealthObj, + method_galileoSvHealthGetSignalHealthStatusE5a); + galileoSvHealth.signalHealthStatusE5a = + static_cast<GalileoSvHealth::GalileoHealthStatusType>(signalHealthStatusE5a); + jint signalHealthStatusE5b = + env->CallIntMethod(galileoSvHealthObj, + method_galileoSvHealthGetSignalHealthStatusE5b); + galileoSvHealth.signalHealthStatusE5b = + static_cast<GalileoSvHealth::GalileoHealthStatusType>(signalHealthStatusE5b); + galileoSatelliteEphemeris.svHealth = galileoSvHealth; + env->DeleteLocalRef(galileoSvHealthObj); + + // Set the satelliteEphemerisTime of the satellite. + jobject satelliteEphemerisTimeObj = + env->CallObjectMethod(galileoSatelliteEphemerisObj, + method_galileoSatelliteEphemerisGetSatelliteEphemerisTime); + GnssAssistanceUtil::setSatelliteEphemerisTime(env, satelliteEphemerisTimeObj, + galileoSatelliteEphemeris + .satelliteEphemerisTime); + env->DeleteLocalRef(satelliteEphemerisTimeObj); + + galileoSatelliteEphemerisList.push_back(galileoSatelliteEphemeris); + env->DeleteLocalRef(galileoSatelliteEphemerisObj); + } +} + +void GnssAssistanceUtil::setBeidouAssistance(JNIEnv* env, jobject beidouAssistanceObj, + BeidouAssistance& beidouAssistance) { + jobject beidouAlmanacObj = + env->CallObjectMethod(beidouAssistanceObj, method_beidouAssistanceGetAlmanac); + jobject ionosphericModelObj = + env->CallObjectMethod(beidouAssistanceObj, method_beidouAssistanceGetIonosphericModel); + jobject utcModelObj = + env->CallObjectMethod(beidouAssistanceObj, method_beidouAssistanceGetUtcModel); + jobject leapSecondsModelObj = + env->CallObjectMethod(beidouAssistanceObj, method_beidouAssistanceGetLeapSecondsModel); + jobject timeModelsObj = + env->CallObjectMethod(beidouAssistanceObj, method_beidouAssistanceGetTimeModels); + jobject satelliteEphemerisObj = + env->CallObjectMethod(beidouAssistanceObj, + method_beidouAssistanceGetSatelliteEphemeris); + jobject realTimeIntegrityModelsObj = + env->CallObjectMethod(beidouAssistanceObj, + method_beidouAssistanceGetRealTimeIntegrityModels); + jobject satelliteCorrectionsObj = + env->CallObjectMethod(beidouAssistanceObj, + method_beidouAssistanceGetSatelliteCorrections); + setGnssAlmanac(env, beidouAlmanacObj, beidouAssistance.almanac); + setKlobucharIonosphericModel(env, ionosphericModelObj, beidouAssistance.ionosphericModel); + setUtcModel(env, utcModelObj, beidouAssistance.utcModel); + setLeapSecondsModel(env, leapSecondsModelObj, beidouAssistance.leapSecondsModel); + setTimeModels(env, timeModelsObj, beidouAssistance.timeModels); + setBeidouSatelliteEphemeris(env, satelliteEphemerisObj, beidouAssistance.satelliteEphemeris); + setRealTimeIntegrityModels(env, realTimeIntegrityModelsObj, + beidouAssistance.realTimeIntegrityModels); + setSatelliteCorrections(env, satelliteCorrectionsObj, beidouAssistance.satelliteCorrections); + env->DeleteLocalRef(beidouAlmanacObj); + env->DeleteLocalRef(ionosphericModelObj); + env->DeleteLocalRef(utcModelObj); + env->DeleteLocalRef(leapSecondsModelObj); + env->DeleteLocalRef(timeModelsObj); + env->DeleteLocalRef(satelliteEphemerisObj); + env->DeleteLocalRef(realTimeIntegrityModelsObj); + env->DeleteLocalRef(satelliteCorrectionsObj); +} + +void GnssAssistanceUtil::setBeidouSatelliteEphemeris( + JNIEnv* env, jobject beidouSatelliteEphemerisListObj, + std::vector<BeidouSatelliteEphemeris>& beidouSatelliteEphemerisList) { + if (beidouSatelliteEphemerisListObj == nullptr) return; + auto len = env->CallIntMethod(beidouSatelliteEphemerisListObj, method_listSize); + for (uint16_t i = 0; i < len; ++i) { + jobject beidouSatelliteEphemerisObj = + env->CallObjectMethod(beidouSatelliteEphemerisListObj, method_listGet, i); + if (beidouSatelliteEphemerisObj == nullptr) continue; + BeidouSatelliteEphemeris beidouSatelliteEphemeris; + + // Set the svid of the satellite. + jint svid = env->CallIntMethod(beidouSatelliteEphemerisObj, + method_beidouSatelliteEphemerisGetSvid); + beidouSatelliteEphemeris.svid = static_cast<int32_t>(svid); + + // Set the satelliteClockModel of the satellite. + jobject satelliteClockModelObj = + env->CallObjectMethod(beidouSatelliteEphemerisObj, + method_beidouSatelliteEphemerisGetSatelliteClockModel); + jdouble af0 = env->CallDoubleMethod(satelliteClockModelObj, + method_beidouSatelliteClockModelGetAf0); + jdouble af1 = env->CallDoubleMethod(satelliteClockModelObj, + method_beidouSatelliteClockModelGetAf1); + jdouble af2 = env->CallDoubleMethod(satelliteClockModelObj, + method_beidouSatelliteClockModelGetAf2); + jdouble tgd1 = env->CallDoubleMethod(satelliteClockModelObj, + method_beidouSatelliteClockModelGetTgd1); + jdouble tgd2 = env->CallDoubleMethod(satelliteClockModelObj, + method_beidouSatelliteClockModelGetTgd2); + jdouble aodc = env->CallDoubleMethod(satelliteClockModelObj, + method_beidouSatelliteClockModelGetAodc); + jlong timeOfClockSeconds = + env->CallLongMethod(satelliteClockModelObj, + method_beidouSatelliteClockModelGetTimeOfClockSeconds); + beidouSatelliteEphemeris.satelliteClockModel.af0 = af0; + beidouSatelliteEphemeris.satelliteClockModel.af1 = af1; + beidouSatelliteEphemeris.satelliteClockModel.af2 = af2; + beidouSatelliteEphemeris.satelliteClockModel.tgd1 = tgd1; + beidouSatelliteEphemeris.satelliteClockModel.tgd2 = tgd2; + beidouSatelliteEphemeris.satelliteClockModel.aodc = aodc; + beidouSatelliteEphemeris.satelliteClockModel.timeOfClockSeconds = timeOfClockSeconds; + env->DeleteLocalRef(satelliteClockModelObj); + + // Set the satelliteOrbitModel of the satellite. + jobject satelliteOrbitModelObj = + env->CallObjectMethod(beidouSatelliteEphemerisObj, + method_beidouSatelliteEphemerisGetSatelliteOrbitModel); + GnssAssistanceUtil::setKeplerianOrbitModel(env, satelliteOrbitModelObj, + beidouSatelliteEphemeris.satelliteOrbitModel); + env->DeleteLocalRef(satelliteOrbitModelObj); + + // Set the satelliteHealth of the satellite. + jobject satelliteHealthObj = + env->CallObjectMethod(beidouSatelliteEphemerisObj, + method_beidouSatelliteEphemerisGetSatelliteHealth); + jint satH1 = env->CallIntMethod(satelliteHealthObj, method_beidouSatelliteHealthGetSatH1); + jint svAccur = + env->CallIntMethod(satelliteHealthObj, method_beidouSatelliteHealthGetSvAccur); + beidouSatelliteEphemeris.satelliteHealth.satH1 = static_cast<int32_t>(satH1); + beidouSatelliteEphemeris.satelliteHealth.svAccur = static_cast<int32_t>(svAccur); + env->DeleteLocalRef(satelliteHealthObj); + + // Set the satelliteEphemerisTime of the satellite. + jobject satelliteEphemerisTimeObj = + env->CallObjectMethod(beidouSatelliteEphemerisObj, + method_beidouSatelliteEphemerisGetSatelliteEphemerisTime); + jint iode = env->CallIntMethod(satelliteEphemerisTimeObj, + method_beidouSatelliteEphemerisTimeGetIode); + jint beidouWeekNumber = + env->CallIntMethod(satelliteEphemerisTimeObj, + method_beidouSatelliteEphemerisTimeGetBeidouWeekNumber); + jint toeSeconds = env->CallDoubleMethod(satelliteEphemerisTimeObj, + method_beidouSatelliteEphemerisTimeGetToeSeconds); + beidouSatelliteEphemeris.satelliteEphemerisTime.aode = static_cast<int32_t>(iode); + beidouSatelliteEphemeris.satelliteEphemerisTime.weekNumber = + static_cast<int32_t>(beidouWeekNumber); + beidouSatelliteEphemeris.satelliteEphemerisTime.toeSeconds = + static_cast<int32_t>(toeSeconds); + env->DeleteLocalRef(satelliteEphemerisTimeObj); + + beidouSatelliteEphemerisList.push_back(beidouSatelliteEphemeris); + env->DeleteLocalRef(beidouSatelliteEphemerisObj); + } +} + +void GnssAssistanceUtil::setGpsAssistance(JNIEnv* env, jobject gpsAssistanceObj, + GpsAssistance& gpsAssistance) { + jobject gnssAlmanacObj = + env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetAlmanac); + jobject ionosphericModelObj = + env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetIonosphericModel); + jobject utcModelObj = env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetUtcModel); + jobject leapSecondsModelObj = + env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetLeapSecondsModel); + jobject timeModelsObj = + env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetTimeModels); + jobject satelliteEphemerisObj = + env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetSatelliteEphemeris); + jobject realTimeIntegrityModelsObj = + env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetRealTimeIntegrityModels); + jobject satelliteCorrectionsObj = + env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetSatelliteCorrections); + + setGnssAlmanac(env, gnssAlmanacObj, gpsAssistance.almanac); + setKlobucharIonosphericModel(env, ionosphericModelObj, gpsAssistance.ionosphericModel); + setUtcModel(env, utcModelObj, gpsAssistance.utcModel); + setLeapSecondsModel(env, leapSecondsModelObj, gpsAssistance.leapSecondsModel); + setTimeModels(env, timeModelsObj, gpsAssistance.timeModels); + setGpsOrQzssSatelliteEphemeris<GpsSatelliteEphemeris>(env, satelliteEphemerisObj, + gpsAssistance.satelliteEphemeris); + setRealTimeIntegrityModels(env, realTimeIntegrityModelsObj, + gpsAssistance.realTimeIntegrityModels); + setSatelliteCorrections(env, satelliteCorrectionsObj, gpsAssistance.satelliteCorrections); + env->DeleteLocalRef(gnssAlmanacObj); + env->DeleteLocalRef(ionosphericModelObj); + env->DeleteLocalRef(utcModelObj); + env->DeleteLocalRef(leapSecondsModelObj); + env->DeleteLocalRef(timeModelsObj); + env->DeleteLocalRef(satelliteEphemerisObj); + env->DeleteLocalRef(realTimeIntegrityModelsObj); + env->DeleteLocalRef(satelliteCorrectionsObj); +} + +/** Set the GPS/QZSS satellite ephemeris list. */ +template <class T> +void GnssAssistanceUtil::setGpsOrQzssSatelliteEphemeris(JNIEnv* env, + jobject satelliteEphemerisListObj, + std::vector<T>& satelliteEphemerisList) { + if (satelliteEphemerisListObj == nullptr) return; + auto len = env->CallIntMethod(satelliteEphemerisListObj, method_listSize); + for (uint16_t i = 0; i < len; ++i) { + jobject satelliteEphemerisObj = + env->CallObjectMethod(satelliteEphemerisListObj, method_listGet, i); + if (satelliteEphemerisObj == nullptr) continue; + T satelliteEphemeris; + // Set the svid of the satellite. + jint svid = env->CallIntMethod(satelliteEphemerisObj, method_gpsSatelliteEphemerisGetSvid); + satelliteEphemeris.svid = static_cast<int32_t>(svid); + + // Set the gpsL2Params of the satellite. + jobject gpsL2ParamsObj = env->CallObjectMethod(satelliteEphemerisObj, + method_gpsSatelliteEphemerisGetGpsL2Params); + jint l2Code = env->CallIntMethod(gpsL2ParamsObj, method_gpsL2ParamsGetL2Code); + jint l2Flag = env->CallIntMethod(gpsL2ParamsObj, method_gpsL2ParamsGetL2Flag); + satelliteEphemeris.gpsL2Params.l2Code = static_cast<int32_t>(l2Code); + satelliteEphemeris.gpsL2Params.l2Flag = static_cast<int32_t>(l2Flag); + env->DeleteLocalRef(gpsL2ParamsObj); + + // Set the satelliteClockModel of the satellite. + jobject satelliteClockModelObj = + env->CallObjectMethod(satelliteEphemerisObj, + method_gpsSatelliteEphemerisGetSatelliteClockModel); + jdouble af0 = + env->CallDoubleMethod(satelliteClockModelObj, method_gpsSatelliteClockModelGetAf0); + jdouble af1 = + env->CallDoubleMethod(satelliteClockModelObj, method_gpsSatelliteClockModelGetAf1); + jdouble af2 = + env->CallDoubleMethod(satelliteClockModelObj, method_gpsSatelliteClockModelGetAf2); + jdouble tgd = + env->CallDoubleMethod(satelliteClockModelObj, method_gpsSatelliteClockModelGetTgd); + jint iodc = + env->CallDoubleMethod(satelliteClockModelObj, method_gpsSatelliteClockModelGetIodc); + jlong timeOfClockSeconds = + env->CallLongMethod(satelliteClockModelObj, + method_gpsSatelliteClockModelGetTimeOfClockSeconds); + satelliteEphemeris.satelliteClockModel.af0 = af0; + satelliteEphemeris.satelliteClockModel.af1 = af1; + satelliteEphemeris.satelliteClockModel.af2 = af2; + satelliteEphemeris.satelliteClockModel.tgd = tgd; + satelliteEphemeris.satelliteClockModel.iodc = static_cast<int32_t>(iodc); + satelliteEphemeris.satelliteClockModel.timeOfClockSeconds = timeOfClockSeconds; + env->DeleteLocalRef(satelliteClockModelObj); + + // Set the satelliteOrbitModel of the satellite. + jobject satelliteOrbitModelObj = + env->CallObjectMethod(satelliteEphemerisObj, + method_gpsSatelliteEphemerisGetSatelliteOrbitModel); + GnssAssistanceUtil::setKeplerianOrbitModel(env, satelliteOrbitModelObj, + satelliteEphemeris.satelliteOrbitModel); + env->DeleteLocalRef(satelliteOrbitModelObj); + + // Set the satelliteHealth of the satellite. + jobject satelliteHealthObj = + env->CallObjectMethod(satelliteEphemerisObj, + method_gpsSatelliteEphemerisGetSatelliteHealth); + jint svHealth = + env->CallIntMethod(satelliteHealthObj, method_gpsSatelliteHealthGetSvHealth); + jdouble svAccur = + env->CallDoubleMethod(satelliteHealthObj, method_gpsSatelliteHealthGetSvAccur); + jdouble fitInt = env->CallIntMethod(satelliteHealthObj, method_gpsSatelliteHealthGetFitInt); + satelliteEphemeris.satelliteHealth.svHealth = static_cast<int32_t>(svHealth); + satelliteEphemeris.satelliteHealth.svAccur = svAccur; + satelliteEphemeris.satelliteHealth.fitInt = fitInt; + env->DeleteLocalRef(satelliteHealthObj); + + // Set the satelliteEphemerisTime of the satellite. + jobject satelliteEphemerisTimeObj = + env->CallObjectMethod(satelliteEphemerisObj, + method_gpsSatelliteEphemerisGetSatelliteEphemerisTime); + GnssAssistanceUtil::setSatelliteEphemerisTime(env, satelliteEphemerisTimeObj, + satelliteEphemeris.satelliteEphemerisTime); + env->DeleteLocalRef(satelliteEphemerisTimeObj); + + satelliteEphemerisList.push_back(satelliteEphemeris); + env->DeleteLocalRef(satelliteEphemerisObj); + } +} + +void GnssAssistanceUtil::setSatelliteCorrections( + JNIEnv* env, jobject satelliteCorrectionsObj, + std::vector<GnssSatelliteCorrections>& gnssSatelliteCorrectionsList) { + if (satelliteCorrectionsObj == nullptr) return; + auto len = env->CallIntMethod(satelliteCorrectionsObj, method_listSize); + for (uint16_t i = 0; i < len; ++i) { + GnssSatelliteCorrections gnssSatelliteCorrections; + jobject satelliteCorrectionObj = + env->CallObjectMethod(satelliteCorrectionsObj, method_listGet, i); + if (satelliteCorrectionObj == nullptr) continue; + jint svid = env->CallIntMethod(satelliteCorrectionObj, method_satelliteCorrectionGetSvid); + gnssSatelliteCorrections.svid = svid; + jobject ionosphericCorrectionsObj = + env->CallObjectMethod(satelliteCorrectionObj, + method_satelliteCorrectionGetIonosphericCorrections); + env->DeleteLocalRef(satelliteCorrectionObj); + auto size = env->CallIntMethod(ionosphericCorrectionsObj, method_listSize); + for (uint16_t j = 0; j < size; ++j) { + jobject ionosphericCorrectionObj = + env->CallObjectMethod(ionosphericCorrectionsObj, method_listGet, j); + if (ionosphericCorrectionObj == nullptr) continue; + IonosphericCorrection ionosphericCorrection; + jlong carrierFrequencyHz = + env->CallLongMethod(ionosphericCorrectionObj, + method_ionosphericCorrectionGetCarrierFrequencyHz); + ionosphericCorrection.carrierFrequencyHz = carrierFrequencyHz; + + jobject gnssCorrectionComponentObj = + env->CallObjectMethod(ionosphericCorrectionObj, + method_ionosphericCorrectionGetIonosphericCorrection); + env->DeleteLocalRef(ionosphericCorrectionObj); + + jstring sourceKey = static_cast<jstring>( + env->CallObjectMethod(gnssCorrectionComponentObj, + method_gnssCorrectionComponentGetSourceKey)); + ScopedJniString jniSourceKey{env, sourceKey}; + ionosphericCorrection.ionosphericCorrectionComponent.sourceKey = + android::String16(jniSourceKey.c_str()); + + jobject pseudorangeCorrectionObj = + env->CallObjectMethod(gnssCorrectionComponentObj, + method_gnssCorrectionComponentGetPseudorangeCorrection); + jdouble correctionMeters = + env->CallDoubleMethod(pseudorangeCorrectionObj, + method_pseudorangeCorrectionGetCorrectionMeters); + jdouble correctionUncertaintyMeters = env->CallDoubleMethod( + pseudorangeCorrectionObj, + method_pseudorangeCorrectionGetCorrectionUncertaintyMeters); + jdouble correctionRateMetersPerSecond = env->CallDoubleMethod( + pseudorangeCorrectionObj, + method_pseudorangeCorrectionGetCorrectionRateMetersPerSecond); + ionosphericCorrection.ionosphericCorrectionComponent.pseudorangeCorrection + .correctionMeters = correctionMeters; + ionosphericCorrection.ionosphericCorrectionComponent.pseudorangeCorrection + .correctionUncertaintyMeters = correctionUncertaintyMeters; + ionosphericCorrection.ionosphericCorrectionComponent.pseudorangeCorrection + .correctionRateMetersPerSecond = correctionRateMetersPerSecond; + env->DeleteLocalRef(pseudorangeCorrectionObj); + + jobject gnssIntervalObj = + env->CallObjectMethod(gnssCorrectionComponentObj, + method_gnssCorrectionComponentGetValidityInterval); + jdouble startMillisSinceGpsEpoch = + env->CallDoubleMethod(gnssIntervalObj, + method_gnssIntervalGetStartMillisSinceGpsEpoch); + jdouble endMillisSinceGpsEpoch = + env->CallDoubleMethod(gnssIntervalObj, + method_gnssIntervalGetEndMillisSinceGpsEpoch); + ionosphericCorrection.ionosphericCorrectionComponent.validityInterval + .startMillisSinceGpsEpoch = startMillisSinceGpsEpoch; + ionosphericCorrection.ionosphericCorrectionComponent.validityInterval + .endMillisSinceGpsEpoch = endMillisSinceGpsEpoch; + env->DeleteLocalRef(gnssIntervalObj); + + env->DeleteLocalRef(gnssCorrectionComponentObj); + gnssSatelliteCorrections.ionosphericCorrections.push_back(ionosphericCorrection); + } + gnssSatelliteCorrectionsList.push_back(gnssSatelliteCorrections); + env->DeleteLocalRef(ionosphericCorrectionsObj); + } +} + +void GnssAssistanceUtil::setRealTimeIntegrityModels( + JNIEnv* env, jobject realTimeIntegrityModelsObj, + std::vector<RealTimeIntegrityModel>& realTimeIntegrityModels) { + if (realTimeIntegrityModelsObj == nullptr) return; + auto len = env->CallIntMethod(realTimeIntegrityModelsObj, method_listSize); + for (uint16_t i = 0; i < len; ++i) { + jobject realTimeIntegrityModelObj = + env->CallObjectMethod(realTimeIntegrityModelsObj, method_listGet, i); + if (realTimeIntegrityModelObj == nullptr) continue; + RealTimeIntegrityModel realTimeIntegrityModel; + jint badSvid = env->CallIntMethod(realTimeIntegrityModelObj, + method_realTimeIntegrityModelGetBadSvid); + jobject badSignalTypesObj = + env->CallObjectMethod(realTimeIntegrityModelObj, + method_realTimeIntegrityModelGetBadSignalTypes); + auto badSignalTypesSize = env->CallIntMethod(badSignalTypesObj, method_listSize); + for (uint16_t j = 0; j < badSignalTypesSize; ++j) { + GnssSignalType badSignalType; + jobject badSignalTypeObj = env->CallObjectMethod(badSignalTypesObj, method_listGet, j); + if (badSignalTypeObj != nullptr) { + setGnssSignalType(env, badSignalTypeObj, badSignalType); + realTimeIntegrityModel.badSignalTypes.push_back(badSignalType); + env->DeleteLocalRef(badSignalTypeObj); + } + } + + jlong startDateSeconds = + env->CallLongMethod(realTimeIntegrityModelObj, + method_realTimeIntegrityModelGetStartDateSeconds); + jlong endDateSeconds = env->CallLongMethod(realTimeIntegrityModelObj, + method_realTimeIntegrityModelGetEndDateSeconds); + jlong publishDateSeconds = + env->CallLongMethod(realTimeIntegrityModelObj, + method_realTimeIntegrityModelGetPublishDateSeconds); + jstring advisoryNumber = static_cast<jstring>( + env->CallObjectMethod(realTimeIntegrityModelObj, + method_realTimeIntegrityModelGetAdvisoryNumber)); + ScopedJniString jniAdvisoryNumber{env, advisoryNumber}; + jstring advisoryType = static_cast<jstring>( + env->CallObjectMethod(realTimeIntegrityModelObj, + method_realTimeIntegrityModelGetAdvisoryType)); + ScopedJniString jniAdvisoryType{env, advisoryType}; + + realTimeIntegrityModel.badSvid = badSvid; + realTimeIntegrityModel.startDateSeconds = startDateSeconds; + realTimeIntegrityModel.endDateSeconds = endDateSeconds; + realTimeIntegrityModel.publishDateSeconds = publishDateSeconds; + realTimeIntegrityModel.advisoryNumber = android::String16(jniAdvisoryNumber.c_str()); + realTimeIntegrityModel.advisoryType = android::String16(jniAdvisoryType.c_str()); + realTimeIntegrityModels.push_back(realTimeIntegrityModel); + env->DeleteLocalRef(badSignalTypesObj); + env->DeleteLocalRef(realTimeIntegrityModelObj); + } +} + +void GnssAssistanceUtil::setGnssSignalType(JNIEnv* env, jobject gnssSignalTypeObj, + GnssSignalType& gnssSignalType) { + if (gnssSignalTypeObj == nullptr) { + ALOGE("gnssSignalTypeObj is null"); + return; + } + jint constellationType = + env->CallIntMethod(gnssSignalTypeObj, method_gnssSignalTypeGetConstellationType); + jdouble carrierFrequencyHz = + env->CallIntMethod(gnssSignalTypeObj, method_gnssSignalTypeGetCarrierFrequencyHz); + jstring codeType = static_cast<jstring>( + env->CallObjectMethod(gnssSignalTypeObj, method_gnssSignalTypeGetCodeType)); + ScopedJniString jniCodeType{env, codeType}; + + gnssSignalType.constellation = static_cast<GnssConstellationType>(constellationType); + gnssSignalType.carrierFrequencyHz = static_cast<int32_t>(carrierFrequencyHz); + gnssSignalType.codeType = std::string(jniCodeType.c_str()); +} + +void GnssAssistanceUtil::setTimeModels(JNIEnv* env, jobject timeModelsObj, + std::vector<TimeModel>& timeModels) { + if (timeModelsObj == nullptr) return; + auto len = env->CallIntMethod(timeModelsObj, method_listSize); + for (uint16_t i = 0; i < len; ++i) { + jobject timeModelObj = env->CallObjectMethod(timeModelsObj, method_listGet, i); + TimeModel timeModel; + jint toGnss = env->CallIntMethod(timeModelObj, method_timeModelsGetToGnss); + jlong timeOfWeek = env->CallLongMethod(timeModelObj, method_timeModelsGetTimeOfWeek); + jint weekNumber = env->CallIntMethod(timeModelObj, method_timeModelsGetWeekNumber); + jdouble a0 = env->CallDoubleMethod(timeModelObj, method_timeModelsGetA0); + jdouble a1 = env->CallDoubleMethod(timeModelObj, method_timeModelsGetA1); + timeModel.toGnss = static_cast<GnssConstellationType>(toGnss); + timeModel.timeOfWeek = timeOfWeek; + timeModel.weekNumber = static_cast<int32_t>(weekNumber); + timeModel.a0 = a0; + timeModel.a1 = a1; + timeModels.push_back(timeModel); + env->DeleteLocalRef(timeModelObj); + } +} + +void GnssAssistanceUtil::setLeapSecondsModel(JNIEnv* env, jobject leapSecondsModelObj, + LeapSecondsModel& leapSecondsModel) { + if (leapSecondsModelObj == nullptr) { + leapSecondsModel.leapSeconds = -1; + return; + } + jint dayNumberLeapSecondsFuture = + env->CallIntMethod(leapSecondsModelObj, + method_leapSecondsModelGetDayNumberLeapSecondsFuture); + jint leapSeconds = + env->CallIntMethod(leapSecondsModelObj, method_leapSecondsModelGetLeapSeconds); + jint leapSecondsFuture = + env->CallIntMethod(leapSecondsModelObj, method_leapSecondsModelGetLeapSecondsFuture); + jint weekNumberLeapSecondsFuture = + env->CallIntMethod(leapSecondsModelObj, + method_leapSecondsModelGetWeekNumberLeapSecondsFuture); + leapSecondsModel.dayNumberLeapSecondsFuture = static_cast<int32_t>(dayNumberLeapSecondsFuture); + leapSecondsModel.leapSeconds = static_cast<int32_t>(leapSeconds); + leapSecondsModel.leapSecondsFuture = static_cast<int32_t>(leapSecondsFuture); + leapSecondsModel.weekNumberLeapSecondsFuture = + static_cast<int32_t>(weekNumberLeapSecondsFuture); +} + +void GnssAssistanceUtil::setSatelliteEphemerisTime(JNIEnv* env, jobject satelliteEphemerisTimeObj, + SatelliteEphemerisTime& satelliteEphemerisTime) { + if (satelliteEphemerisTimeObj == nullptr) return; + jdouble iode = + env->CallDoubleMethod(satelliteEphemerisTimeObj, method_satelliteEphemerisTimeGetIode); + jdouble toeSeconds = env->CallDoubleMethod(satelliteEphemerisTimeObj, + method_satelliteEphemerisTimeGetToeSeconds); + jint weekNumber = env->CallIntMethod(satelliteEphemerisTimeObj, + method_satelliteEphemerisTimeGetWeekNumber); + satelliteEphemerisTime.iode = iode; + satelliteEphemerisTime.toeSeconds = toeSeconds; + satelliteEphemerisTime.weekNumber = weekNumber; +} + +void GnssAssistanceUtil::setKeplerianOrbitModel(JNIEnv* env, jobject keplerianOrbitModelObj, + KeplerianOrbitModel& keplerianOrbitModel) { + if (keplerianOrbitModelObj == nullptr) return; + jdouble rootA = + env->CallDoubleMethod(keplerianOrbitModelObj, method_keplerianOrbitModelGetRootA); + jdouble eccentricity = env->CallDoubleMethod(keplerianOrbitModelObj, + method_keplerianOrbitModelGetEccentricity); + jdouble m0 = env->CallDoubleMethod(keplerianOrbitModelObj, method_keplerianOrbitModelGetM0); + jdouble omega = + env->CallDoubleMethod(keplerianOrbitModelObj, method_keplerianOrbitModelGetOmega); + jdouble omegaDot = + env->CallDoubleMethod(keplerianOrbitModelObj, method_keplerianOrbitModelGetOmegaDot); + jdouble deltaN = + env->CallDoubleMethod(keplerianOrbitModelObj, method_keplerianOrbitModelGetDeltaN); + jdouble iDot = env->CallDoubleMethod(keplerianOrbitModelObj, method_keplerianOrbitModelGetIDot); + jobject secondOrderHarmonicPerturbationObj = + env->CallObjectMethod(keplerianOrbitModelObj, + method_keplerianOrbitModelGetSecondOrderHarmonicPerturbation); + jdouble cic = env->CallDoubleMethod(secondOrderHarmonicPerturbationObj, + method_secondOrderHarmonicPerturbationGetCic); + jdouble cis = env->CallDoubleMethod(secondOrderHarmonicPerturbationObj, + method_secondOrderHarmonicPerturbationGetCis); + jdouble crs = env->CallDoubleMethod(secondOrderHarmonicPerturbationObj, + method_secondOrderHarmonicPerturbationGetCrs); + jdouble crc = env->CallDoubleMethod(secondOrderHarmonicPerturbationObj, + method_secondOrderHarmonicPerturbationGetCrc); + jdouble cuc = env->CallDoubleMethod(secondOrderHarmonicPerturbationObj, + method_secondOrderHarmonicPerturbationGetCuc); + jdouble cus = env->CallDoubleMethod(secondOrderHarmonicPerturbationObj, + method_secondOrderHarmonicPerturbationGetCus); + keplerianOrbitModel.rootA = rootA; + keplerianOrbitModel.eccentricity = eccentricity; + keplerianOrbitModel.m0 = m0; + keplerianOrbitModel.omega = omega; + keplerianOrbitModel.omegaDot = omegaDot; + keplerianOrbitModel.deltaN = deltaN; + keplerianOrbitModel.iDot = iDot; + keplerianOrbitModel.secondOrderHarmonicPerturbation.cic = cic; + keplerianOrbitModel.secondOrderHarmonicPerturbation.cis = cis; + keplerianOrbitModel.secondOrderHarmonicPerturbation.crs = crs; + keplerianOrbitModel.secondOrderHarmonicPerturbation.crc = crc; + keplerianOrbitModel.secondOrderHarmonicPerturbation.cuc = cuc; + keplerianOrbitModel.secondOrderHarmonicPerturbation.cus = cus; + env->DeleteLocalRef(secondOrderHarmonicPerturbationObj); +} + +void GnssAssistanceUtil::setKlobucharIonosphericModel( + JNIEnv* env, jobject klobucharIonosphericModelObj, + KlobucharIonosphericModel& klobucharIonosphericModel) { + if (klobucharIonosphericModelObj == nullptr) return; + jdouble alpha0 = env->CallDoubleMethod(klobucharIonosphericModelObj, + method_klobucharIonosphericModelGetAlpha0); + jdouble alpha1 = env->CallDoubleMethod(klobucharIonosphericModelObj, + method_klobucharIonosphericModelGetAlpha1); + jdouble alpha2 = env->CallDoubleMethod(klobucharIonosphericModelObj, + method_klobucharIonosphericModelGetAlpha2); + jdouble alpha3 = env->CallDoubleMethod(klobucharIonosphericModelObj, + method_klobucharIonosphericModelGetAlpha3); + jdouble beta0 = env->CallDoubleMethod(klobucharIonosphericModelObj, + method_klobucharIonosphericModelGetBeta0); + jdouble beta1 = env->CallDoubleMethod(klobucharIonosphericModelObj, + method_klobucharIonosphericModelGetBeta1); + jdouble beta2 = env->CallDoubleMethod(klobucharIonosphericModelObj, + method_klobucharIonosphericModelGetBeta2); + jdouble beta3 = env->CallDoubleMethod(klobucharIonosphericModelObj, + method_klobucharIonosphericModelGetBeta3); + klobucharIonosphericModel.alpha0 = alpha0; + klobucharIonosphericModel.alpha1 = alpha1; + klobucharIonosphericModel.alpha2 = alpha2; + klobucharIonosphericModel.alpha3 = alpha3; + klobucharIonosphericModel.beta0 = beta0; + klobucharIonosphericModel.beta1 = beta1; + klobucharIonosphericModel.beta2 = beta2; + klobucharIonosphericModel.beta3 = beta3; +} + +void GnssAssistanceUtil::setUtcModel(JNIEnv* env, jobject utcModelObj, UtcModel& utcModel) { + if (utcModelObj == nullptr) { + utcModel.weekNumber = -1; + return; + } + jdouble a0 = env->CallDoubleMethod(utcModelObj, method_utcModelGetA0); + jdouble a1 = env->CallDoubleMethod(utcModelObj, method_utcModelGetA1); + jlong timeOfWeek = env->CallLongMethod(utcModelObj, method_utcModelGetTimeOfWeek); + jint weekNumber = env->CallIntMethod(utcModelObj, method_utcModelGetWeekNumber); + utcModel.a0 = a0; + utcModel.a1 = a1; + utcModel.timeOfWeek = timeOfWeek; + utcModel.weekNumber = static_cast<int32_t>(weekNumber); +} + +void GnssAssistanceUtil::setGnssAlmanac(JNIEnv* env, jobject gnssAlmanacObj, + GnssAlmanac& gnssAlmanac) { + if (gnssAlmanacObj == nullptr) { + gnssAlmanac.weekNumber = -1; + return; + } + jlong issueDateMillis = + env->CallLongMethod(gnssAlmanacObj, method_gnssAlmanacGetIssueDateMillis); + jint ioda = env->CallIntMethod(gnssAlmanacObj, method_gnssAlmanacGetIoda); + jint weekNumber = env->CallIntMethod(gnssAlmanacObj, method_gnssAlmanacGetWeekNumber); + jlong toaSeconds = env->CallLongMethod(gnssAlmanacObj, method_gnssAlmanacGetToaSeconds); + jboolean isCompleteAlmanacProvided = + env->CallBooleanMethod(gnssAlmanacObj, method_gnssAlmanacIsCompleteAlmanacProvided); + gnssAlmanac.issueDateMs = issueDateMillis; + gnssAlmanac.ioda = ioda; + gnssAlmanac.weekNumber = weekNumber; + gnssAlmanac.toaSeconds = toaSeconds; + gnssAlmanac.isCompleteAlmanacProvided = isCompleteAlmanacProvided; + + jobject satelliteAlmanacsListObj = + env->CallObjectMethod(gnssAlmanacObj, method_gnssAlmanacGetSatelliteAlmanacs); + auto len = env->CallIntMethod(satelliteAlmanacsListObj, method_listSize); + std::vector<GnssSatelliteAlmanac> list(len); + for (uint16_t i = 0; i < len; ++i) { + jobject gnssSatelliteAlmanacObj = + env->CallObjectMethod(satelliteAlmanacsListObj, method_listGet, i); + if (gnssSatelliteAlmanacObj == nullptr) continue; + GnssSatelliteAlmanac gnssSatelliteAlmanac; + jint svid = env->CallIntMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetSvid); + jint svHealth = + env->CallIntMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetSvHealth); + jdouble af0 = env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetAf0); + jdouble af1 = env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetAf1); + jdouble eccentricity = env->CallDoubleMethod(gnssSatelliteAlmanacObj, + method_satelliteAlmanacGetEccentricity); + jdouble inclination = env->CallDoubleMethod(gnssSatelliteAlmanacObj, + method_satelliteAlmanacGetInclination); + jdouble m0 = env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetM0); + jdouble omega = + env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetOmega); + jdouble omega0 = + env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetOmega0); + jdouble omegaDot = + env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetOmegaDot); + jdouble rootA = + env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetRootA); + gnssSatelliteAlmanac.svid = static_cast<int32_t>(svid); + gnssSatelliteAlmanac.svHealth = static_cast<int32_t>(svHealth); + gnssSatelliteAlmanac.af0 = af0; + gnssSatelliteAlmanac.af1 = af1; + gnssSatelliteAlmanac.eccentricity = eccentricity; + gnssSatelliteAlmanac.inclination = inclination; + gnssSatelliteAlmanac.m0 = m0; + gnssSatelliteAlmanac.omega = omega; + gnssSatelliteAlmanac.omega0 = omega0; + gnssSatelliteAlmanac.omegaDot = omegaDot; + gnssSatelliteAlmanac.rootA = rootA; + list.at(i) = gnssSatelliteAlmanac; + env->DeleteLocalRef(gnssSatelliteAlmanacObj); + } + gnssAlmanac.satelliteAlmanacs = list; + env->DeleteLocalRef(satelliteAlmanacsListObj); +} + +void GnssAssistanceUtil::setAuxiliaryInformation(JNIEnv* env, jobject auxiliaryInformationObj, + AuxiliaryInformation& auxiliaryInformation) { + if (auxiliaryInformationObj == nullptr) { + auxiliaryInformation.svid = -1; + return; + } + jint svid = env->CallIntMethod(auxiliaryInformationObj, method_auxiliaryInformationGetSvid); + jobject availableSignalTypesObj = + env->CallObjectMethod(auxiliaryInformationObj, + method_auxiliaryInformationGetAvailableSignalTypes); + auto size = env->CallIntMethod(availableSignalTypesObj, method_listSize); + std::vector<GnssSignalType> availableSignalTypes(size); + for (uint16_t i = 0; i < size; ++i) { + jobject availableSignalTypeObj = + env->CallObjectMethod(availableSignalTypesObj, method_listGet, i); + GnssSignalType availableSignalType; + setGnssSignalType(env, availableSignalTypeObj, availableSignalType); + availableSignalTypes.at(i) = availableSignalType; + env->DeleteLocalRef(availableSignalTypeObj); + } + jint frequencyChannelNumber = + env->CallIntMethod(auxiliaryInformationObj, + method_auxiliaryInformationGetFrequencyChannelNumber); + jint satType = + env->CallIntMethod(auxiliaryInformationObj, method_auxiliaryInformationGetSatType); + auxiliaryInformation.svid = static_cast<int32_t>(svid); + auxiliaryInformation.availableSignalTypes = availableSignalTypes; + auxiliaryInformation.frequencyChannelNumber = static_cast<int32_t>(frequencyChannelNumber); + auxiliaryInformation.satType = static_cast<BeidouB1CSatelliteOrbitType>(satType); + env->DeleteLocalRef(availableSignalTypesObj); +} + +} // namespace android::gnss diff --git a/services/core/jni/gnss/GnssAssistance.h b/services/core/jni/gnss/GnssAssistance.h new file mode 100644 index 000000000000..ee97e19371f8 --- /dev/null +++ b/services/core/jni/gnss/GnssAssistance.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_SERVER_GNSSASSITANCE_H +#define _ANDROID_SERVER_GNSSASSITANCE_H + +#include <sys/stat.h> +#pragma once + +#ifndef LOG_TAG +#error LOG_TAG must be defined before including this file. +#endif + +#include <android/hardware/gnss/gnss_assistance/BnGnssAssistanceInterface.h> +#include <log/log.h> + +#include "GnssAssistanceCallback.h" +#include "jni.h" + +namespace android::gnss { + +using IGnssAssistanceInterface = android::hardware::gnss::gnss_assistance::IGnssAssistanceInterface; +using IGnssAssistanceCallback = android::hardware::gnss::gnss_assistance::IGnssAssistanceCallback; +using BeidouAssistance = android::hardware::gnss::gnss_assistance::GnssAssistance::BeidouAssistance; +using BeidouSatelliteEphemeris = android::hardware::gnss::gnss_assistance::BeidouSatelliteEphemeris; +using GalileoAssistance = + android::hardware::gnss::gnss_assistance::GnssAssistance::GalileoAssistance; +using GalileoSatelliteEphemeris = + android::hardware::gnss::gnss_assistance::GalileoSatelliteEphemeris; +using GalileoIonosphericModel = android::hardware::gnss::gnss_assistance::GalileoIonosphericModel; +using GlonassAssistance = + android::hardware::gnss::gnss_assistance::GnssAssistance::GlonassAssistance; +using GlonassAlmanac = android::hardware::gnss::gnss_assistance::GlonassAlmanac; +using GlonassSatelliteEphemeris = + android::hardware::gnss::gnss_assistance::GlonassSatelliteEphemeris; +using GnssAssistance = android::hardware::gnss::gnss_assistance::GnssAssistance; +using GnssSignalType = android::hardware::gnss::GnssSignalType; +using GpsAssistance = android::hardware::gnss::gnss_assistance::GnssAssistance::GpsAssistance; +using QzssAssistance = android::hardware::gnss::gnss_assistance::GnssAssistance::QzssAssistance; +using GnssAlmanac = android::hardware::gnss::gnss_assistance::GnssAlmanac; +using GnssSatelliteCorrections = + android::hardware::gnss::gnss_assistance::GnssAssistance::GnssSatelliteCorrections; +using GpsSatelliteEphemeris = android::hardware::gnss::gnss_assistance::GpsSatelliteEphemeris; +using SatelliteEphemerisTime = android::hardware::gnss::gnss_assistance::SatelliteEphemerisTime; +using UtcModel = android::hardware::gnss::gnss_assistance::UtcModel; +using LeapSecondsModel = android::hardware::gnss::gnss_assistance::LeapSecondsModel; +using KeplerianOrbitModel = android::hardware::gnss::gnss_assistance::KeplerianOrbitModel; +using KlobucharIonosphericModel = + android::hardware::gnss::gnss_assistance::KlobucharIonosphericModel; +using TimeModel = android::hardware::gnss::gnss_assistance::TimeModel; +using RealTimeIntegrityModel = android::hardware::gnss::gnss_assistance::RealTimeIntegrityModel; +using AuxiliaryInformation = android::hardware::gnss::gnss_assistance::AuxiliaryInformation; + +void GnssAssistance_class_init_once(JNIEnv* env, jclass clazz); + +class GnssAssistanceInterface { +public: + GnssAssistanceInterface(const sp<IGnssAssistanceInterface>& iGnssAssistance); + jboolean injectGnssAssistance(JNIEnv* env, jobject gnssAssistanceObj); + jboolean setCallback(const sp<IGnssAssistanceCallback>& callback); + +private: + const sp<IGnssAssistanceInterface> mGnssAssistanceInterface; +}; + +struct GnssAssistanceUtil { + static void setGlonassAssistance(JNIEnv* env, jobject glonassAssistanceObj, + GlonassAssistance& galileoAssistance); + static void setGlonassSatelliteEphemeris( + JNIEnv* env, jobject glonassSatelliteEphemerisObj, + std::vector<GlonassSatelliteEphemeris>& glonassSatelliteEphemerisList); + static void setGlonassAlmanac(JNIEnv* env, jobject glonassAlmanacObj, + GlonassAlmanac& glonassAlmanac); + static void setBeidouAssistance(JNIEnv* env, jobject beidouAssistanceObj, + BeidouAssistance& beidouAssistance); + static void setBeidouSatelliteEphemeris( + JNIEnv* env, jobject beidouSatelliteEphemerisObj, + std::vector<BeidouSatelliteEphemeris>& beidouSatelliteEphemerisList); + static void setGalileoAssistance(JNIEnv* env, jobject galileoAssistanceObj, + GalileoAssistance& galileoAssistance); + static void setGalileoSatelliteEphemeris( + JNIEnv* env, jobject galileoSatelliteEphemerisObj, + std::vector<GalileoSatelliteEphemeris>& galileoSatelliteEphemerisList); + static void setGaliloKlobucharIonosphericModel(JNIEnv* env, jobject galileoIonosphericModelObj, + GalileoIonosphericModel& ionosphericModel); + static void setGnssAssistance(JNIEnv* env, jobject gnssAssistanceObj, + GnssAssistance& gnssAssistance); + static void setGpsAssistance(JNIEnv* env, jobject gpsAssistanceObj, + GpsAssistance& gpsAssistance); + template <class T> + static void setGpsOrQzssSatelliteEphemeris(JNIEnv* env, jobject satelliteEphemerisObj, + std::vector<T>& satelliteEphemeris); + static void setQzssAssistance(JNIEnv* env, jobject qzssAssistanceObj, + QzssAssistance& qzssAssistance); + static void setGnssAlmanac(JNIEnv* env, jobject gnssAlmanacObj, GnssAlmanac& gnssAlmanac); + static void setGnssSignalType(JNIEnv* env, jobject gnssSignalTypeObj, + GnssSignalType& gnssSignalType); + static void setKeplerianOrbitModel(JNIEnv* env, jobject keplerianOrbitModelObj, + KeplerianOrbitModel& keplerianOrbitModel); + static void setKlobucharIonosphericModel(JNIEnv* env, jobject klobucharIonosphericModelObj, + KlobucharIonosphericModel& klobucharIonosphericModel); + static void setTimeModels(JNIEnv* env, jobject timeModelsObj, + std::vector<TimeModel>& timeModels); + static void setLeapSecondsModel(JNIEnv* env, jobject leapSecondsModelObj, + LeapSecondsModel& leapSecondsModel); + static void setRealTimeIntegrityModels( + JNIEnv* env, jobject realTimeIntegrityModelsObj, + std::vector<RealTimeIntegrityModel>& realTimeIntegrityModels); + + static void setSatelliteEphemerisTime(JNIEnv* env, jobject satelliteEphemerisTimeObj, + SatelliteEphemerisTime& satelliteEphemerisTime); + static void setUtcModel(JNIEnv* env, jobject utcModelObj, UtcModel& utcModel); + static void setSatelliteCorrections( + JNIEnv* env, jobject satelliteCorrectionsObj, + std::vector<GnssSatelliteCorrections>& satelliteCorrections); + static void setAuxiliaryInformation(JNIEnv* env, jobject auxiliaryInformationObj, + AuxiliaryInformation& auxiliaryInformation); +}; + +} // namespace android::gnss + +#endif // _ANDROID_SERVER_GNSSASSITANCE__H diff --git a/services/core/jni/gnss/GnssAssistanceCallback.cpp b/services/core/jni/gnss/GnssAssistanceCallback.cpp new file mode 100644 index 000000000000..dbb27d72663e --- /dev/null +++ b/services/core/jni/gnss/GnssAssistanceCallback.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "GnssAssistanceCbJni" + +#include "GnssAssistanceCallback.h" + +#include "Utils.h" + +namespace { +jmethodID method_gnssAssistanceInjectRequest; +} // anonymous namespace + +namespace android::gnss { + +using binder::Status; +using hardware::Return; +using hardware::Void; + +void GnssAssistanceCallback_class_init_once(JNIEnv* env, jclass clazz) { + method_gnssAssistanceInjectRequest = + env->GetStaticMethodID(clazz, "gnssAssistanceInjectRequest", "()V"); +} + +// Implementation of android::hardware::gnss::gnss_assistance::GnssAssistanceCallback. + +Status GnssAssistanceCallback::injectRequestCb() { + ALOGD("%s.", __func__); + JNIEnv* env = getJniEnv(); + env->CallVoidMethod(mCallbacksObj, method_gnssAssistanceInjectRequest); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return Status::ok(); +} + +} // namespace android::gnss diff --git a/services/core/jni/gnss/GnssAssistanceCallback.h b/services/core/jni/gnss/GnssAssistanceCallback.h new file mode 100644 index 000000000000..4c8c5fc06730 --- /dev/null +++ b/services/core/jni/gnss/GnssAssistanceCallback.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_SERVER_GNSS_GNSSASSITANCECALLBACK_H +#define _ANDROID_SERVER_GNSS_GNSSASSITANCECALLBACK_H + +#pragma once + +#ifndef LOG_TAG +#error LOG_TAG must be defined before including this file. +#endif + +#include <android/hardware/gnss/gnss_assistance/BnGnssAssistanceCallback.h> +#include <log/log.h> + +#include "Utils.h" +#include "jni.h" + +namespace android::gnss { + +void GnssAssistanceCallback_class_init_once(JNIEnv* env, jclass clazz); + +/* + * GnssAssistanceCallback class implements the callback methods required by the + * android::hardware::gnss::gnss_assistance::IGnssAssistanceCallback interface. + */ +class GnssAssistanceCallback : public hardware::gnss::gnss_assistance::BnGnssAssistanceCallback { +public: + GnssAssistanceCallback() {} + binder::Status injectRequestCb() override; +}; + +} // namespace android::gnss + +#endif // _ANDROID_SERVER_GNSS_GNSSASSITANCECALLBACK_H diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 42e457c97fd4..bc5c427e3ccb 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -51,7 +51,6 @@ import android.credentials.ISetEnabledProvidersCallback; import android.credentials.PrepareGetCredentialResponseInternal; import android.credentials.RegisterCredentialDescriptionRequest; import android.credentials.UnregisterCredentialDescriptionRequest; -import android.credentials.flags.Flags; import android.os.Binder; import android.os.CancellationSignal; import android.os.IBinder; @@ -538,34 +537,31 @@ public final class CredentialManagerService final int userId = UserHandle.getCallingUserId(); final int callingUid = Binder.getCallingUid(); - if (Flags.safeguardCandidateCredentialsApiCaller()) { - try { - String credentialManagerAutofillCompName = mContext.getResources().getString( - R.string.config_defaultCredentialManagerAutofillService); - ComponentName componentName = ComponentName.unflattenFromString( - credentialManagerAutofillCompName); - if (componentName == null) { - throw new SecurityException( - "Credential Autofill service does not exist on this device."); - } - PackageManager pm = mContext.createContextAsUser( - UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager(); - String callingProcessPackage = pm.getNameForUid(callingUid); - if (callingProcessPackage == null) { - throw new SecurityException( - "Couldn't determine the identity of the caller."); - } - if (!Objects.equals(componentName.getPackageName(), callingProcessPackage)) { - throw new SecurityException(callingProcessPackage - + " is not the device's credential autofill package."); - } - } catch (Resources.NotFoundException e) { + try { + String credentialManagerAutofillCompName = mContext.getResources().getString( + R.string.config_defaultCredentialManagerAutofillService); + ComponentName componentName = ComponentName.unflattenFromString( + credentialManagerAutofillCompName); + if (componentName == null) { throw new SecurityException( "Credential Autofill service does not exist on this device."); } + PackageManager pm = mContext.createContextAsUser( + UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager(); + String callingProcessPackage = pm.getNameForUid(callingUid); + if (callingProcessPackage == null) { + throw new SecurityException( + "Couldn't determine the identity of the caller."); + } + if (!Objects.equals(componentName.getPackageName(), callingProcessPackage)) { + throw new SecurityException(callingProcessPackage + + " is not the device's credential autofill package."); + } + } catch (Resources.NotFoundException e) { + throw new SecurityException( + "Credential Autofill service does not exist on this device."); } - // New request session, scoped for this request only. final GetCandidateRequestSession session = new GetCandidateRequestSession( diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java index de78271acddc..69e856de401a 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java @@ -325,7 +325,8 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, } if (newState != INVALID_DEVICE_STATE_IDENTIFIER && newState != mLastReportedState) { - if (Trace.isTagEnabled(TRACE_TAG_SYSTEM_SERVER)) { + if (mLastHingeAngleSensorEvent != null + && Trace.isTagEnabled(TRACE_TAG_SYSTEM_SERVER)) { Trace.instant(TRACE_TAG_SYSTEM_SERVER, "[Device state changed] Last hinge sensor event timestamp: " + mLastHingeAngleSensorEvent.timestamp); diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java index e545a49d3139..554b5b4297f2 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java @@ -19,8 +19,8 @@ package com.android.server.pm; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.isNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; diff --git a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java index 9117cc8e5ab8..a38ecc8523b1 100644 --- a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java +++ b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java @@ -3186,6 +3186,32 @@ public class VpnTest extends VpnTestBase { assertEquals(profile, ikev2VpnProfile.toVpnProfile()); } + @Test + public void testStartAlwaysOnVpnOnSafeMode() throws Exception { + final Vpn vpn = createVpn(PRIMARY_USER.id); + setMockedUsers(PRIMARY_USER); + + // UID checks must return a different UID; otherwise it'll be treated as already prepared. + final int uid = Process.myUid() + 1; + when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt())) + .thenReturn(uid); + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(mVpnProfile.encode()); + + setAndVerifyAlwaysOnPackage(vpn, uid, false); + assertTrue(vpn.startAlwaysOnVpn()); + assertEquals(TEST_VPN_PKG, vpn.getAlwaysOnPackage()); + + // Simulate safe mode and restart the always-on VPN to verify the always-on package is not + // reset. + doReturn(null).when(mVpnProfileStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG)); + doReturn(null).when(mPackageManager).queryIntentServicesAsUser( + any(), any(), eq(PRIMARY_USER.id)); + doReturn(true).when(mPackageManager).isSafeMode(); + assertFalse(vpn.startAlwaysOnVpn()); + assertEquals(TEST_VPN_PKG, vpn.getAlwaysOnPackage()); + } + // Make it public and un-final so as to spy it public class TestDeps extends Vpn.Dependencies { TestDeps() {} diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java index 29f07227a12d..fecbc7c81347 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java @@ -25,7 +25,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.hardware.Sensor; @@ -396,7 +396,7 @@ public final class DisplayPowerProximityStateControllerTest { assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled()); assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged()); assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()); - verifyZeroInteractions(mWakelockController); + verifyNoMoreInteractions(mWakelockController); } private void setScreenOffBecauseOfPositiveProximityState() { diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index f8b4113a3c04..b8a5f341eb8a 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -234,6 +234,7 @@ public class LocalDisplayAdapterTest { doReturn(true).when(mFlags).isDisplayOffloadEnabled(); doReturn(true).when(mFlags).isEvenDimmerEnabled(); + doReturn(true).when(mFlags).isDisplayContentModeManagementEnabled(); initDisplayOffloadSession(); } @@ -1468,6 +1469,103 @@ public class LocalDisplayAdapterTest { assertFalse(mDisplayOffloadSession.isActive()); } + @Test + public void testAllowsContentSwitch_firstDisplay() throws Exception { + // Set up a first display + setUpDisplay(new FakeDisplay(PORT_A)); + updateAvailableDisplays(); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + // The first display should be allowed to use the content mode switch + DisplayDevice firstDisplayDevice = mListener.addedDisplays.get(0); + assertTrue((firstDisplayDevice.getDisplayDeviceInfoLocked().flags + & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0); + } + + @Test + public void testAllowsContentSwitch_secondaryDisplayPublicAndNotShouldShowOwnContent() + throws Exception { + // Set up a first display and a secondary display + setUpDisplay(new FakeDisplay(PORT_A)); + setUpDisplay(new FakeDisplay(PORT_B)); + updateAvailableDisplays(); + + // Set the secondary display to be a public display + doReturn(new int[0]).when(mMockedResources) + .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts); + // Disable FLAG_OWN_CONTENT_ONLY for the secondary display + doReturn(true).when(mMockedResources) + .getBoolean(com.android.internal.R.bool.config_localDisplaysMirrorContent); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + // This secondary display should be allowed to use the content mode switch + DisplayDevice secondaryDisplayDevice = mListener.addedDisplays.get(1); + assertTrue((secondaryDisplayDevice.getDisplayDeviceInfoLocked().flags + & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0); + } + + @Test + public void testAllowsContentSwitch_privateDisplay() throws Exception { + // Set up a first display and a secondary display + setUpDisplay(new FakeDisplay(PORT_A)); + setUpDisplay(new FakeDisplay(PORT_B)); + updateAvailableDisplays(); + + // Set the secondary display to be a private display + doReturn(new int[]{ PORT_B }).when(mMockedResources) + .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + // The private display should not be allowed to use the content mode switch + DisplayDevice secondaryDisplayDevice = mListener.addedDisplays.get(1); + assertTrue((secondaryDisplayDevice.getDisplayDeviceInfoLocked().flags + & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) == 0); + } + + @Test + public void testAllowsContentSwitch_ownContentOnlyDisplay() throws Exception { + // Set up a first display and a secondary display + setUpDisplay(new FakeDisplay(PORT_A)); + setUpDisplay(new FakeDisplay(PORT_B)); + updateAvailableDisplays(); + + // Enable FLAG_OWN_CONTENT_ONLY for the secondary display + doReturn(false).when(mMockedResources) + .getBoolean(com.android.internal.R.bool.config_localDisplaysMirrorContent); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + // The secondary display with FLAG_OWN_CONTENT_ONLY enabled should not be allowed to use the + // content mode switch + DisplayDevice secondaryDisplayDevice = mListener.addedDisplays.get(1); + assertTrue((secondaryDisplayDevice.getDisplayDeviceInfoLocked().flags + & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) == 0); + } + + @Test + public void testAllowsContentSwitch_flagShouldShowSystemDecorations() throws Exception { + // Set up a display + FakeDisplay display = new FakeDisplay(PORT_A); + setUpDisplay(display); + updateAvailableDisplays(); + + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + // Display with FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS enabled should not be allowed to use the + // content mode switch + DisplayDevice displayDevice = mListener.addedDisplays.get(0); + int flags = displayDevice.getDisplayDeviceInfoLocked().flags; + boolean allowsContentModeSwitch = + ((flags & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0); + boolean shouldShowSystemDecorations = + ((flags & DisplayDeviceInfo.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0); + assertFalse(allowsContentModeSwitch && shouldShowSystemDecorations); + } + private void initDisplayOffloadSession() { when(mDisplayOffloader.startOffload()).thenReturn(true); when(mDisplayOffloader.allowAutoBrightnessInDoze()).thenReturn(true); diff --git a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java index 019b70ef1424..f067fa10f611 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.hardware.display.DisplayManagerInternal; @@ -210,7 +210,7 @@ public final class WakelockControllerTest { // Validate one suspend blocker was released assertFalse(mWakelockController.isProximityPositiveAcquired()); - verifyZeroInteractions(mDisplayPowerCallbacks); + verifyNoMoreInteractions(mDisplayPowerCallbacks); } @Test @@ -238,7 +238,7 @@ public final class WakelockControllerTest { // Validate one suspend blocker was released assertFalse(mWakelockController.isProximityNegativeAcquired()); - verifyZeroInteractions(mDisplayPowerCallbacks); + verifyNoMoreInteractions(mDisplayPowerCallbacks); } @Test @@ -265,7 +265,7 @@ public final class WakelockControllerTest { // Validate one suspend blocker was released assertFalse(mWakelockController.isOnStateChangedPending()); - verifyZeroInteractions(mDisplayPowerCallbacks); + verifyNoMoreInteractions(mDisplayPowerCallbacks); } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index 49de80179683..bf0543939d85 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -28,7 +28,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.content.Context; @@ -422,7 +421,7 @@ public final class DisplayBrightnessControllerTest { mDisplayBrightnessController.setAutomaticBrightnessController( automaticBrightnessController); assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f); - verifyZeroInteractions(automaticBrightnessController); + verifyNoMoreInteractions(automaticBrightnessController); verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay(); verify(mBrightnessSetting, never()).setBrightnessNoNotify(brightness); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java index f9fbf1bd5085..b41f03bb8030 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java @@ -17,9 +17,9 @@ package com.android.server.display.whitebalance; import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java index 7c239ef02e58..586ff52aa78c 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java @@ -328,6 +328,7 @@ public class TestDreamEnvironment { case DREAM_STATE_STARTED -> startDream(); case DREAM_STATE_WOKEN -> wakeDream(); } + mTestableLooper.processAllMessages(); } while (mCurrentDreamState < state); return true; diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index eda5e8613dba..77d67019c0ed 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -67,6 +67,9 @@ import java.util.concurrent.TimeUnit; /** * Test RescueParty. + * TODO: b/354112511 delete this file + * Moved to frameworks/base/tests/PackageWatchdog/src/com/android/server/RescuePartyTest + * */ public class RescuePartyTest { private static final long CURRENT_NETWORK_TIME_MILLIS = 0L; diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index d79d88400cf9..f0e61ec9c692 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -109,7 +109,7 @@ import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.Manifest; import android.app.ActivityManager; @@ -3724,8 +3724,8 @@ public final class AlarmManagerServiceTest { setDeviceConfigInt(KEY_TEMPORARY_QUOTA_BUMP, 0); mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER); - verifyZeroInteractions(mPackageManagerInternal); - verifyZeroInteractions(mService.mHandler); + verifyNoMoreInteractions(mPackageManagerInternal); + verifyNoMoreInteractions(mService.mHandler); } private void testTemporaryQuota_bumpedAfterDeferral(int standbyBucket) throws Exception { diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java index 7dab1c854625..859d2d2f2e38 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java @@ -30,7 +30,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.app.AlarmManager; import android.app.PendingIntent; @@ -244,7 +244,7 @@ public class AlarmStoreTest { addAlarmsToStore(simpleAlarm, alarmClock); mAlarmStore.remove(simpleAlarm::equals); - verifyZeroInteractions(onRemoved); + verifyNoMoreInteractions(onRemoved); mAlarmStore.remove(alarmClock::equals); verify(onRemoved).run(); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java index 35ab2d233563..acc06d0c7cba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -74,7 +74,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import android.Manifest; import android.app.ActivityManager; @@ -584,7 +583,7 @@ public class ActivityManagerServiceTest { if (app.uid == uidRec.getUid() && expectedBlockState == NETWORK_STATE_BLOCK) { verify(app.getThread()).setNetworkBlockSeq(uidRec.curProcStateSeq); } else { - verifyZeroInteractions(app.getThread()); + verifyNoMoreInteractions(app.getThread()); } Mockito.reset(app.getThread()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java index 194bf4ba73f3..84110ae5cd02 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java @@ -34,9 +34,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java index e678acc092e9..987b9c6427d8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java @@ -21,6 +21,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.server.am.ActivityManagerService.Injector; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -625,6 +626,60 @@ public class ApplicationStartInfoTest { assertTrue(startInfo.equals(startInfoFromParcel)); } + /** Test that new timestamps are added to the correct record (the most recently created one). */ + @Test + public void testTimestampAddedToCorrectRecord() throws Exception { + // Use a different start timestamp for each record so we can identify which was added to. + final long startTimeRecord1 = 123L; + final long startTimeRecord2 = 456L; + + final long forkTime = 789L; + + // Create a process record to use with all starts. + ProcessRecord app = makeProcessRecord( + APP_1_PID_1, // pid + APP_1_UID, // uid + APP_1_UID, // packageUid + null, // definingUid + APP_1_PROCESS_NAME, // processName + APP_1_PACKAGE_NAME); // packageName + + // Trigger a start info record. + mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord1, app, + buildIntent(COMPONENT), false /* isAlarm */); + + // Wait at least 1 ms for monotonic time to increase. + sleep(1); + + // Verify the record was added successfully. + ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>(); + mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list); + assertEquals(1, list.size()); + assertEquals(startTimeRecord1, list.get(0).getStartupTimestamps().get(0).longValue()); + + // Now trigger another start info record. + mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord2, app, + buildIntent(COMPONENT), false /* isAlarm */); + + // Add a timestamp to the most recent record. + mAppStartInfoTracker.addTimestampToStart( + app, forkTime, ApplicationStartInfo.START_TIMESTAMP_FORK); + + // Verify the record was added successfully. + list.clear(); + mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list); + assertEquals(2, list.size()); + assertEquals(startTimeRecord2, list.get(0).getStartupTimestamps().get(0).longValue()); + assertEquals(startTimeRecord1, list.get(1).getStartupTimestamps().get(0).longValue()); + + // Verify that the new timestamp is set correctly on the 2nd record that was added and not + // on the first. + assertEquals(forkTime, list.get(0).getStartupTimestamps() + .get(ApplicationStartInfo.START_TIMESTAMP_FORK).longValue()); + assertFalse(list.get(1).getStartupTimestamps().containsKey( + ApplicationStartInfo.START_TIMESTAMP_FORK)); + } + private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) { try { Field field = clazz.getDeclaredField(fieldName); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java index 637c73f31ce0..dd5924ad4a7e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java @@ -88,7 +88,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyLong; -import static org.mockito.Mockito.anyObject; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.clearInvocations; @@ -747,7 +747,7 @@ public final class BackgroundRestrictionTest { mCurrentTimeMillis = 10_000L; doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp(); - doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(anyObject()); + doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(any()); mAppFGSTracker.onForegroundServiceStateChanged(testPkgName, testUid, testPid, true); mAppFGSTracker.onForegroundServiceNotificationUpdated( @@ -1925,7 +1925,7 @@ public final class BackgroundRestrictionTest { mCurrentTimeMillis = 10_000L; doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp(); - doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(anyObject()); + doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(any()); // Run with a media playback service which starts/stops immediately, we should // goto the restricted bucket. @@ -3170,7 +3170,7 @@ public final class BackgroundRestrictionTest { inv.getArgument(2)); return null; }).when(telephonyManager).registerCarrierPrivilegesCallback( - anyInt(), anyObject(), anyObject()); + anyInt(), any(), any()); } public void registerCarrierPrivilegesCallback(int phoneId, Executor executor, diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 3a9c99d57d71..d540b2ec13eb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -155,7 +155,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { doAnswer((invocation) -> { Log.v(TAG, "Intercepting startProcessLocked() for " + Arrays.toString(invocation.getArguments())); - assertHealth(); final String processName = invocation.getArgument(0); final ProcessStartBehavior behavior = mNewProcessStartBehaviors.getOrDefault( processName, mNextProcessStartBehavior.getAndSet(ProcessStartBehavior.SUCCESS)); @@ -206,6 +205,9 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { mActiveProcesses.remove(res); res.setKilled(true); break; + case MISSING_RESPONSE: + res.setPendingStart(true); + break; default: throw new UnsupportedOperationException(); } @@ -244,6 +246,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0; mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500; mConstants.MAX_FROZEN_OUTGOING_BROADCASTS = 10; + mConstants.PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS = 2000; } @After @@ -279,6 +282,8 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { FAIL_NULL, /** Process is killed without reporting to BroadcastQueue */ KILLED_WITHOUT_NOTIFY, + /** Process start fails without no response */ + MISSING_RESPONSE, } private enum ProcessBehavior { @@ -1173,6 +1178,37 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { verifyScheduleReceiver(times(1), receiverOrangeApp, timezone); } + @Test + public void testProcessStartWithMissingResponse() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); + + mNewProcessStartBehaviors.put(PACKAGE_GREEN, ProcessStartBehavior.MISSING_RESPONSE); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of( + withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10), + withPriority(makeRegisteredReceiver(receiverBlueApp), 5), + withPriority(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), 0)))); + + final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(timezone, callerApp, + List.of(makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE)))); + + waitForIdle(); + final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN, + getUidForPackage(PACKAGE_GREEN)); + final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW, + getUidForPackage(PACKAGE_YELLOW)); + final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE, + getUidForPackage(PACKAGE_ORANGE)); + + verifyScheduleReceiver(times(1), receiverGreenApp, airplane); + verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane); + verifyScheduleReceiver(times(1), receiverYellowApp, airplane); + verifyScheduleReceiver(times(1), receiverOrangeApp, timezone); + } + /** * Verify that a broadcast sent to a frozen app, which gets killed as part of unfreezing * process due to pending sync binder transactions, is delivered as expected. diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java index ae0452a60161..b7087c74bf8d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java @@ -21,7 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.os.ParcelFileDescriptor; import android.platform.test.annotations.Presubmit; @@ -105,7 +105,7 @@ public class FullBackupUtilsTest { } catch (EOFException expected) { } - verifyZeroInteractions(mOutputStreamMock); + verifyNoMoreInteractions(mOutputStreamMock); assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue(); } @@ -126,7 +126,7 @@ public class FullBackupUtilsTest { } catch (EOFException expected) { } - verifyZeroInteractions(mOutputStreamMock); + verifyNoMoreInteractions(mOutputStreamMock); assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue(); } @@ -141,7 +141,7 @@ public class FullBackupUtilsTest { FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor, mOutputStreamMock); - verifyZeroInteractions(mOutputStreamMock); + verifyNoMoreInteractions(mOutputStreamMock); assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue(); } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java index bf7e3a0bd0a6..346d5f787621 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java @@ -32,7 +32,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.internal.verification.VerificationModeFactory.times; import android.app.backup.IBackupManagerMonitor; @@ -239,7 +238,7 @@ public class TarBackupReaderTest { mMockPackageManagerInternal, mUserId, mContext); assertThat(policy).isEqualTo(RestorePolicy.IGNORE); - verifyZeroInteractions(mBackupManagerMonitorMock); + verifyNoMoreInteractions(mBackupManagerMonitorMock); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 3e8794377d37..2d84887afb41 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -1029,6 +1029,7 @@ public class QuotaControllerTest { @Test @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS) public void testGetExecutionStatsLocked_Values_NewDefaultBucketWindowSizes() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(0, "com.android.test", @@ -1127,6 +1128,54 @@ public class QuotaControllerTest { } } + @Test + @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS, + Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS}) + public void testGetExecutionStatsLocked_Values_NewDefaultBucketWindowSizes_Tuning() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); + + ExecutionStats expectedStats = new ExecutionStats(); + + // Exempted + expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_EXEMPTED; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_EXEMPTED; + expectedStats.expirationTimeElapsed = now + 34 * MINUTE_IN_MILLIS; + expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 5; + expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 20; + expectedStats.sessionCountInWindow = 1; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + EXEMPTED_INDEX)); + } + + // Active + expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; + // There is only one session in the past active bucket window, the empty time for this + // window is the bucket window size - duration of the session. + expectedStats.expirationTimeElapsed = now + 54 * MINUTE_IN_MILLIS; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + ACTIVE_INDEX)); + } + } + /** * Tests that getExecutionStatsLocked returns the correct stats soon after device startup. */ @@ -1195,6 +1244,7 @@ public class QuotaControllerTest { @Test @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS) public void testGetExecutionStatsLocked_Values_BeginningOfTime_NewDefaultBucketWindowSizes() { // Set time to 3 minutes after boot. advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis()); @@ -1206,10 +1256,10 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); // Exempted - expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS; - expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; - expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_EXEMPTED; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_EXEMPTED; expectedStats.expirationTimeElapsed = 10 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS; expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 2; @@ -1268,6 +1318,49 @@ public class QuotaControllerTest { } } + @Test + @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS, + Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS}) + public void testGetExecutionStatsLocked_Values_BeginningOfTime_NewDefaultBucketWindowSizes_Tunning() { + // Set time to 3 minutes after boot. + advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis()); + advanceElapsedClock(3 * MINUTE_IN_MILLIS); + + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2), false); + + ExecutionStats expectedStats = new ExecutionStats(); + + // Exempted + expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_EXEMPTED; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_EXEMPTED; + expectedStats.expirationTimeElapsed = 30 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS; + expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 2; + expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 2; + expectedStats.sessionCountInWindow = 1; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + EXEMPTED_INDEX)); + } + + // Active + expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; + expectedStats.expirationTimeElapsed = 50 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + ACTIVE_INDEX)); + } + } + /** * Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing. */ @@ -1425,6 +1518,7 @@ public class QuotaControllerTest { @Test @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS) public void testGetExecutionStatsLocked_CoalescingSessions_NewDefaultBucketWindowSizes() { for (int i = 0; i < 20; ++i) { mQuotaController.saveTimingSession(0, "com.android.test", @@ -1581,6 +1675,165 @@ public class QuotaControllerTest { } } + @Test + @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS, + Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS}) + public void testGetExecutionStatsLocked_CoalescingSessions_NewDefaultBucketWindowSizes_Tuning() { + for (int i = 0; i < 20; ++i) { + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession( + JobSchedulerService.sElapsedRealtimeClock.millis(), + 5 * MINUTE_IN_MILLIS, 5), false); + advanceElapsedClock(5 * MINUTE_IN_MILLIS); + advanceElapsedClock(5 * MINUTE_IN_MILLIS); + for (int j = 0; j < 5; ++j) { + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession( + JobSchedulerService.sElapsedRealtimeClock.millis(), + MINUTE_IN_MILLIS, 2), false); + advanceElapsedClock(MINUTE_IN_MILLIS); + advanceElapsedClock(54 * SECOND_IN_MILLIS); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession( + JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1), false); + advanceElapsedClock(500); + advanceElapsedClock(400); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession( + JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1), false); + advanceElapsedClock(100); + advanceElapsedClock(5 * SECOND_IN_MILLIS); + } + advanceElapsedClock(40 * MINUTE_IN_MILLIS); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 0); + + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(16, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + assertEquals(64, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + assertEquals(192, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + assertEquals(320, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 500); + + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(11, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + // WINDOW_SIZE_WORKING_MS * 5 TimingSessions are coalesced + assertEquals(44, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + // WINDOW_SIZE_FREQUENT_MS * 5 TimingSessions are coalesced + assertEquals(132, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + // WINDOW_SIZE_RARE_MS * 5 TimingSessions are coalesced + assertEquals(220, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1000); + + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(11, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + assertEquals(44, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + assertEquals(132, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + assertEquals(220, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, + 5 * SECOND_IN_MILLIS); + + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(7, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + // WINDOW_SIZE_WORKING_MS * 9 TimingSessions are coalesced + assertEquals(28, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + // WINDOW_SIZE_FREQUENT_MS * 9 TimingSessions are coalesced + assertEquals(84, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + // WINDOW_SIZE_RARE_MS * 9 TimingSessions are coalesced + assertEquals(140, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, + MINUTE_IN_MILLIS); + + // Only two TimingSessions there for every hour. + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(2, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + assertEquals(8, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + assertEquals(24, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + assertEquals(40, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, + 5 * MINUTE_IN_MILLIS); + + // Only one TimingSessions there for every hour + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(1, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + assertEquals(4, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + assertEquals(12, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + assertEquals(20, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, + 15 * MINUTE_IN_MILLIS); + + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(1, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + assertEquals(4, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + assertEquals(12, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + assertEquals(20, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + // QuotaController caps the duration at 15 minutes, so there shouldn't be any difference + // between an hour and 15 minutes. + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, HOUR_IN_MILLIS); + + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(1, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + assertEquals(4, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + assertEquals(12, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + assertEquals(20, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + } + /** * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object. */ @@ -2231,32 +2484,6 @@ public class QuotaControllerTest { } } - @Test - @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) - public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow_NewDefaultBucketWindowSizes() { - final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (8 * HOUR_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), false); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), - false); - - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, - 20 * MINUTE_IN_MILLIS); - setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 20 * MINUTE_IN_MILLIS); - // window size = allowed time, so jobs can essentially run non-stop until they reach the - // max execution time. - setStandbyBucket(EXEMPTED_INDEX); - synchronized (mQuotaController.mLock) { - assertEquals(10 * MINUTE_IN_MILLIS, - mQuotaController.getRemainingExecutionTimeLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS, - mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - } - } - /** * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket * window. @@ -2327,6 +2554,7 @@ public class QuotaControllerTest { @Test @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS) public void testGetTimeUntilQuotaConsumedLocked_BucketWindow_NewDefaultBucketWindowSizes() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); // Close to RARE boundary. @@ -2390,7 +2618,30 @@ public class QuotaControllerTest { @Test @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS, + Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS}) + public void testGetTimeUntilQuotaConsumedLocked_BucketWindow_NewDefaultBucketWindowSizes_Tuning() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + // Close to ACTIVE boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS), + 3 * MINUTE_IN_MILLIS, 5), false); + + // ACTIVE window != allowed time. + setStandbyBucket(ACTIVE_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(17 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(20 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + } + } + + @Test + @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS, Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER}) + @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS) public void testGetTimeUntilQuotaConsumedLocked_Installer() { PackageInfo pi = new PackageInfo(); pi.packageName = SOURCE_PACKAGE; @@ -2412,7 +2663,7 @@ public class QuotaControllerTest { // Far away from FREQUENT boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( - now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS), + now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5), false); // Overlap WORKING_SET boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -2422,12 +2673,12 @@ public class QuotaControllerTest { // Close to ACTIVE boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( - now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS), + now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5), false); // Close to EXEMPTED boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( - now - (mQcConstants.WINDOW_SIZE_EXEMPTED_MS - MINUTE_IN_MILLIS), + now - (mQcConstants.WINDOW_SIZE_EXEMPTED_MS - MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5), false); // No additional quota for the system installer when the app is in RARE, FREQUENT, @@ -2486,6 +2737,42 @@ public class QuotaControllerTest { } } + @Test + @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS, + Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER, + Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS}) + public void testGetTimeUntilQuotaConsumedLocked_Installer_Tuning() { + PackageInfo pi = new PackageInfo(); + pi.packageName = SOURCE_PACKAGE; + pi.requestedPermissions = new String[]{Manifest.permission.INSTALL_PACKAGES}; + pi.requestedPermissionsFlags = new int[]{PackageInfo.REQUESTED_PERMISSION_GRANTED}; + pi.applicationInfo = new ApplicationInfo(); + pi.applicationInfo.uid = mSourceUid; + doReturn(List.of(pi)).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt()); + doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkPermission( + eq(Manifest.permission.INSTALL_PACKAGES), anyInt(), eq(mSourceUid)); + mQuotaController.onSystemServicesReady(); + + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + // Close to EXEMPTED boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession( + now - (mQcConstants.WINDOW_SIZE_EXEMPTED_MS - MINUTE_IN_MILLIS), + 2 * MINUTE_IN_MILLIS, 5), false); + + // Additional quota for the system installer when the app is in EXEMPTED bucket. + // EXEMPTED window == allowed time. + setStandbyBucket(EXEMPTED_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(38 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 2 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + } + } + /** * Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit. */ @@ -2637,33 +2924,6 @@ public class QuotaControllerTest { } } - @Test - @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) - public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow_NewDefaultBucketWindowSizes() { - final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (24 * HOUR_IN_MILLIS), - mQcConstants.MAX_EXECUTION_TIME_MS - 20 * MINUTE_IN_MILLIS, 5), - false); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (20 * MINUTE_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), - false); - - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, - 20 * MINUTE_IN_MILLIS); - setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 20 * MINUTE_IN_MILLIS); - // window size != allowed time. - setStandbyBucket(EXEMPTED_INDEX); - synchronized (mQuotaController.mLock) { - assertEquals(0, - mQuotaController.getRemainingExecutionTimeLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 20 * MINUTE_IN_MILLIS, - mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - } - } - /** * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket * window and the session is rolling out of the window. diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java index 3d0c63780ef3..29af7d28339d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java @@ -27,7 +27,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.testng.AssertJUnit.assertEquals; @@ -128,7 +128,7 @@ public class BackgroundUserSoundNotifierTest { AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT); clearInvocations(mNotificationManager); mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi); - verifyZeroInteractions(mNotificationManager); + verifyNoMoreInteractions(mNotificationManager); } @Test @@ -143,7 +143,7 @@ public class BackgroundUserSoundNotifierTest { Build.VERSION.SDK_INT); clearInvocations(mNotificationManager); mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi); - verifyZeroInteractions(mNotificationManager); + verifyNoMoreInteractions(mNotificationManager); } @Test diff --git a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java index 8257168f8d08..8257168f8d08 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java index dc04b6aea318..bf3fe8c70bf1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java @@ -29,7 +29,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.app.AppOpsManager; import android.content.Context; @@ -176,9 +176,9 @@ public class CameraPrivacyLightControllerTest { prepareCameraPrivacyLightController(List.of(getNextLight(true)), Collections.EMPTY_SET, true, new int[0], mDefaultAlsThresholdsLux, mDefaultAlsAveragingIntervalMillis); - verifyZeroInteractions(mLightsManager); - verifyZeroInteractions(mAppOpsManager); - verifyZeroInteractions(mSensorManager); + verifyNoMoreInteractions(mLightsManager); + verifyNoMoreInteractions(mAppOpsManager); + verifyNoMoreInteractions(mSensorManager); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/TimingsTraceAndSlogTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/TimingsTraceAndSlogTest.java index 52cd29cabb94..1415dd690a0a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/utils/TimingsTraceAndSlogTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/utils/TimingsTraceAndSlogTest.java @@ -22,10 +22,10 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.contains; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.matches; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.matches; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java index 241ffdc19ce4..4f74667f3ff2 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java @@ -33,10 +33,8 @@ import static com.android.window.flags.Flags.FLAG_MULTI_CROP; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; @@ -48,7 +46,6 @@ import android.platform.test.annotations.RequiresFlagsEnabled; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; -import android.view.Display; import android.view.DisplayInfo; import android.view.WindowInsets; import android.view.WindowManager; @@ -95,7 +92,6 @@ public class WallpaperCropperTest { @Mock private Resources mResources; - private WallpaperCropper mWallpaperCropper; private static final Point PORTRAIT_ONE = new Point(500, 800); private static final Point PORTRAIT_TWO = new Point(400, 1000); @@ -171,7 +167,6 @@ public class WallpaperCropperTest { return getWallpaperTestDir(userId); }).when(() -> WallpaperUtils.getWallpaperDir(anyInt())); - mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper); } private File getWallpaperTestDir(int userId) { @@ -738,13 +733,13 @@ public class WallpaperCropperTest { DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 2560; displayInfo.logicalHeight = 1044; + setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight))); doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(DEFAULT_DISPLAY)); WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false, new Point(100, 100)); - assertThat( - mWallpaperCropper.isWallpaperCompatibleForDisplay(DEFAULT_DISPLAY, - wallpaperData)).isTrue(); + assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay( + DEFAULT_DISPLAY, wallpaperData)).isTrue(); } // Test isWallpaperCompatibleForDisplay always return true for the stock wallpaper. @@ -755,49 +750,67 @@ public class WallpaperCropperTest { DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 2560; displayInfo.logicalHeight = 1044; + setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight))); doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId)); WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ true, new Point(100, 100)); - assertThat( - mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId, - wallpaperData)).isTrue(); + assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay( + displayId, wallpaperData)).isTrue(); } // Test isWallpaperCompatibleForDisplay wallpaper is suitable for the display and wallpaper // aspect ratio meets the hard-coded aspect ratio. @Test - public void isWallpaperCompatibleForDisplay_wallpaperSizeSuitableForDisplayAndMeetAspectRatio_returnTrue() + public void isWallpaperCompatibleForDisplay_wallpaperSizeLargerThanDisplayAndMeetAspectRatio_returnTrue() throws Exception { final int displayId = 2; DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 2560; displayInfo.logicalHeight = 1044; + setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight))); doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId)); WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false, new Point(4000, 3000)); - assertThat( - mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId, - wallpaperData)).isTrue(); + assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay( + displayId, wallpaperData)).isTrue(); } - // Test isWallpaperCompatibleForDisplay wallpaper is not suitable for the display and wallpaper - // aspect ratio meets the hard-coded aspect ratio. + // Test isWallpaperCompatibleForDisplay wallpaper is smaller than the display but larger than + // the threshold and wallpaper aspect ratio meets the hard-coded aspect ratio. @Test - public void isWallpaperCompatibleForDisplay_wallpaperSizeNotSuitableForDisplayAndMeetAspectRatio_returnFalse() + public void isWallpaperCompatibleForDisplay_wallpaperSizeSmallerThanDisplayButBeyondThresholdAndMeetAspectRatio_returnTrue() throws Exception { final int displayId = 2; DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 2560; displayInfo.logicalHeight = 1044; + setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight))); doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId)); WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false, - new Point(1000, 500)); + new Point(2000, 900)); + + assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay( + displayId, wallpaperData)).isTrue(); + } + + // Test isWallpaperCompatibleForDisplay wallpaper is smaller than the display but larger than + // the threshold and wallpaper aspect ratio meets the hard-coded aspect ratio. + @Test + public void isWallpaperCompatibleForDisplay_wallpaperSizeSmallerThanDisplayButAboveThresholdAndMeetAspectRatio_returnFalse() + throws Exception { + final int displayId = 2; + DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 2560; + displayInfo.logicalHeight = 1044; + setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight))); + doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId)); + WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false, + new Point(2000, 800)); - assertThat( - mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId, - wallpaperData)).isFalse(); + assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay( + displayId, wallpaperData)).isFalse(); } // Test isWallpaperCompatibleForDisplay wallpaper is suitable for the display and wallpaper @@ -809,13 +822,13 @@ public class WallpaperCropperTest { DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 2560; displayInfo.logicalHeight = 1044; + setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight))); doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId)); WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false, new Point(2000, 4000)); - assertThat( - mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId, - wallpaperData)).isFalse(); + assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay( + displayId, wallpaperData)).isFalse(); } // Test isWallpaperCompatibleForDisplay, portrait display, wallpaper is suitable for the display @@ -827,24 +840,13 @@ public class WallpaperCropperTest { DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 1044; displayInfo.logicalHeight = 2560; + setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight))); doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId)); WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false, new Point(2000, 4000)); - assertThat( - mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId, - wallpaperData)).isTrue(); - } - - private void mockDisplay(int displayId, Point displayResolution) { - final Display mockDisplay = mock(Display.class); - when(mockDisplay.getDisplayInfo(any(DisplayInfo.class))).thenAnswer(invocation -> { - DisplayInfo displayInfo = invocation.getArgument(0); - displayInfo.displayId = displayId; - displayInfo.logicalWidth = displayResolution.x; - displayInfo.logicalHeight = displayResolution.y; - return true; - }); + assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay( + displayId, wallpaperData)).isTrue(); } private WallpaperData createWallpaperData(boolean isStockWallpaper, Point wallpaperSize) diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index 4e56422ec391..94ce72368a92 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java @@ -39,9 +39,9 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.content.Context; @@ -53,8 +53,8 @@ import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Handler; import android.os.IBinder; -import android.os.IWakeLockCallback; import android.os.IScreenTimeoutPolicyListener; +import android.os.IWakeLockCallback; import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; @@ -205,7 +205,7 @@ public class NotifierTest { mTestExecutor.simulateAsyncExecutionOfLastCommand(); // THEN the device doesn't vibrate - verifyZeroInteractions(mVibrator); + verifyNoMoreInteractions(mVibrator); } @Test @@ -238,7 +238,7 @@ public class NotifierTest { mTestExecutor.simulateAsyncExecutionOfLastCommand(); // THEN the device doesn't vibrate - verifyZeroInteractions(mVibrator); + verifyNoMoreInteractions(mVibrator); } @Test @@ -725,10 +725,11 @@ public class NotifierTest { final int uid = 1234; final int pid = 5678; + mNotifier.onWakeLockReleased(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag", "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null, exceptingCallback); - verifyZeroInteractions(mWakeLockLog); + verifyNoMoreInteractions(mWakeLockLog); mTestLooper.dispatchAll(); verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, 1); clearInvocations(mBatteryStats); @@ -790,6 +791,55 @@ public class NotifierTest { } @Test + public void test_wakeLockLogUsesWorkSource() { + createNotifier(); + clearInvocations(mWakeLockLog); + IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() { + @Override public void onStateChanged(boolean enabled) throws RemoteException { + throw new RemoteException("Just testing"); + } + }; + + final int uid = 1234; + final int pid = 5678; + WorkSource worksource = new WorkSource(1212); + WorkSource worksource2 = new WorkSource(3131); + + mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag", + "my.package.name", uid, pid, worksource, /* historyTag= */ null, + exceptingCallback); + verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", 1212, + PowerManager.PARTIAL_WAKE_LOCK, -1); + + // Release the wakelock + mNotifier.onWakeLockReleased(PowerManager.FULL_WAKE_LOCK, "wakelockTag2", + "my.package.name", uid, pid, worksource2, /* historyTag= */ null, + exceptingCallback); + verify(mWakeLockLog).onWakeLockReleased("wakelockTag2", 3131, -1); + + // clear the handler + mTestLooper.dispatchAll(); + + // Now test with improveWakelockLatency flag true + clearInvocations(mWakeLockLog); + when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true); + + mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag", + "my.package.name", uid, pid, worksource, /* historyTag= */ null, + exceptingCallback); + mTestLooper.dispatchAll(); + verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", 1212, + PowerManager.PARTIAL_WAKE_LOCK, 1); + + // Release the wakelock + mNotifier.onWakeLockReleased(PowerManager.FULL_WAKE_LOCK, "wakelockTag2", + "my.package.name", uid, pid, worksource2, /* historyTag= */ null, + exceptingCallback); + mTestLooper.dispatchAll(); + verify(mWakeLockLog).onWakeLockReleased("wakelockTag2", 3131, 1); + } + + @Test public void test_notifierProcessesWorkSourceDeepCopy_OnWakelockChanging() throws RemoteException { when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true); @@ -845,7 +895,7 @@ public class NotifierTest { exceptingCallback); // No interaction because we expect that to happen in async - verifyZeroInteractions(mWakeLockLog, mBatteryStats, mAppOpsManager); + verifyNoMoreInteractions(mWakeLockLog, mBatteryStats, mAppOpsManager); // Progressing the looper, and validating all the interactions mTestLooper.dispatchAll(); @@ -944,15 +994,23 @@ public class NotifierTest { assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.PARTIAL_WAKE_LOCK), PowerManager.PARTIAL_WAKE_LOCK); assertEquals(mNotifier.getWakelockMonitorTypeForLogging( + PowerManager.DOZE_WAKE_LOCK), -1); + } + + @Test + public void getWakelockMonitorTypeForLogging_evaluateProximityLevel() { + // How proximity wakelock is evaluated depends on boolean configuration. Test both. + when(mResourcesSpy.getBoolean( + com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity)) + .thenReturn(false); + createNotifier(); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging( PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK), PowerManager.PARTIAL_WAKE_LOCK); - assertEquals(mNotifier.getWakelockMonitorTypeForLogging( - PowerManager.DOZE_WAKE_LOCK), -1); when(mResourcesSpy.getBoolean( com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity)) .thenReturn(true); - createNotifier(); assertEquals(mNotifier.getWakelockMonitorTypeForLogging( PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK), -1); @@ -1239,7 +1297,7 @@ public class NotifierTest { } @Override - public WakeLockLog getWakeLockLog(Context context) { + public @NonNull WakeLockLog getWakeLockLog(Context context) { return mWakeLockLog; } diff --git a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java index c1d7c7b4a4c2..534337ee9dbf 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java @@ -27,6 +27,8 @@ import android.content.pm.PackageManager; import android.os.PowerManager; import android.os.Process; +import com.android.server.power.WakeLockLog.TagData; + import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -62,8 +64,9 @@ public class WakeLockLogTest { @Test public void testAddTwoItems_withNoEventTimeSupplied() { final int tagDatabaseSize = 128; + final int tagStartingSize = 16; final int logSize = 20; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("TagPartial", 101, @@ -93,8 +96,9 @@ public class WakeLockLogTest { @Test public void testAddTwoItems() { final int tagDatabaseSize = 128; + final int tagStartingSize = 16; final int logSize = 20; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); log.onWakeLockAcquired("TagPartial", 101, @@ -117,8 +121,9 @@ public class WakeLockLogTest { @Test public void testAddTwoItemsWithTimeReset() { final int tagDatabaseSize = 128; + final int tagStartingSize = 16; final int logSize = 20; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L); @@ -136,9 +141,10 @@ public class WakeLockLogTest { @Test public void testAddTwoItemsWithTagOverwrite() { - final int tagDatabaseSize = 2; + final int tagDatabaseSize = 1; + final int tagStartingSize = 1; final int logSize = 20; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L); @@ -157,8 +163,9 @@ public class WakeLockLogTest { @Test public void testAddFourItemsWithRingBufferOverflow() { final int tagDatabaseSize = 6; + final int tagStartingSize = 2; final int logSize = 10; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); // Wake lock 1 acquired - log size = 3 @@ -206,8 +213,9 @@ public class WakeLockLogTest { @Test public void testAddItemWithBadTag() { final int tagDatabaseSize = 6; + final int tagStartingSize = 2; final int logSize = 10; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); // Bad tag means it wont get written @@ -224,8 +232,9 @@ public class WakeLockLogTest { @Test public void testAddItemWithReducedTagName() { final int tagDatabaseSize = 6; + final int tagStartingSize = 2; final int logSize = 10; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); log.onWakeLockAcquired("*job*/com.one.two.3hree/.one..Last", 101, @@ -242,9 +251,10 @@ public class WakeLockLogTest { @Test public void testAddAcquireAndReleaseWithRepeatTagName() { - final int tagDatabaseSize = 6; + final int tagDatabaseSize = 5; + final int tagStartingSize = 5; final int logSize = 10; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L); @@ -263,8 +273,9 @@ public class WakeLockLogTest { @Test public void testAddAcquireAndReleaseWithTimeTravel() { final int tagDatabaseSize = 6; + final int tagStartingSize = 2; final int logSize = 10; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK, 1100L); @@ -283,8 +294,9 @@ public class WakeLockLogTest { @Test public void testAddSystemWakelock() { final int tagDatabaseSize = 6; + final int tagStartingSize = 2; final int logSize = 10; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); log.onWakeLockAcquired("TagPartial", 101, @@ -302,8 +314,9 @@ public class WakeLockLogTest { @Test public void testAddItemWithNoPackageName() { final int tagDatabaseSize = 128; + final int tagStartingSize = 16; final int logSize = 20; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); when(mPackageManager.getPackagesForUid(101)).thenReturn(null); @@ -322,8 +335,9 @@ public class WakeLockLogTest { @Test public void testAddItemWithMultiplePackageNames() { final int tagDatabaseSize = 128; + final int tagStartingSize = 16; final int logSize = 20; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); when(mPackageManager.getPackagesForUid(101)).thenReturn( @@ -344,8 +358,9 @@ public class WakeLockLogTest { @Test public void testAddItemsWithRepeatOwnerUid_UsesCache() { final int tagDatabaseSize = 128; + final int tagStartingSize = 16; final int logSize = 20; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); log.onWakeLockAcquired("TagPartial", 101, @@ -375,8 +390,9 @@ public class WakeLockLogTest { @Test public void testAddItemsWithRepeatOwnerUid_SavedAcquisitions_UsesCache() { final int tagDatabaseSize = 128; + final int tagStartingSize = 16; final int logSize = 10; - TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); log.onWakeLockAcquired("TagPartial", 101, @@ -420,6 +436,34 @@ public class WakeLockLogTest { verify(mPackageManager, times(1)).getPackagesForUid(101); } + @Test + public void testTagDatabaseGrowsBeyondStartingSize() { + final int tagDatabaseSize = 3; + final int tagStartingSize = 1; + final int logSize = 10; + // start with size = 1 and max size + TestInjector injector = new TestInjector(tagDatabaseSize, tagStartingSize, logSize); + WakeLockLog.TagDatabase td = new WakeLockLog.TagDatabase(injector); + + // Add one + TagData data1 = td.findOrCreateTag("Tagname1", 1001, /* shouldCreate= */ true); + assertEquals(0, td.getTagIndex(data1)); + + // Check that it grows by adding 1 more + TagData data2 = td.findOrCreateTag("Tagname2", 1001, /* shouldCreate= */ true); + assertEquals(1, td.getTagIndex(data2)); + + // Lets add the last one to fill up the DB to maxSize + TagData data3 = td.findOrCreateTag("Tagname3", 1001, /* shouldCreate= */ true); + assertEquals(2, td.getTagIndex(data3)); + + // Adding a fourth one should replace the oldest one (Tagname1) + TagData data4 = td.findOrCreateTag("Tagname4", 1001, /* shouldCreate= */ true); + assertEquals(0, td.getTagIndex(data4)); + assertEquals(tagDatabaseSize, td.getTagIndex(data1)); + + } + private String dumpLog(WakeLockLog log, boolean includeTagDb) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); @@ -429,10 +473,12 @@ public class WakeLockLogTest { public static class TestInjector extends WakeLockLog.Injector { private final int mTagDatabaseSize; + private final int mTagStartingSize; private final int mLogSize; - public TestInjector(int tagDatabaseSize, int logSize) { + public TestInjector(int tagDatabaseSize, int tagStartingSize, int logSize) { mTagDatabaseSize = tagDatabaseSize; + mTagStartingSize = tagStartingSize; mLogSize = logSize; } @@ -442,6 +488,11 @@ public class WakeLockLogTest { } @Override + public int getTagDatabaseStartingSize() { + return mTagStartingSize; + } + + @Override public int getLogSize() { return mLogSize; } 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 7157d56db345..8a1d37b55255 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 @@ -574,7 +574,7 @@ public class BatteryUsageStatsProviderTest { MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); accumulateBatteryUsageStats(batteryStats, 10000000, 0); // Accumulate every 200 bytes of battery history - accumulateBatteryUsageStats(batteryStats, 200, 2); + accumulateBatteryUsageStats(batteryStats, 200, 1); accumulateBatteryUsageStats(batteryStats, 50, 5); // Accumulate on every invocation of accumulateBatteryUsageStats accumulateBatteryUsageStats(batteryStats, 0, 7); diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java index 879aa4893802..2fd316edf71a 100644 --- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java +++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java @@ -409,7 +409,7 @@ public class IntrusionDetectionServiceTest { final String TAG = "startTestService"; final CountDownLatch latch = new CountDownLatch(1); LocalIntrusionDetectionEventTransport transport = - new LocalIntrusionDetectionEventTransport(); + new LocalIntrusionDetectionEventTransport(mContext); ServiceConnection serviceConnection = new ServiceConnection() { // Called when connection with the service is established. diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java index f0012da44fa4..b0b781575cb3 100644 --- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java +++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java @@ -18,8 +18,13 @@ package com.android.coretests.apps.testapp; +import android.app.admin.SecurityLog; +import android.app.admin.SecurityLog.SecurityEvent; +import android.content.Context; +import android.content.Intent; import android.security.intrusiondetection.IntrusionDetectionEvent; import android.security.intrusiondetection.IntrusionDetectionEventTransport; +import android.util.Log; import java.util.ArrayList; import java.util.List; @@ -36,6 +41,44 @@ import java.util.List; public class LocalIntrusionDetectionEventTransport extends IntrusionDetectionEventTransport { private List<IntrusionDetectionEvent> mEvents = new ArrayList<>(); + private static final String ACTION_SECURITY_EVENT_RECEIVED = + "com.android.coretests.apps.testapp.ACTION_SECURITY_EVENT_RECEIVED"; + private static final String TAG = "LocalIntrusionDetectionEventTransport"; + private static final String TEST_SECURITY_EVENT_TAG = "test_security_event_tag"; + private static Context sContext; + + public LocalIntrusionDetectionEventTransport(Context context) { + sContext = context; + } + + // Broadcast an intent to the CTS test service to indicate that the security + // event was received. + private static void broadcastSecurityEventReceived() { + try { + Intent intent = new Intent(ACTION_SECURITY_EVENT_RECEIVED); + sContext.sendBroadcast(intent); + Log.i(TAG, "LIZ_TESTING: sent broadcast"); + } catch (Exception e) { + Log.e(TAG, "Exception sending broadcast", e); + } + } + + private static void checkIfSecurityEventReceivedFromCts(List<IntrusionDetectionEvent> events) { + // Loop through the events and check if any of them are the security event + // that uses the TEST_SECURITY_EVENT_TAG tag, which is set by the CTS test. + for (IntrusionDetectionEvent event : events) { + if (event.getType() == IntrusionDetectionEvent.SECURITY_EVENT) { + SecurityEvent securityEvent = event.getSecurityEvent(); + Object[] eventData = (Object[]) securityEvent.getData(); + if (securityEvent.getTag() == SecurityLog.TAG_KEY_GENERATED + && eventData[1].equals(TEST_SECURITY_EVENT_TAG)) { + broadcastSecurityEventReceived(); + return; + } + } + } + } + @Override public boolean initialize() { return true; @@ -43,6 +86,11 @@ public class LocalIntrusionDetectionEventTransport extends IntrusionDetectionEve @Override public boolean addData(List<IntrusionDetectionEvent> events) { + // Our CTS tests will generate a security event. In order to + // verify the event is received with the appropriate data, we will + // check the events locally and set a property value that can be + // read by the test. + checkIfSecurityEventReceivedFromCts(events); mEvents.addAll(events); return true; } @@ -55,4 +103,4 @@ public class LocalIntrusionDetectionEventTransport extends IntrusionDetectionEve public List<IntrusionDetectionEvent> getEvents() { return mEvents; } -}
\ No newline at end of file +} diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java index e4bf987402fd..9183a75580ff 100644 --- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java +++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java @@ -17,19 +17,20 @@ package com.android.coretests.apps.testapp; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.os.IBinder; -import android.os.Process; - -import com.android.internal.infra.AndroidFuture; - public class TestLoggingService extends Service { private static final String TAG = "TestLoggingService"; private LocalIntrusionDetectionEventTransport mLocalIntrusionDetectionEventTransport; - public TestLoggingService() { - mLocalIntrusionDetectionEventTransport = new LocalIntrusionDetectionEventTransport(); + @Override + public void onCreate() { + super.onCreate(); + + Context context = getApplicationContext(); + mLocalIntrusionDetectionEventTransport = new LocalIntrusionDetectionEventTransport(context); } // Binder given to clients. diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java index 5240f581fd9f..cc0d5e4710d2 100644 --- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java @@ -32,8 +32,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index ce3751abfed7..d254e9689048 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -23,10 +23,10 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyListOf; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -35,7 +35,6 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.Manifest.permission; @@ -243,7 +242,7 @@ public class NetworkScoreServiceTest { @Test public void testRequestScores_providerNotConnected() throws Exception { assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0])); - verifyZeroInteractions(mRecommendationProvider); + verifyNoMoreInteractions(mRecommendationProvider); } @Test @@ -328,8 +327,8 @@ public class NetworkScoreServiceTest { // updateScores should update both caches mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK}); - verify(mNetworkScoreCache).updateScores(anyListOf(ScoredNetwork.class)); - verify(mNetworkScoreCache2).updateScores(anyListOf(ScoredNetwork.class)); + verify(mNetworkScoreCache).updateScores(anyList()); + verify(mNetworkScoreCache2).updateScores(anyList()); mNetworkScoreService.unregisterNetworkScoreCache( NetworkKey.TYPE_WIFI, mNetworkScoreCache2); @@ -337,7 +336,7 @@ public class NetworkScoreServiceTest { // updateScores should only update the first cache since the 2nd has been unregistered mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK}); - verify(mNetworkScoreCache, times(2)).updateScores(anyListOf(ScoredNetwork.class)); + verify(mNetworkScoreCache, times(2)).updateScores(anyList()); mNetworkScoreService.unregisterNetworkScoreCache( NetworkKey.TYPE_WIFI, mNetworkScoreCache); @@ -604,7 +603,7 @@ public class NetworkScoreServiceTest { consumer.accept(mNetworkScoreCache, null /*cookie*/); verify(mNetworkScoreCache).updateScores(scoredNetworkList); - verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); + verifyNoMoreInteractions(mCurrentNetworkFilter, mScanResultsFilter); } @Test @@ -618,7 +617,7 @@ public class NetworkScoreServiceTest { consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE); verify(mNetworkScoreCache).updateScores(scoredNetworkList); - verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); + verifyNoMoreInteractions(mCurrentNetworkFilter, mScanResultsFilter); } @Test @@ -632,7 +631,7 @@ public class NetworkScoreServiceTest { consumer.accept(mNetworkScoreCache, -1 /*cookie*/); verify(mNetworkScoreCache).updateScores(scoredNetworkList); - verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); + verifyNoMoreInteractions(mCurrentNetworkFilter, mScanResultsFilter); } @Test @@ -646,7 +645,7 @@ public class NetworkScoreServiceTest { consumer.accept(mNetworkScoreCache, "not an int" /*cookie*/); verify(mNetworkScoreCache).updateScores(scoredNetworkList); - verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); + verifyNoMoreInteractions(mCurrentNetworkFilter, mScanResultsFilter); } @Test @@ -658,7 +657,7 @@ public class NetworkScoreServiceTest { consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE); - verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter); + verifyNoMoreInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter); } @Test @@ -676,7 +675,7 @@ public class NetworkScoreServiceTest { consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK); verify(mNetworkScoreCache).updateScores(filteredList); - verifyZeroInteractions(mScanResultsFilter); + verifyNoMoreInteractions(mScanResultsFilter); } @Test @@ -694,7 +693,7 @@ public class NetworkScoreServiceTest { consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS); verify(mNetworkScoreCache).updateScores(filteredList); - verifyZeroInteractions(mCurrentNetworkFilter); + verifyNoMoreInteractions(mCurrentNetworkFilter); } @Test diff --git a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java index 52428e8bc478..1621c5d41a3d 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java @@ -23,9 +23,9 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java index c18faef2c028..16df97b76c94 100644 --- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java @@ -18,7 +18,7 @@ package com.android.server; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.anyInt; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index 42b84bdc51e6..c7c8c5846bb1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -64,7 +64,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityService; @@ -838,7 +837,7 @@ public class AbstractAccessibilityServiceConnectionTest { // ...without secure layers included assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isFalse(); // No error sent to callback - verifyZeroInteractions(mMockCallback); + verifyNoMoreInteractions(mMockCallback); } @Test @@ -856,7 +855,7 @@ public class AbstractAccessibilityServiceConnectionTest { // ...with secure layers included assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isTrue(); // No error sent to callback - verifyZeroInteractions(mMockCallback); + verifyNoMoreInteractions(mMockCallback); } @Test @@ -889,7 +888,7 @@ public class AbstractAccessibilityServiceConnectionTest { // ...with secure layers included assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isTrue(); // No error sent to callback - verifyZeroInteractions(mMockCallback); + verifyNoMoreInteractions(mMockCallback); } private void takeScreenshotOfWindow(int windowFlags) throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 8253595a50d1..2ccd33648c3e 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -50,6 +50,7 @@ import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; @@ -66,6 +67,7 @@ import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.app.RemoteAction; @@ -77,10 +79,12 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.XmlResourceParser; import android.graphics.drawable.Icon; +import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.hardware.input.KeyGestureEvent; import android.net.Uri; @@ -114,6 +118,7 @@ import android.view.accessibility.IUserInitializationCompleteCallback; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.TestUtils; import com.android.internal.R; @@ -136,6 +141,8 @@ import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; +import com.google.common.truth.Correspondence; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -188,6 +195,8 @@ public class AccessibilityManagerServiceTest { DESCRIPTION, TEST_PENDING_INTENT); + private static final int FAKE_SYSTEMUI_UID = 1000; + private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1; private static final String TARGET_MAGNIFICATION = MAGNIFICATION_CONTROLLER_NAME; private static final ComponentName TARGET_ALWAYS_ON_A11Y_SERVICE = @@ -207,11 +216,12 @@ public class AccessibilityManagerServiceTest { @Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport; @Mock private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController; @Mock private PackageManager mMockPackageManager; + @Mock + private PackageManagerInternal mMockPackageManagerInternal; @Mock private WindowManagerInternal mMockWindowManagerService; @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy; @Mock private SystemActionPerformer mMockSystemActionPerformer; @Mock private AccessibilityWindowManager mMockA11yWindowManager; - @Mock private AccessibilityDisplayListener mMockA11yDisplayListener; @Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal; @Mock private UserManagerInternal mMockUserManagerInternal; @Mock private IBinder mMockBinder; @@ -234,6 +244,7 @@ public class AccessibilityManagerServiceTest { private TestableLooper mTestableLooper; private Handler mHandler; private FakePermissionEnforcer mFakePermissionEnforcer; + private TestDisplayManagerWrapper mTestDisplayManagerWrapper; @Before public void setUp() throws Exception { @@ -246,6 +257,7 @@ public class AccessibilityManagerServiceTest { LocalServices.removeServiceForTest(UserManagerInternal.class); LocalServices.removeServiceForTest(StatusBarManagerInternal.class); LocalServices.removeServiceForTest(PermissionEnforcer.class); + LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService( WindowManagerInternal.class, mMockWindowManagerService); LocalServices.addService( @@ -256,6 +268,12 @@ public class AccessibilityManagerServiceTest { mInputFilter = mock(FakeInputFilter.class); mTestableContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager); + when(mMockPackageManagerInternal.getSystemUiServiceComponent()).thenReturn( + new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService")); + when(mMockPackageManagerInternal.getPackageUid(eq("com.android.systemui"), anyLong(), + anyInt())).thenReturn(FAKE_SYSTEMUI_UID); + LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); + when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn( mMockMagnificationConnectionManager); when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn( @@ -273,15 +291,9 @@ public class AccessibilityManagerServiceTest { eq(UserHandle.USER_CURRENT))) .thenReturn(mTestableContext.getUserId()); - final ArrayList<Display> displays = new ArrayList<>(); - final Display defaultDisplay = new Display(DisplayManagerGlobal.getInstance(), - Display.DEFAULT_DISPLAY, new DisplayInfo(), - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); - final Display testDisplay = new Display(DisplayManagerGlobal.getInstance(), TEST_DISPLAY, - new DisplayInfo(), DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); - displays.add(defaultDisplay); - displays.add(testDisplay); - when(mMockA11yDisplayListener.getValidDisplayList()).thenReturn(displays); + mTestDisplayManagerWrapper = new TestDisplayManagerWrapper(mTestableContext); + mTestDisplayManagerWrapper.mDisplays = createFakeDisplayList(Display.TYPE_INTERNAL, + Display.TYPE_EXTERNAL); mA11yms = new AccessibilityManagerService( mTestableContext, @@ -290,7 +302,7 @@ public class AccessibilityManagerServiceTest { mMockSecurityPolicy, mMockSystemActionPerformer, mMockA11yWindowManager, - mMockA11yDisplayListener, + mTestDisplayManagerWrapper, mMockMagnificationController, mInputFilter, mProxyManager, @@ -2309,6 +2321,73 @@ public class AccessibilityManagerServiceTest { mA11yms.getCurrentUserIdLocked())).isEmpty(); } + @Test + public void displayListReturnsDisplays() { + mTestDisplayManagerWrapper.mDisplays = createFakeDisplayList( + Display.TYPE_INTERNAL, + Display.TYPE_EXTERNAL, + Display.TYPE_WIFI, + Display.TYPE_OVERLAY, + Display.TYPE_VIRTUAL + ); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // In #setUp() we already have TYPE_INTERNAL and TYPE_EXTERNAL. Call the rest. + for (int i = 2; i < mTestDisplayManagerWrapper.mDisplays.size(); i++) { + mTestDisplayManagerWrapper.mRegisteredListener.onDisplayAdded( + mTestDisplayManagerWrapper.mDisplays.get(i).getDisplayId()); + } + }); + + List<Display> displays = mA11yms.getValidDisplayList(); + assertThat(displays).hasSize(5); + assertThat(displays) + .comparingElementsUsing( + Correspondence.transforming(Display::getType, "has a type of")) + .containsExactly(Display.TYPE_INTERNAL, + Display.TYPE_EXTERNAL, + Display.TYPE_WIFI, + Display.TYPE_OVERLAY, + Display.TYPE_VIRTUAL); + } + + @Test + public void displayListReturnsDisplays_excludesVirtualPrivate() { + // Add a private virtual display whose uid is different from systemui. + final List<Display> displays = createFakeDisplayList(Display.TYPE_INTERNAL, + Display.TYPE_EXTERNAL); + displays.add(createFakeVirtualPrivateDisplay(/* displayId= */ 2, FAKE_SYSTEMUI_UID + 100)); + mTestDisplayManagerWrapper.mDisplays = displays; + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mTestDisplayManagerWrapper.mRegisteredListener.onDisplayAdded(2); + }); + + List<Display> validDisplays = mA11yms.getValidDisplayList(); + assertThat(validDisplays).hasSize(2); + assertThat(validDisplays) + .comparingElementsUsing( + Correspondence.transforming(Display::getType, "has a type of")) + .doesNotContain(Display.TYPE_VIRTUAL); + } + + @Test + public void displayListReturnsDisplays_includesVirtualSystemUIPrivate() { + // Add a private virtual display whose uid is systemui. + final List<Display> displays = createFakeDisplayList(Display.TYPE_INTERNAL, + Display.TYPE_EXTERNAL); + displays.add(createFakeVirtualPrivateDisplay(/* displayId= */ 2, FAKE_SYSTEMUI_UID)); + mTestDisplayManagerWrapper.mDisplays = displays; + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mTestDisplayManagerWrapper.mRegisteredListener.onDisplayAdded(2); + }); + + List<Display> validDisplays = mA11yms.getValidDisplayList(); + assertThat(validDisplays).hasSize(3); + assertThat(validDisplays) + .comparingElementsUsing( + Correspondence.transforming(Display::getType, "has a type of")) + .contains(Display.TYPE_VIRTUAL); + } + private Set<String> readStringsFromSetting(String setting) { final Set<String> result = new ArraySet<>(); mA11yms.readColonDelimitedSettingToSet( @@ -2422,6 +2501,27 @@ public class AccessibilityManagerServiceTest { }); } + private static List<Display> createFakeDisplayList(int... types) { + final ArrayList<Display> displays = new ArrayList<>(); + for (int i = 0; i < types.length; i++) { + final DisplayInfo info = new DisplayInfo(); + info.type = types[i]; + final Display display = new Display(DisplayManagerGlobal.getInstance(), + i, info, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + displays.add(display); + } + return displays; + } + + private static Display createFakeVirtualPrivateDisplay(int displayId, int uid) { + final DisplayInfo info = new DisplayInfo(); + info.type = Display.TYPE_VIRTUAL; + info.flags |= Display.FLAG_PRIVATE; + info.ownerUid = uid; + return new Display(DisplayManagerGlobal.getInstance(), + displayId, info, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + } + public static class FakeInputFilter extends AccessibilityInputFilter { FakeInputFilter(Context context, AccessibilityManagerService service) { @@ -2506,4 +2606,35 @@ public class AccessibilityManagerServiceTest { Set<String> setting = readStringsFromSetting(ShortcutUtils.convertToKey(shortcutType)); assertThat(setting).containsExactlyElementsIn(value); } + + private static class TestDisplayManagerWrapper extends + AccessibilityDisplayListener.DisplayManagerWrapper { + List<Display> mDisplays; + DisplayManager.DisplayListener mRegisteredListener; + + TestDisplayManagerWrapper(Context context) { + super(context); + } + + @Override + public Display[] getDisplays() { + return mDisplays.toArray(new Display[0]); + } + + @Override + public Display getDisplay(int displayId) { + for (final Display display : mDisplays) { + if (display.getDisplayId() == displayId) { + return display; + } + } + return null; + } + + @Override + public void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, + @Nullable Handler handler) { + mRegisteredListener = listener; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java index c16f3d3fa1ab..1792201a304a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java @@ -29,9 +29,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -129,7 +129,7 @@ public class ActionReplacingCallbackTest { mMockServiceCallback, mMockReplacerConnection, INTERACTION_ID, INTERROGATING_PID, INTERROGATING_TID); verify(mMockReplacerConnection).findAccessibilityNodeInfoByAccessibilityId( - eq(AccessibilityNodeInfo.ROOT_NODE_ID), (Region) anyObject(), + eq(AccessibilityNodeInfo.ROOT_NODE_ID), (Region) any(), mInteractionIdCaptor.capture(), eq(mActionReplacingCallback), eq(0), eq(INTERROGATING_PID), eq(INTERROGATING_TID), nullable(MagnificationSpec.class), nullable(float[].class), eq(null)); @@ -246,9 +246,9 @@ public class ActionReplacingCallbackTest { throws RemoteException { doThrow(RemoteException.class).when(mMockReplacerConnection) .findAccessibilityNodeInfoByAccessibilityId(eq(AccessibilityNodeInfo.ROOT_NODE_ID), - (Region) anyObject(), anyInt(), (ActionReplacingCallback) anyObject(), + (Region) any(), anyInt(), (ActionReplacingCallback) any(), eq(0), eq(INTERROGATING_PID), eq(INTERROGATING_TID), - (MagnificationSpec) anyObject(), nullable(float[].class), eq(null)); + (MagnificationSpec) any(), nullable(float[].class), eq(null)); ActionReplacingCallback actionReplacingCallback = new ActionReplacingCallback( mMockServiceCallback, mMockReplacerConnection, INTERACTION_ID, INTERROGATING_PID, INTERROGATING_TID); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java index 96ae102e53f3..d0dc2cbb0f86 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java @@ -24,7 +24,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.accessibilityservice.FingerprintGestureController; @@ -86,7 +86,7 @@ public class FingerprintGestureControllerTest { mMockFingerprintGestureCallback); mFingerprintGestureController.onGestureDetectionActiveChanged(true); mFingerprintGestureController.onGestureDetectionActiveChanged(false); - verifyZeroInteractions(mMockFingerprintGestureCallback); + verifyNoMoreInteractions(mMockFingerprintGestureCallback); } @Test @@ -118,7 +118,7 @@ public class FingerprintGestureControllerTest { mFingerprintGestureController.onGestureDetectionActiveChanged(true); mFingerprintGestureController.onGestureDetectionActiveChanged(false); assertFalse(messageCapturingHandler.hasMessages()); - verifyZeroInteractions(mMockFingerprintGestureCallback); + verifyNoMoreInteractions(mMockFingerprintGestureCallback); messageCapturingHandler.removeAllMessages(); } @@ -135,7 +135,7 @@ public class FingerprintGestureControllerTest { mFingerprintGestureController.unregisterFingerprintGestureCallback( mMockFingerprintGestureCallback); mFingerprintGestureController.onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN); - verifyZeroInteractions(mMockFingerprintGestureCallback); + verifyNoMoreInteractions(mMockFingerprintGestureCallback); } @Test @@ -159,7 +159,7 @@ public class FingerprintGestureControllerTest { mMockFingerprintGestureCallback); mFingerprintGestureController.onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN); assertFalse(messageCapturingHandler.hasMessages()); - verifyZeroInteractions(mMockFingerprintGestureCallback); + verifyNoMoreInteractions(mMockFingerprintGestureCallback); messageCapturingHandler.removeAllMessages(); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java index 30d00ad716e6..f5a708d71ba3 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java @@ -18,8 +18,8 @@ package com.android.server.accessibility; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; 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 63c572af37b2..3565244d90b3 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java @@ -36,6 +36,8 @@ 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; @@ -67,6 +69,8 @@ import java.util.concurrent.Executor; public class HearingDevicePhoneCallNotificationControllerTest { @Rule public MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private static final String TEST_ADDRESS = "55:66:77:88:99:AA"; @@ -118,6 +122,7 @@ public class HearingDevicePhoneCallNotificationControllerTest { AudioManager.DEVICE_OUT_BLE_HEADSET); when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn( new AudioDeviceInfo[]{hapDeviceInfo}); + when(mAudioManager.getCommunicationDevice()).thenReturn(hapDeviceInfo); when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(hapDeviceInfo)); mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK); @@ -132,6 +137,7 @@ public class HearingDevicePhoneCallNotificationControllerTest { AudioManager.DEVICE_OUT_BLUETOOTH_A2DP); when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn( new AudioDeviceInfo[]{a2dpDeviceInfo}); + when(mAudioManager.getCommunicationDevice()).thenReturn(a2dpDeviceInfo); when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(a2dpDeviceInfo)); mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK); @@ -146,6 +152,7 @@ public class HearingDevicePhoneCallNotificationControllerTest { AudioManager.DEVICE_OUT_BLE_HEADSET); when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn( new AudioDeviceInfo[]{hapDeviceInfo}); + when(mAudioManager.getCommunicationDevice()).thenReturn(hapDeviceInfo); when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(hapDeviceInfo)); mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK); @@ -155,6 +162,51 @@ public class HearingDevicePhoneCallNotificationControllerTest { eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH)); } + @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); + AudioDeviceInfo a2dpDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS, + AudioManager.DEVICE_OUT_BLUETOOTH_A2DP); + when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn( + new AudioDeviceInfo[]{a2dpDeviceInfo}); + when(mAudioManager.getCommunicationDevice()).thenReturn(a2dpDeviceInfo); + + mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK); + mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_IDLE); + + verify(mAudioManager).addOnCommunicationDeviceChangedListener(any(Executor.class), + listenerCaptor.capture()); + verify(mAudioManager).removeOnCommunicationDeviceChangedListener( + eq(listenerCaptor.getValue())); + } + + + @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); + AudioDeviceInfo hapDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS, + AudioManager.DEVICE_OUT_BLE_HEADSET); + AudioDeviceInfo a2dpDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS, + AudioManager.DEVICE_OUT_BLUETOOTH_A2DP); + when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn( + new AudioDeviceInfo[]{a2dpDeviceInfo}); + when(mAudioManager.getCommunicationDevice()).thenReturn(a2dpDeviceInfo); + + mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK); + verify(mAudioManager).addOnCommunicationDeviceChangedListener(any(Executor.class), + listenerCaptor.capture()); + when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn( + new AudioDeviceInfo[]{hapDeviceInfo}); + listenerCaptor.getValue().onCommunicationDeviceChanged(hapDeviceInfo); + + verify(mNotificationManager).notify( + eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH), any()); + } + private AudioDeviceInfo createAudioDeviceInfo(String address, int type) { AudioDevicePort audioDevicePort = mock(AudioDevicePort.class); doReturn(type).when(audioDevicePort).type(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java index ebb73e877db4..186f7425b189 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java @@ -20,14 +20,14 @@ import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.argThat; @@ -96,12 +96,12 @@ public class KeyEventDispatcherTest { mLock, powerManager, mMessageCapturingHandler); mKeyEventFilter1 = mock(KeyEventFilter.class); - when(mKeyEventFilter1.onKeyEvent((KeyEvent) anyObject(), + when(mKeyEventFilter1.onKeyEvent((KeyEvent) any(), mFilter1SequenceCaptor.capture().intValue())) .thenReturn(true); mKeyEventFilter2 = mock(KeyEventFilter.class); - when(mKeyEventFilter2.onKeyEvent((KeyEvent) anyObject(), + when(mKeyEventFilter2.onKeyEvent((KeyEvent) any(), mFilter2SequenceCaptor.capture().intValue())) .thenReturn(true); } @@ -122,7 +122,7 @@ public class KeyEventDispatcherTest { @Test public void testNotifyKeyEvent_boundServiceDoesntProcessEvents_shouldReturnFalse() { KeyEventFilter keyEventFilter = mock(KeyEventFilter.class); - when(keyEventFilter.onKeyEvent((KeyEvent) anyObject(), anyInt())).thenReturn(false); + when(keyEventFilter.onKeyEvent((KeyEvent) any(), anyInt())).thenReturn(false); assertFalse(mKeyEventDispatcher .notifyKeyEventLocked(mKeyEvent, 0, Arrays.asList(keyEventFilter))); assertFalse(isTimeoutPending(mMessageCapturingHandler)); @@ -159,7 +159,7 @@ public class KeyEventDispatcherTest { mFilter1SequenceCaptor.getValue()); assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verifyZeroInteractions(mMockPowerManagerService); + verifyNoMoreInteractions(mMockPowerManagerService); assertFalse(isTimeoutPending(mMessageCapturingHandler)); } @@ -189,7 +189,7 @@ public class KeyEventDispatcherTest { mFilter2SequenceCaptor.getValue()); assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verifyZeroInteractions(mMockPowerManagerService); + verifyNoMoreInteractions(mMockPowerManagerService); assertFalse(isTimeoutPending(mMessageCapturingHandler)); } @@ -261,7 +261,7 @@ public class KeyEventDispatcherTest { mKeyEventDispatcher.setOnKeyEventResult(mKeyEventFilter2, false, mFilter2SequenceCaptor.getValue()); assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verifyZeroInteractions(mMockPowerManagerService); + verifyNoMoreInteractions(mMockPowerManagerService); assertFalse(isTimeoutPending(mMessageCapturingHandler)); } @@ -278,7 +278,7 @@ public class KeyEventDispatcherTest { mKeyEventDispatcher.handleMessage(getTimedMessage(mMessageCapturingHandler, 0)); assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verifyZeroInteractions(mMockPowerManagerService); + verifyNoMoreInteractions(mMockPowerManagerService); } @Test @@ -293,7 +293,7 @@ public class KeyEventDispatcherTest { mKeyEventDispatcher.handleMessage(getTimedMessage(mMessageCapturingHandler, 0)); assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verifyZeroInteractions(mMockPowerManagerService); + verifyNoMoreInteractions(mMockPowerManagerService); } @Test @@ -327,7 +327,7 @@ public class KeyEventDispatcherTest { mFilter1SequenceCaptor.getValue()); assertFalse(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verifyZeroInteractions(mMockPowerManagerService); + verifyNoMoreInteractions(mMockPowerManagerService); } @Test @@ -344,7 +344,7 @@ public class KeyEventDispatcherTest { mKeyEventDispatcher.handleMessage(getTimedMessage(mMessageCapturingHandler, 0)); assertFalse(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT)); - verifyZeroInteractions(mMockPowerManagerService); + verifyNoMoreInteractions(mMockPowerManagerService); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java index c62cae5e9b6e..e3515125397a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java @@ -21,8 +21,8 @@ import static junit.framework.Assert.assertTrue; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyObject; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -94,7 +94,7 @@ public class KeyboardInterceptorTest { when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()), argThat(matchesKeyEvent(event)), eq(0))).thenReturn(-1L); mInterceptor.onKeyEvent(event, 0); - verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt()); + verify(mMockAms, times(0)).notifyKeyEvent(any(), anyInt()); assertFalse(mHandler.hasMessages()); } @@ -106,7 +106,7 @@ public class KeyboardInterceptorTest { mInterceptor.onKeyEvent(event, 0); assertTrue(mHandler.hasMessages()); - verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt()); + verify(mMockAms, times(0)).notifyKeyEvent(any(), anyInt()); when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()), argThat(matchesKeyEvent(event)), eq(0))).thenReturn(0L); @@ -123,13 +123,13 @@ public class KeyboardInterceptorTest { mInterceptor.onKeyEvent(event, 0); assertTrue(mHandler.hasMessages()); - verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt()); + verify(mMockAms, times(0)).notifyKeyEvent(any(), anyInt()); when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()), argThat(matchesKeyEvent(event)), eq(0))).thenReturn(-1L); mHandler.sendAllMessages(); - verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt()); + verify(mMockAms, times(0)).notifyKeyEvent(any(), anyInt()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java index c75cfe67ab79..367f2d143acc 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java @@ -29,15 +29,14 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.hamcrest.MockitoHamcrest.argThat; import android.accessibilityservice.GestureDescription.GestureStep; @@ -223,7 +222,7 @@ public class MotionEventInjectorTest { verifyNoMoreInteractions(next); reset(next); - verifyZeroInteractions(mServiceInterface); + verifyNoMoreInteractions(mServiceInterface); mMessageCapturingHandler.sendOneMessage(); // Send a motion event verify(next).onMotionEvent(argThat(allOf(mIsLineEnd, hasRightDownTime)), diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java index 2be43c6f21a5..99c922ca30c4 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -74,12 +75,13 @@ public class AutoclickControllerTest { private static class MotionEventCaptor extends BaseEventStreamTransformation { public MotionEvent downEvent; - + public int eventCount = 0; @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downEvent = event; + eventCount++; break; } } @@ -813,6 +815,57 @@ public class AutoclickControllerTest { @Test @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void sendClick_clickType_scroll_showsScrollPanelOnlyOnce() { + MotionEventCaptor motionEventCaptor = new MotionEventCaptor(); + mController.setNext(motionEventCaptor); + + injectFakeMouseActionHoverMoveEvent(); + // Set delay to zero so click is scheduled to run immediately. + mController.mClickScheduler.updateDelay(0); + + // Set click type to scroll. + mController.clickPanelController.handleAutoclickTypeChange( + AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL); + + // Mock the scroll panel to verify interactions. + AutoclickScrollPanel mockScrollPanel = mock(AutoclickScrollPanel.class); + mController.mAutoclickScrollPanel = mockScrollPanel; + + // First hover move event. + MotionEvent hoverMove1 = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove1.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove1, hoverMove1, /* policyFlags= */ 0); + mTestableLooper.processAllMessages(); + + // Verify scroll panel is shown once. + verify(mockScrollPanel, times(1)).show(); + assertThat(motionEventCaptor.downEvent).isNull(); + + // Second significant hover move event to trigger another autoclick. + MotionEvent hoverMove2 = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 200, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 100f, + /* y= */ 100f, + /* metaState= */ 0); + hoverMove2.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove2, hoverMove2, /* policyFlags= */ 0); + mTestableLooper.processAllMessages(); + + // Verify scroll panel is still only shown once (not called again). + verify(mockScrollPanel, times(1)).show(); + assertThat(motionEventCaptor.downEvent).isNull(); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) public void hoverOnAutoclickPanel_rightClickType_forceTriggerLeftClick() { MotionEventCaptor motionEventCaptor = new MotionEventCaptor(); mController.setNext(motionEventCaptor); @@ -870,6 +923,41 @@ public class AutoclickControllerTest { mController.onKeyEvent(keyEvent, /* policyFlags= */ 0); } + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void sendClick_clickType_doubleclick_triggerClickTwice() { + MotionEventCaptor motionEventCaptor = new MotionEventCaptor(); + mController.setNext(motionEventCaptor); + + injectFakeMouseActionHoverMoveEvent(); + // Set delay to zero so click is scheduled to run immediately. + mController.mClickScheduler.updateDelay(0); + + // Set click type to double click. + mController.clickPanelController.handleAutoclickTypeChange( + AutoclickTypePanel.AUTOCLICK_TYPE_DOUBLE_CLICK); + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + mTestableLooper.processAllMessages(); + + // Verify left click sent. + assertThat(motionEventCaptor.downEvent).isNotNull(); + assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo( + MotionEvent.BUTTON_PRIMARY); + assertThat(motionEventCaptor.eventCount).isEqualTo(2); + } + private MotionEvent getFakeMotionHoverMoveEvent() { return MotionEvent.obtain( /* downTime= */ 0, diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 4ea5fcfd79c8..31cdd6c3a658 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -43,8 +43,8 @@ import static org.junit.Assume.assumeTrue; import static org.mockito.AdditionalMatchers.gt; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java index 0c92abce7254..9a241043c52f 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java @@ -19,11 +19,11 @@ package com.android.server.accounts; import static android.database.sqlite.SQLiteDatabase.deleteDatabase; import static org.mockito.ArgumentMatchers.contains; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java index 61ac74cc3490..514c07827ae9 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyString; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 1627f683cd3e..6d656609c759 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -58,10 +58,10 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java index 3475c8f5444d..20a95e90b668 100644 --- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java @@ -34,7 +34,6 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import android.attention.AttentionManagerInternal.AttentionCallbackInternal; import android.attention.AttentionManagerInternal.ProximityUpdateCallbackInternal; @@ -196,7 +195,7 @@ public class AttentionManagerServiceTest { @Test public void testUnregisterProximityUpdates_noCrashWhenNoCallbackIsRegistered() { mSpyAttentionManager.onStopProximityUpdates(mMockProximityUpdateCallbackInternal); - verifyZeroInteractions(mMockProximityUpdateCallbackInternal); + verifyNoMoreInteractions(mMockProximityUpdateCallbackInternal); } @Test diff --git a/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java index d5638e9346f6..d89cf7bb5db5 100644 --- a/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java @@ -16,7 +16,7 @@ package com.android.server.audio; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyObject; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -107,13 +107,13 @@ public class MusicFxHelperTest { List<ResolveInfo> list, int bind, int broadcast, String packageName, int audioSession, int uid) { doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); - doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(anyObject(), anyInt()); + doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(any(), anyInt()); if (list != null && list.size() != 0) { try { doReturn(uid).when(mMockPackageManager) - .getPackageUidAsUser(eq(packageName), anyObject(), anyInt()); + .getPackageUidAsUser(eq(packageName), any(), anyInt()); doReturn(mMusicFxUid).when(mMockPackageManager) - .getPackageUidAsUser(eq(mMusicFxPkgName), anyObject(), anyInt()); + .getPackageUidAsUser(eq(mMusicFxPkgName), any(), anyInt()); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "NameNotFoundException: " + e); } @@ -123,8 +123,8 @@ public class MusicFxHelperTest { packageName, audioSession); mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent); verify(mMockContext, times(bind)) - .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject()); - verify(mMockContext, times(broadcast)).sendBroadcastAsUser(anyObject(), anyObject()); + .bindServiceAsUser(any(), any(), anyInt(), any()); + verify(mMockContext, times(broadcast)).sendBroadcastAsUser(any(), any()); } /** @@ -136,13 +136,13 @@ public class MusicFxHelperTest { List<ResolveInfo> list, int unBind, int broadcast, String packageName, int audioSession, int uid) { doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); - doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(anyObject(), anyInt()); + doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(any(), anyInt()); if (list != null && list.size() != 0) { try { doReturn(uid).when(mMockPackageManager) - .getPackageUidAsUser(eq(packageName), anyObject(), anyInt()); + .getPackageUidAsUser(eq(packageName), any(), anyInt()); doReturn(mMusicFxUid).when(mMockPackageManager) - .getPackageUidAsUser(eq(mMusicFxPkgName), anyObject(), anyInt()); + .getPackageUidAsUser(eq(mMusicFxPkgName), any(), anyInt()); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "NameNotFoundException: " + e); } @@ -151,8 +151,8 @@ public class MusicFxHelperTest { Intent intent = newIntent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION, packageName, audioSession); mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent); - verify(mMockContext, times(unBind)).unbindService(anyObject()); - verify(mMockContext, times(broadcast)).sendBroadcastAsUser(anyObject(), anyObject()); + verify(mMockContext, times(unBind)).unbindService(any()); + verify(mMockContext, times(broadcast)).sendBroadcastAsUser(any(), any()); } /** @@ -160,8 +160,8 @@ public class MusicFxHelperTest { */ private void sendMessage(int msgId, int uid, int unBinds, int broadcasts) { mMusicFxHelper.handleMessage(Message.obtain(null, msgId, uid /* arg1 */, 0 /* arg2 */)); - verify(mMockContext, times(broadcasts)).sendBroadcastAsUser(anyObject(), anyObject()); - verify(mMockContext, times(unBinds)).unbindService(anyObject()); + verify(mMockContext, times(broadcasts)).sendBroadcastAsUser(any(), any()); + verify(mMockContext, times(unBinds)).unbindService(any()); } /** @@ -209,15 +209,15 @@ public class MusicFxHelperTest { intent.setPackage(mTestPkg1); mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent); verify(mMockContext, times(0)) - .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject()); - verify(mMockContext, times(0)).sendBroadcastAsUser(anyObject(), anyObject()); + .bindServiceAsUser(any(), any(), anyInt(), any()); + verify(mMockContext, times(0)).sendBroadcastAsUser(any(), any()); intent = newIntent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION, null, 1); intent.setPackage(mTestPkg2); mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent); verify(mMockContext, times(0)) - .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject()); - verify(mMockContext, times(0)).sendBroadcastAsUser(anyObject(), anyObject()); + .bindServiceAsUser(any(), any(), anyInt(), any()); + verify(mMockContext, times(0)).sendBroadcastAsUser(any(), any()); } /** diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index d8a616214f96..a5d6a19d1491 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -42,7 +42,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -451,7 +450,7 @@ public class AuthSessionTest { assertEquals(startFingerprintNow ? BiometricSensor.STATE_AUTHENTICATING : BiometricSensor.STATE_COOKIE_RETURNED, session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState()); - verify(mBiometricContext).updateContext((OperationContextExt) anyObject(), + verify(mBiometricContext).updateContext((OperationContextExt) any(), eq(session.isCrypto())); // start fingerprint sensor if it was delayed @@ -554,7 +553,7 @@ public class AuthSessionTest { session.onDialogDismissed(DISMISSED_REASON_BIOMETRIC_CONFIRMED, null); verify(mBiometricFrameworkStatsLogger, times(1)).authenticate( - (OperationContextExt) anyObject(), + (OperationContextExt) any(), eq(BiometricsProtoEnums.MODALITY_FACE), eq(BiometricsProtoEnums.ACTION_UNKNOWN), eq(BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT), @@ -582,10 +581,10 @@ public class AuthSessionTest { session.onDialogDismissed(DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED, null); verify(mBiometricFrameworkStatsLogger, never()).authenticate( - anyObject(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyLong(), anyInt(), + any(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyLong(), anyInt(), anyBoolean(), anyInt(), eq(-1f)); verify(mBiometricFrameworkStatsLogger, never()).error( - anyObject(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyLong(), anyInt(), + any(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyLong(), anyInt(), anyInt(), anyInt()); } @@ -605,7 +604,7 @@ public class AuthSessionTest { session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null); verify(mBiometricFrameworkStatsLogger, times(1)).error( - (OperationContextExt) anyObject(), + (OperationContextExt) any(), eq(BiometricsProtoEnums.MODALITY_FACE), eq(BiometricsProtoEnums.ACTION_AUTHENTICATE), eq(BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT), @@ -632,7 +631,7 @@ public class AuthSessionTest { session.onDialogDismissed(DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS, null); verify(mBiometricFrameworkStatsLogger, times(1)).error( - (OperationContextExt) anyObject(), + (OperationContextExt) any(), eq(BiometricsProtoEnums.MODALITY_FACE), eq(BiometricsProtoEnums.ACTION_AUTHENTICATE), eq(BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT), diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java index 7a770338a34b..f4e87177e072 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java @@ -24,7 +24,7 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.same; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.hardware.biometrics.BiometricFaceConstants; @@ -172,7 +172,7 @@ public class FaceDetectClientTest { client.onInteractionDetected(); client.stopHalOperation(); - verifyZeroInteractions(mVibrator); + verifyNoMoreInteractions(mVibrator); } private FaceDetectClient createClient() throws RemoteException { diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java index 67fc564fa778..2e07cd8ae698 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java @@ -22,18 +22,25 @@ import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.sensor.IVirtualSensorCallback; import android.companion.virtual.sensor.VirtualSensor; +import android.companion.virtual.sensor.VirtualSensorAdditionalInfo; import android.companion.virtual.sensor.VirtualSensorConfig; import android.companion.virtual.sensor.VirtualSensorEvent; import android.content.AttributionSource; import android.hardware.Sensor; +import android.hardware.SensorAdditionalInfo; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -49,6 +56,7 @@ import com.google.common.collect.Iterables; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -65,6 +73,9 @@ public class SensorControllerTest { private static final int VIRTUAL_SENSOR_TYPE = Sensor.TYPE_ACCELEROMETER; + private static final float[] ADDITIONAL_INFO_VALUES_1 = new float[] {1.2f, 3.4f}; + private static final float[] ADDITIONAL_INFO_VALUES_2 = new float[] {5.6f, 7.8f}; + @Mock private SensorManagerInternal mSensorManagerInternalMock; @Mock @@ -155,6 +166,53 @@ public class SensorControllerTest { } @Test + public void sendSensorAdditionalInfo_invalidToken_throwsException() throws Exception { + SensorController sensorController = doCreateSensorSuccessfully(); + + final VirtualSensorAdditionalInfo info = + new VirtualSensorAdditionalInfo.Builder(SensorAdditionalInfo.TYPE_UNTRACKED_DELAY) + .addValues(ADDITIONAL_INFO_VALUES_1) + .addValues(ADDITIONAL_INFO_VALUES_2) + .build(); + assertThrows( + IllegalArgumentException.class, + () -> sensorController.sendSensorAdditionalInfo( + new Binder("invalidSensorToken"), info)); + } + + @Test + public void sendSensorAdditionalInfo_success() throws Exception { + SensorController sensorController = doCreateSensorSuccessfully(); + + clearInvocations(mSensorManagerInternalMock); + when(mSensorManagerInternalMock.sendSensorAdditionalInfo( + anyInt(), anyInt(), anyInt(), anyLong(), any())) + .thenReturn(true); + IBinder token = Iterables.getOnlyElement(sensorController.getSensorDescriptors().keySet()); + + final VirtualSensorAdditionalInfo info = + new VirtualSensorAdditionalInfo.Builder(SensorAdditionalInfo.TYPE_UNTRACKED_DELAY) + .addValues(ADDITIONAL_INFO_VALUES_1) + .addValues(ADDITIONAL_INFO_VALUES_2) + .build(); + sensorController.sendSensorAdditionalInfo(token, info); + + InOrder inOrder = inOrder(mSensorManagerInternalMock); + inOrder.verify(mSensorManagerInternalMock).sendSensorAdditionalInfo( + eq(SENSOR_HANDLE), eq(SensorAdditionalInfo.TYPE_FRAME_BEGIN), + /*serial=*/ eq(0), /* timestamp= */ anyLong(), /*values=*/ isNull()); + inOrder.verify(mSensorManagerInternalMock).sendSensorAdditionalInfo( + eq(SENSOR_HANDLE), eq(SensorAdditionalInfo.TYPE_UNTRACKED_DELAY), + /*serial=*/ eq(0), /* timestamp= */ anyLong(), eq(ADDITIONAL_INFO_VALUES_1)); + inOrder.verify(mSensorManagerInternalMock).sendSensorAdditionalInfo( + eq(SENSOR_HANDLE), eq(SensorAdditionalInfo.TYPE_UNTRACKED_DELAY), + /*serial=*/ eq(1), /* timestamp= */ anyLong(), eq(ADDITIONAL_INFO_VALUES_2)); + inOrder.verify(mSensorManagerInternalMock).sendSensorAdditionalInfo( + eq(SENSOR_HANDLE), eq(SensorAdditionalInfo.TYPE_FRAME_END), + /*serial=*/ eq(0), /* timestamp= */ anyLong(), /*values=*/ isNull()); + } + + @Test public void close_unregistersSensors() throws Exception { SensorController sensorController = doCreateSensorSuccessfully(); diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java index b445226be60f..4fa75b9823e0 100644 --- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java @@ -23,7 +23,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.annotation.NonNull; @@ -132,8 +132,8 @@ public class ContentCaptureManagerServiceTest { assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0); assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0); assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); - verifyZeroInteractions(mMockContentProtectionAllowlistManager); - verifyZeroInteractions(mMockContentProtectionConsentManager); + verifyNoMoreInteractions(mMockContentProtectionAllowlistManager); + verifyNoMoreInteractions(mMockContentProtectionConsentManager); } @Test @@ -147,7 +147,7 @@ public class ContentCaptureManagerServiceTest { assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); verify(mMockContentProtectionAllowlistManager).start(anyLong()); verify(mMockContentProtectionAllowlistManager, never()).stop(); - verifyZeroInteractions(mMockContentProtectionConsentManager); + verifyNoMoreInteractions(mMockContentProtectionConsentManager); } @Test @@ -157,8 +157,8 @@ public class ContentCaptureManagerServiceTest { assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0); assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0); assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); - verifyZeroInteractions(mMockContentProtectionAllowlistManager); - verifyZeroInteractions(mMockContentProtectionConsentManager); + verifyNoMoreInteractions(mMockContentProtectionAllowlistManager); + verifyNoMoreInteractions(mMockContentProtectionConsentManager); } @Test @@ -172,7 +172,7 @@ public class ContentCaptureManagerServiceTest { assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); verify(mMockContentProtectionAllowlistManager).start(anyLong()); verify(mMockContentProtectionAllowlistManager, never()).stop(); - verifyZeroInteractions(mMockContentProtectionConsentManager); + verifyNoMoreInteractions(mMockContentProtectionConsentManager); } @Test @@ -187,7 +187,7 @@ public class ContentCaptureManagerServiceTest { assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); verify(mMockContentProtectionAllowlistManager).start(anyLong()); verify(mMockContentProtectionAllowlistManager, never()).stop(); - verifyZeroInteractions(mMockContentProtectionConsentManager); + verifyNoMoreInteractions(mMockContentProtectionConsentManager); } @Test @@ -203,7 +203,7 @@ public class ContentCaptureManagerServiceTest { assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); verify(mMockContentProtectionAllowlistManager).start(anyLong()); verify(mMockContentProtectionAllowlistManager).stop(); - verifyZeroInteractions(mMockContentProtectionConsentManager); + verifyNoMoreInteractions(mMockContentProtectionConsentManager); } @Test @@ -216,8 +216,8 @@ public class ContentCaptureManagerServiceTest { assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0); assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0); assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); - verifyZeroInteractions(mMockContentProtectionAllowlistManager); - verifyZeroInteractions(mMockContentProtectionConsentManager); + verifyNoMoreInteractions(mMockContentProtectionAllowlistManager); + verifyNoMoreInteractions(mMockContentProtectionConsentManager); } @Test @@ -230,8 +230,8 @@ public class ContentCaptureManagerServiceTest { assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0); assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0); assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); - verifyZeroInteractions(mMockContentProtectionAllowlistManager); - verifyZeroInteractions(mMockContentProtectionConsentManager); + verifyNoMoreInteractions(mMockContentProtectionAllowlistManager); + verifyNoMoreInteractions(mMockContentProtectionConsentManager); } @Test @@ -248,7 +248,7 @@ public class ContentCaptureManagerServiceTest { assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); verify(mMockContentProtectionAllowlistManager).start(anyLong()); verify(mMockContentProtectionAllowlistManager).stop(); - verifyZeroInteractions(mMockContentProtectionConsentManager); + verifyNoMoreInteractions(mMockContentProtectionConsentManager); } @Test @@ -265,7 +265,7 @@ public class ContentCaptureManagerServiceTest { assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); verify(mMockContentProtectionAllowlistManager).start(anyLong()); verify(mMockContentProtectionAllowlistManager).stop(); - verifyZeroInteractions(mMockContentProtectionConsentManager); + verifyNoMoreInteractions(mMockContentProtectionConsentManager); } @Test @@ -513,7 +513,7 @@ public class ContentCaptureManagerServiceTest { assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0); assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0); - verifyZeroInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); } @Test @@ -528,7 +528,7 @@ public class ContentCaptureManagerServiceTest { assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1); assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0); - verifyZeroInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); } @Test diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java index 195ab68427b9..9d37b99c5bf4 100644 --- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java @@ -24,7 +24,7 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.os.Handler; @@ -98,10 +98,10 @@ public class ContentProtectionAllowlistManagerTest { @Test public void constructor() { assertThat(mHandler.hasMessagesOrCallbacks()).isFalse(); - verifyZeroInteractions(mMockContentCaptureManagerService); - verifyZeroInteractions(mMockPackageMonitor); - verifyZeroInteractions(mMockRemoteContentProtectionService); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockContentCaptureManagerService); + verifyNoMoreInteractions(mMockPackageMonitor); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -110,10 +110,10 @@ public class ContentProtectionAllowlistManagerTest { mTestLooper.dispatchAll(); assertThat(mHandler.hasMessagesOrCallbacks()).isTrue(); - verifyZeroInteractions(mMockContentCaptureManagerService); - verifyZeroInteractions(mMockPackageMonitor); - verifyZeroInteractions(mMockRemoteContentProtectionService); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockContentCaptureManagerService); + verifyNoMoreInteractions(mMockPackageMonitor); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -126,8 +126,8 @@ public class ContentProtectionAllowlistManagerTest { verify(mMockContentCaptureManagerService).createRemoteContentProtectionService(); verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler)); verify(mMockPackageMonitor, never()).unregister(); - verifyZeroInteractions(mMockRemoteContentProtectionService); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -142,8 +142,8 @@ public class ContentProtectionAllowlistManagerTest { verify(mMockContentCaptureManagerService).createRemoteContentProtectionService(); verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler)); verify(mMockPackageMonitor, never()).unregister(); - verifyZeroInteractions(mMockRemoteContentProtectionService); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -153,11 +153,11 @@ public class ContentProtectionAllowlistManagerTest { mContentProtectionAllowlistManager.stop(); assertThat(mHandler.hasMessagesOrCallbacks()).isFalse(); - verifyZeroInteractions(mMockContentCaptureManagerService); + verifyNoMoreInteractions(mMockContentCaptureManagerService); verify(mMockPackageMonitor, never()).register(any(), any(), any()); verify(mMockPackageMonitor).unregister(); - verifyZeroInteractions(mMockRemoteContentProtectionService); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -169,11 +169,11 @@ public class ContentProtectionAllowlistManagerTest { mContentProtectionAllowlistManager.stop(); assertThat(mHandler.hasMessagesOrCallbacks()).isFalse(); - verifyZeroInteractions(mMockContentCaptureManagerService); + verifyNoMoreInteractions(mMockContentCaptureManagerService); verify(mMockPackageMonitor, never()).register(any(), any(), any()); verify(mMockPackageMonitor).unregister(); - verifyZeroInteractions(mMockRemoteContentProtectionService); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -188,8 +188,8 @@ public class ContentProtectionAllowlistManagerTest { verify(mMockContentCaptureManagerService).createRemoteContentProtectionService(); verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler)); verify(mMockPackageMonitor).unregister(); - verifyZeroInteractions(mMockRemoteContentProtectionService); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -205,8 +205,8 @@ public class ContentProtectionAllowlistManagerTest { assertThat(mHandler.hasMessagesOrCallbacks()).isFalse(); verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler)); verify(mMockPackageMonitor).unregister(); - verifyZeroInteractions(mMockRemoteContentProtectionService); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -223,8 +223,8 @@ public class ContentProtectionAllowlistManagerTest { assertThat(mHandler.hasMessagesOrCallbacks()).isFalse(); verify(mMockPackageMonitor, times(2)).register(any(), eq(UserHandle.ALL), eq(mHandler)); verify(mMockPackageMonitor).unregister(); - verifyZeroInteractions(mMockRemoteContentProtectionService); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -232,10 +232,10 @@ public class ContentProtectionAllowlistManagerTest { boolean actual = mContentProtectionAllowlistManager.isAllowed(FIRST_PACKAGE_NAME); assertThat(actual).isFalse(); - verifyZeroInteractions(mMockContentCaptureManagerService); - verifyZeroInteractions(mMockPackageMonitor); - verifyZeroInteractions(mMockRemoteContentProtectionService); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockContentCaptureManagerService); + verifyNoMoreInteractions(mMockPackageMonitor); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -248,9 +248,9 @@ public class ContentProtectionAllowlistManagerTest { boolean actual = manager.isAllowed(SECOND_PACKAGE_NAME); assertThat(actual).isFalse(); - verifyZeroInteractions(mMockContentCaptureManagerService); - verifyZeroInteractions(mMockPackageMonitor); - verifyZeroInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockContentCaptureManagerService); + verifyNoMoreInteractions(mMockPackageMonitor); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); } @Test @@ -263,9 +263,9 @@ public class ContentProtectionAllowlistManagerTest { boolean actual = manager.isAllowed(FIRST_PACKAGE_NAME); assertThat(actual).isTrue(); - verifyZeroInteractions(mMockContentCaptureManagerService); - verifyZeroInteractions(mMockPackageMonitor); - verifyZeroInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockContentCaptureManagerService); + verifyNoMoreInteractions(mMockPackageMonitor); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); } @Test @@ -276,8 +276,8 @@ public class ContentProtectionAllowlistManagerTest { manager.mPackageMonitor.onSomePackagesChanged(); verify(mMockContentCaptureManagerService).createRemoteContentProtectionService(); - verifyZeroInteractions(mMockRemoteContentProtectionService); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -291,7 +291,7 @@ public class ContentProtectionAllowlistManagerTest { verify(mMockRemoteContentProtectionService) .onUpdateAllowlistRequest(mMockAllowlistCallback); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -309,7 +309,7 @@ public class ContentProtectionAllowlistManagerTest { // Does not rethrow verify(mMockRemoteContentProtectionService) .onUpdateAllowlistRequest(mMockAllowlistCallback); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -321,8 +321,8 @@ public class ContentProtectionAllowlistManagerTest { manager.mPackageMonitor.onSomePackagesChanged(); verify(mMockContentCaptureManagerService, times(2)).createRemoteContentProtectionService(); - verifyZeroInteractions(mMockRemoteContentProtectionService); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockRemoteContentProtectionService); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -338,7 +338,7 @@ public class ContentProtectionAllowlistManagerTest { verify(mMockContentCaptureManagerService).createRemoteContentProtectionService(); verify(mMockRemoteContentProtectionService) .onUpdateAllowlistRequest(mMockAllowlistCallback); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test @@ -355,7 +355,7 @@ public class ContentProtectionAllowlistManagerTest { verify(mMockContentCaptureManagerService, times(2)).createRemoteContentProtectionService(); verify(mMockRemoteContentProtectionService, times(2)) .onUpdateAllowlistRequest(mMockAllowlistCallback); - verifyZeroInteractions(mMockAllowlistCallback); + verifyNoMoreInteractions(mMockAllowlistCallback); } @Test diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java index b012aaaed3bf..cd36a1889d2f 100644 --- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java @@ -27,7 +27,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.admin.DevicePolicyCache; @@ -112,8 +112,8 @@ public class ContentProtectionConsentManagerTest { boolean actual = manager.isConsentGranted(TEST_USER_ID); assertThat(actual).isFalse(); - verifyZeroInteractions(mMockDevicePolicyManagerInternal); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyManagerInternal); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -125,8 +125,8 @@ public class ContentProtectionConsentManagerTest { boolean actual = manager.isConsentGranted(TEST_USER_ID); assertThat(actual).isFalse(); - verifyZeroInteractions(mMockDevicePolicyManagerInternal); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyManagerInternal); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -138,8 +138,8 @@ public class ContentProtectionConsentManagerTest { boolean actual = manager.isConsentGranted(TEST_USER_ID); assertThat(actual).isFalse(); - verifyZeroInteractions(mMockDevicePolicyManagerInternal); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyManagerInternal); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -152,7 +152,7 @@ public class ContentProtectionConsentManagerTest { assertThat(actual).isTrue(); verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -166,7 +166,7 @@ public class ContentProtectionConsentManagerTest { boolean actual = manager.isConsentGranted(TEST_USER_ID); assertThat(actual).isFalse(); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -179,7 +179,7 @@ public class ContentProtectionConsentManagerTest { assertThat(actual).isFalse(); verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -192,7 +192,7 @@ public class ContentProtectionConsentManagerTest { assertThat(actual).isTrue(); verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -289,8 +289,8 @@ public class ContentProtectionConsentManagerTest { boolean actual = manager.isConsentGranted(TEST_USER_ID); assertThat(actual).isFalse(); - verifyZeroInteractions(mMockDevicePolicyManagerInternal); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyManagerInternal); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -302,8 +302,8 @@ public class ContentProtectionConsentManagerTest { boolean actual = manager.isConsentGranted(TEST_USER_ID); assertThat(actual).isFalse(); - verifyZeroInteractions(mMockDevicePolicyManagerInternal); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyManagerInternal); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -316,7 +316,7 @@ public class ContentProtectionConsentManagerTest { assertThat(actual).isTrue(); verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -339,7 +339,7 @@ public class ContentProtectionConsentManagerTest { assertThat(thirdActual).isTrue(); verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -362,7 +362,7 @@ public class ContentProtectionConsentManagerTest { assertThat(thirdActual).isTrue(); verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -385,7 +385,7 @@ public class ContentProtectionConsentManagerTest { assertThat(thirdActual).isTrue(); verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyCache); } @Test @@ -408,7 +408,7 @@ public class ContentProtectionConsentManagerTest { assertThat(thirdActual).isTrue(); verify(mMockDevicePolicyManagerInternal, times(3)).isUserOrganizationManaged(TEST_USER_ID); - verifyZeroInteractions(mMockDevicePolicyCache); + verifyNoMoreInteractions(mMockDevicePolicyCache); } private void putGlobalSettings(String key, int value) { diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java index 6a7e2865fb32..563a6799e9e5 100644 --- a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java @@ -20,7 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.annotation.NonNull; @@ -87,7 +87,7 @@ public class RemoteContentProtectionServiceTest { @Test public void doesNotAutoConnect() { assertThat(mConnectCallCount).isEqualTo(0); - verifyZeroInteractions(mMockContentProtectionService); + verifyNoMoreInteractions(mMockContentProtectionService); } @Test @@ -124,7 +124,7 @@ public class RemoteContentProtectionServiceTest { mRemoteContentProtectionService.onServiceConnectionStatusChanged( mMockContentProtectionService, /* isConnected= */ true); - verifyZeroInteractions(mMockContentProtectionService); + verifyNoMoreInteractions(mMockContentProtectionService); assertThat(mConnectCallCount).isEqualTo(0); } @@ -133,7 +133,7 @@ public class RemoteContentProtectionServiceTest { mRemoteContentProtectionService.onServiceConnectionStatusChanged( mMockContentProtectionService, /* isConnected= */ false); - verifyZeroInteractions(mMockContentProtectionService); + verifyNoMoreInteractions(mMockContentProtectionService); assertThat(mConnectCallCount).isEqualTo(0); } diff --git a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java index 0f3f27aa2896..9e98af32d6e5 100644 --- a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java @@ -21,7 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; @@ -207,7 +207,7 @@ public class ProviderRegistryGetSessionTest { ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, "unsupportedKey", providerPendingIntentResponse); - verifyZeroInteractions(mGetRequestSession); + verifyNoMoreInteractions(mGetRequestSession); } @Test @@ -216,7 +216,7 @@ public class ProviderRegistryGetSessionTest { ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, null); - verifyZeroInteractions(mGetRequestSession); + verifyNoMoreInteractions(mGetRequestSession); } @Test diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java index 5582e13cbb4d..d5fe0bf9cfdf 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java @@ -23,9 +23,9 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 01bcc2584fe1..c50c62323212 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -88,7 +88,6 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.argThat; import static org.testng.Assert.assertThrows; @@ -5305,7 +5304,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // both the user restriction and the policy were set by the PO. verify(getServices().userManagerInternal).removeUserEvenWhenDisallowed( MANAGED_PROFILE_USER_ID); - verifyZeroInteractions(getServices().recoverySystem); + verifyNoMoreInteractions(getServices().recoverySystem); } @Test @@ -5339,7 +5338,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // not wiped. verify(getServices().userManagerInternal, never()) .removeUserEvenWhenDisallowed(anyInt()); - verifyZeroInteractions(getServices().recoverySystem); + verifyNoMoreInteractions(getServices().recoverySystem); } @Test @@ -5380,7 +5379,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); // DISALLOW_FACTORY_RESET was set by the system, not the DO, so the device is not wiped. - verifyZeroInteractions(getServices().recoverySystem); + verifyNoMoreInteractions(getServices().recoverySystem); verify(getServices().userManagerInternal, never()) .removeUserEvenWhenDisallowed(anyInt()); } @@ -7535,7 +7534,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().notificationManager, never()) .notify(anyInt(), any(Notification.class)); // Apps shouldn't be suspended. - verifyZeroInteractions(getServices().ipackageManager); + verifyNoMoreInteractions(getServices().ipackageManager); clearInvocations(getServices().alarmManager); setUserUnlocked(CALLER_USER_HANDLE, false); @@ -7548,7 +7547,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().notificationManager, never()) .notify(anyInt(), any(Notification.class)); // Apps shouldn't be suspended. - verifyZeroInteractions(getServices().ipackageManager); + verifyNoMoreInteractions(getServices().ipackageManager); clearInvocations(getServices().alarmManager); // Pretend the alarm went off. @@ -7561,7 +7560,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().notificationManager, times(1)) .notifyAsUser(any(), anyInt(), any(), any()); // Apps shouldn't be suspended yet. - verifyZeroInteractions(getServices().ipackageManager); + verifyNoMoreInteractions(getServices().ipackageManager); clearInvocations(getServices().alarmManager); clearInvocations(getServices().notificationManager); @@ -7570,7 +7569,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { sendBroadcastWithUser(dpms, ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE); // Verify the alarm was not set. - verifyZeroInteractions(getServices().alarmManager); + verifyNoMoreInteractions(getServices().alarmManager); // Now the user should see a notification about suspended apps. verify(getServices().notificationManager, times(1)) .notifyAsUser(any(), anyInt(), any(), any()); @@ -8754,7 +8753,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { sendBroadcastWithUser(dpms, Intent.ACTION_MANAGED_PROFILE_REMOVED, CALLER_USER_HANDLE); // Verify that EuiccManager was not called to delete the subscription. - verifyZeroInteractions(getServices().euiccManager); + verifyNoMoreInteractions(getServices().euiccManager); } private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java index 03aaeb7e0db8..b52eb0a6857a 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java @@ -21,7 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java index 0a696ef44897..b01895a527c6 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java @@ -27,7 +27,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import android.content.ComponentName; diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java index e20f1e7065d4..a39f07105eab 100644 --- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java @@ -31,7 +31,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.app.ActivityManagerInternal; import android.content.Context; @@ -226,7 +226,7 @@ public class SystemAppUpdateTrackerTest { assertTrue(!mSystemAppUpdateTracker.getUpdatedApps().contains(DEFAULT_PACKAGE_NAME_2)); // getApplicationLocales should be never be invoked if not a system app. - verifyZeroInteractions(mMockActivityTaskManager); + verifyNoMoreInteractions(mMockActivityTaskManager); // Broadcast should be never sent if not a system app. verify(mMockContext, never()).sendBroadcastAsUser(any(), any()); // It shouldn't write to the file if not a system app. @@ -244,7 +244,7 @@ public class SystemAppUpdateTrackerTest { Binder.getCallingUid()); // getApplicationLocales should be never be invoked if not installer is not present. - verifyZeroInteractions(mMockActivityTaskManager); + verifyNoMoreInteractions(mMockActivityTaskManager); // Broadcast should be never sent if installer is not present. verify(mMockContext, never()).sendBroadcastAsUser(any(), any()); // It shouldn't write to file if no installer present. diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java index 565a9b6c1c44..4d2dcf65bfeb 100644 --- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java +++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java @@ -80,6 +80,10 @@ public class ContextHubEndpointTest { new HubMessage.Builder(SAMPLE_MESSAGE_TYPE, new byte[] {1, 2, 3, 4, 5}) .setResponseRequired(true) .build(); + private static final HubMessage SAMPLE_UNRELIABLE_MESSAGE = + new HubMessage.Builder(SAMPLE_MESSAGE_TYPE, new byte[] {1, 2, 3, 4, 5}) + .setResponseRequired(false) + .build(); private ContextHubClientManager mClientManager; private ContextHubEndpointManager mEndpointManager; @@ -260,6 +264,24 @@ public class ContextHubEndpointTest { unregisterExampleEndpoint(endpoint); } + @Test + public void testUnreliableMessage() throws RemoteException { + IContextHubEndpoint endpoint = registerExampleEndpoint(); + int sessionId = openTestSession(endpoint); + + mEndpointManager.onMessageReceived(sessionId, SAMPLE_UNRELIABLE_MESSAGE); + ArgumentCaptor<HubMessage> messageCaptor = ArgumentCaptor.forClass(HubMessage.class); + verify(mMockCallback).onMessageReceived(eq(sessionId), messageCaptor.capture()); + assertThat(messageCaptor.getValue()).isEqualTo(SAMPLE_UNRELIABLE_MESSAGE); + + // Confirm we can send another message + mEndpointManager.onMessageReceived(sessionId, SAMPLE_UNRELIABLE_MESSAGE); + verify(mMockCallback, times(2)).onMessageReceived(eq(sessionId), messageCaptor.capture()); + assertThat(messageCaptor.getValue()).isEqualTo(SAMPLE_UNRELIABLE_MESSAGE); + + unregisterExampleEndpoint(endpoint); + } + /** A helper method to create a session and validates reliable message sending. */ private void testMessageTransactionInternal( IContextHubEndpoint endpoint, boolean deliverMessageStatus) throws RemoteException { diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index a58a9cd2a28f..4a05ea68daec 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -48,7 +48,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; @@ -488,7 +488,7 @@ public class MediaProjectionManagerServiceTest { projection.stop(StopReason.STOP_UNKNOWN); - verifyZeroInteractions(mMediaProjectionMetricsLogger); + verifyNoMoreInteractions(mMediaProjectionMetricsLogger); } @Test diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java index 570256bf43e6..a46fc2206cf5 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java @@ -35,7 +35,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index da14e451d656..03ba3b655fa8 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -25,11 +25,11 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index bbc2cb23b269..242ebc550d16 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -59,10 +59,10 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.waitOnMainThread; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index f5690b77d2fe..46c532bc40fa 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -23,9 +23,9 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.parceled; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java index 19c26f0b60ab..88395a4889c4 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java @@ -21,10 +21,10 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.isNull; -import static org.mockito.Matchers.notNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java index ee1bf38a6b24..7ea33ad1184b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java @@ -18,8 +18,8 @@ package com.android.server.pm; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java index d55f96782084..2ed27048c288 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.pm.ApplicationInfo; @@ -273,7 +273,7 @@ public class DynamicCodeLoggerTests { assertThat(mMessagesForUid).isEmpty(); assertThat(mWriteTriggered).isFalse(); - verifyZeroInteractions(mPM); + verifyNoMoreInteractions(mPM); } @Test diff --git a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java index 3551af83aa47..5e5be12665d6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java @@ -17,10 +17,10 @@ package com.android.server.pm.permission; import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java index 2a4c3fdf8f43..64f88192a0c5 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java @@ -21,8 +21,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; diff --git a/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java b/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java index 3cdf1098622a..1b93d4a36ec7 100644 --- a/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java +++ b/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java @@ -48,10 +48,10 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java index b647b99df894..4a97b4670289 100644 --- a/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java @@ -18,11 +18,11 @@ package com.android.server.storage; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.isNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 9a7abd43aab6..15a60dc600e5 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -62,9 +62,9 @@ import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.intThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.intThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; diff --git a/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java b/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java index 4eb2474da740..770dfe3666b0 100644 --- a/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java +++ b/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java @@ -20,8 +20,8 @@ import static com.android.server.utils.PriorityDump.dump; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertSame; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.same; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.verify; import android.platform.test.annotations.Presubmit; diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java index bf99b6af2345..3dcd1d7b7da5 100644 --- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java @@ -43,7 +43,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; -import org.mockito.Matchers; +import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import java.util.concurrent.CountDownLatch; @@ -340,7 +340,7 @@ public class WebViewUpdateServiceTest { runWebViewBootPreparationOnMainSync(); Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged( - Matchers.anyObject()); + ArgumentMatchers.any()); WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); @@ -378,7 +378,7 @@ public class WebViewUpdateServiceTest { runWebViewBootPreparationOnMainSync(); Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged( - Matchers.anyObject()); + ArgumentMatchers.any()); WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); @@ -822,7 +822,7 @@ public class WebViewUpdateServiceTest { checkPreparationPhasesForPackage(firstPackage, 1); Mockito.verify(mTestSystemImpl, Mockito.never()).killPackageDependents( - Mockito.anyObject()); + Mockito.any()); } @Test diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java index 4c0361d30d67..cdc28a13fc8e 100644 --- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java +++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java @@ -25,10 +25,10 @@ import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.junit.Assert.assertNotEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyList; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; diff --git a/services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java b/services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java index c8afb78bc12f..630a7e47fa48 100644 --- a/services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java +++ b/services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.AlarmManager; @@ -126,7 +126,7 @@ public final class GnssTimeUpdateServiceTest { locationListener.onLocationChanged(location); verify(mMockLocationManager).removeUpdates(locationListener); - verifyZeroInteractions(mMockTimeDetectorInternal); + verifyNoMoreInteractions(mMockTimeDetectorInternal); verify(mMockAlarmManager).set( eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(), @@ -150,7 +150,7 @@ public final class GnssTimeUpdateServiceTest { // Verify the service returned to location listening. verify(mMockLocationManager).requestLocationUpdates(any(), any(), any(), any()); - verifyZeroInteractions(mMockAlarmManager, mMockTimeDetectorInternal); + verifyNoMoreInteractions(mMockAlarmManager, mMockTimeDetectorInternal); } // Tests what happens when a call is made to startGnssListeningInternal() when service is @@ -172,7 +172,7 @@ public final class GnssTimeUpdateServiceTest { // listening again. verify(mMockAlarmManager).cancel(alarmListenerCaptor.getValue()); verify(mMockLocationManager).requestLocationUpdates(any(), any(), any(), any()); - verifyZeroInteractions(mMockTimeDetectorInternal); + verifyNoMoreInteractions(mMockTimeDetectorInternal); } private void advanceServiceToSleepingState( @@ -190,7 +190,7 @@ public final class GnssTimeUpdateServiceTest { any(), any(), any(), locationListenerCaptor.capture()); LocationListener locationListener = locationListenerCaptor.getValue(); Location location = new Location(LocationManager.GPS_PROVIDER); - verifyZeroInteractions(mMockAlarmManager, mMockTimeDetectorInternal); + verifyNoMoreInteractions(mMockAlarmManager, mMockTimeDetectorInternal); locationListener.onLocationChanged(location); diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index 66d7611a29c6..d34d74d80882 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -11,13 +11,8 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -android_test { - name: "FrameworksUiServicesTests", - - // Include test java files - srcs: [ - "src/**/*.java", - ], +java_defaults { + name: "FrameworksUiServicesTests-defaults", static_libs: [ "compatibility-device-util-axt-minus-dexmaker", @@ -95,12 +90,72 @@ android_test { javacflags: ["-parameters"], } -test_module_config { - name: "FrameworksUiServicesTests_notification", - base: "FrameworksUiServicesTests", - test_suites: [ - "automotive-tests", - "device-tests", +// Utility files used by multiple tests +filegroup { + name: "shared-srcs", + srcs: [ + "src/android/app/ExampleActivity.java", + "src/android/app/NotificationSystemUtil.java", + "src/com/android/frameworks/tests/uiservices/DummyProvider.java", + "src/com/android/internal/logging/InstanceIdSequenceFake.java", + "src/com/android/server/UiServiceTestCase.java", + "src/com/android/server/notification/ZenChangeOrigin.java", + "src/com/android/server/notification/ZenModeEventLoggerFake.java", + ], + visibility: ["//visibility:private"], +} + +filegroup { + name: "notification-srcs", + srcs: [ + "src/**/Notification*.java", + "src/com/android/server/notification/*.java", + ], + visibility: ["//visibility:private"], +} + +filegroup { + name: "notification-zen-srcs", + srcs: [ + "src/android/app/NotificationManagerZenTest.java", + "src/com/android/server/notification/Zen*Test.java", + ], + visibility: ["//visibility:private"], +} + +android_test { + name: "FrameworksUiServicesTests", + + // Include test java files but not the notification & zen ones which are separated + srcs: [ + "src/**/*.java", + ], + + exclude_srcs: [ + ":notification-srcs", + ":notification-zen-srcs", + ], + + defaults: ["FrameworksUiServicesTests-defaults"], +} + +android_test { + name: "FrameworksUiServicesNotificationTests", + srcs: [ + ":notification-srcs", + ":shared-srcs", + ], + exclude_srcs: [":notification-zen-srcs"], + defaults: ["FrameworksUiServicesTests-defaults"], + test_config: "notification-tests.xml", +} + +android_test { + name: "FrameworksUiServicesZenTests", + srcs: [ + ":notification-zen-srcs", + ":shared-srcs", ], - exclude_annotations: ["androidx.test.filters.LargeTest"], + defaults: ["FrameworksUiServicesTests-defaults"], + test_config: "notification-zen-tests.xml", } diff --git a/services/tests/uiservicestests/AndroidTest.xml b/services/tests/uiservicestests/AndroidTest.xml index 11e8f090a8fa..93c8c72630f1 100644 --- a/services/tests/uiservicestests/AndroidTest.xml +++ b/services/tests/uiservicestests/AndroidTest.xml @@ -15,6 +15,7 @@ --> <configuration description="Runs Frameworks UI Services Tests."> <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="FrameworksUiServicesTests.apk" /> </target_preparer> diff --git a/services/tests/uiservicestests/notification-tests.xml b/services/tests/uiservicestests/notification-tests.xml new file mode 100644 index 000000000000..acfd844efe26 --- /dev/null +++ b/services/tests/uiservicestests/notification-tests.xml @@ -0,0 +1,30 @@ +<?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. +--> +<configuration description="Runs Frameworks UI Services Tests (notifications subset)."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="FrameworksUiServicesNotificationTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="FrameworksUiServicesNotificationTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.tests.uiservices" /> + <option name="runner" value="android.testing.TestableInstrumentation" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/services/tests/uiservicestests/notification-zen-tests.xml b/services/tests/uiservicestests/notification-zen-tests.xml new file mode 100644 index 000000000000..01d8aab83d59 --- /dev/null +++ b/services/tests/uiservicestests/notification-zen-tests.xml @@ -0,0 +1,30 @@ +<?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. +--> +<configuration description="Runs Frameworks UI Services Tests (zen mode subset)."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="FrameworksUiServicesZenTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="FrameworksUiServicesZenTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.tests.uiservices" /> + <option name="runner" value="android.testing.TestableInstrumentation" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index 9930c9f07ed8..7b1ce446da7c 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -62,7 +62,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; @@ -992,7 +991,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { () -> mService.requestProjection(mBinder, PROJECTION_TYPE_NONE, PACKAGE_NAME)); verify(mContext, never()).enforceCallingPermission( eq(Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION), any()); - verifyZeroInteractions(mBinder); + verifyNoMoreInteractions(mBinder); assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes()); } @@ -1008,7 +1007,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { () -> mService.requestProjection(mBinder, multipleProjectionTypes, PACKAGE_NAME)); verify(mContext, never()).enforceCallingPermission( eq(Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION), any()); - verifyZeroInteractions(mBinder); + verifyNoMoreInteractions(mBinder); assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes()); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java index 6b989cb0aaee..b33233107766 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java @@ -38,7 +38,6 @@ import android.os.IInterface; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.Condition; -import com.android.internal.R; import com.android.server.UiServiceTestCase; import org.junit.Before; @@ -47,8 +46,6 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.List; - public class ConditionProvidersTest extends UiServiceTestCase { private ConditionProviders mProviders; @@ -172,15 +169,4 @@ public class ConditionProvidersTest extends UiServiceTestCase { assertTrue(mProviders.getApproved(userId, true).isEmpty()); } - - @Test - public void getDefaultDndAccessPackages_returnsPackages() { - mContext.getOrCreateTestableResources().addOverride( - R.string.config_defaultDndAccessPackages, - "com.example.a:com.example.b::::com.example.c"); - - List<String> packages = ConditionProviders.getDefaultDndAccessPackages(mContext); - - assertThat(packages).containsExactly("com.example.a", "com.example.b", "com.example.c"); - } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java index 5ce9a3e8d4d4..8023bdd08927 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java @@ -36,7 +36,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.app.KeyguardManager; @@ -184,7 +183,7 @@ public class DefaultDeviceEffectsApplierTest { mApplier.apply(noEffects, ORIGIN_USER_IN_SYSTEMUI); verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false)); - verifyZeroInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager); + verifyNoMoreInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager); } @Test @@ -252,8 +251,8 @@ public class DefaultDeviceEffectsApplierTest { // Wallpaper dimming was undone, Grayscale was applied, nothing else was touched. verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f)); verify(mColorDisplayManager).setSaturationLevel(eq(0)); - verifyZeroInteractions(mPowerManager); - verifyZeroInteractions(mUiModeManager); + verifyNoMoreInteractions(mPowerManager); + verifyNoMoreInteractions(mUiModeManager); } @Test @@ -269,7 +268,7 @@ public class DefaultDeviceEffectsApplierTest { ORIGIN_APP); // Effect was not yet applied, but a broadcast receiver was registered. - verifyZeroInteractions(mUiModeManager); + verifyNoMoreInteractions(mUiModeManager); verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), intentFilterCaptor.capture(), anyInt()); assertThat(intentFilterCaptor.getValue().getAction(0)).isEqualTo(Intent.ACTION_SCREEN_OFF); @@ -337,7 +336,7 @@ public class DefaultDeviceEffectsApplierTest { origin.value()); // Effect was not applied, will be on next screen-off. - verifyZeroInteractions(mUiModeManager); + verifyNoMoreInteractions(mUiModeManager); verify(mContext).registerReceiver(any(), argThat(filter -> Intent.ACTION_SCREEN_OFF.equals(filter.getAction(0))), anyInt()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java index a9759c8a61f3..11143653a75d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -50,9 +50,9 @@ import static junit.framework.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -60,7 +60,7 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.annotation.SuppressLint; @@ -313,7 +313,7 @@ public class GroupHelperTest extends UiServiceTestCase { getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false); } - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -327,7 +327,7 @@ public class GroupHelperTest extends UiServiceTestCase { } mGroupHelper.onNotificationPosted( getNotificationRecord(pkg2, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM), false); - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -340,7 +340,7 @@ public class GroupHelperTest extends UiServiceTestCase { } mGroupHelper.onNotificationPosted( getNotificationRecord(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.of(7)), false); - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -353,7 +353,7 @@ public class GroupHelperTest extends UiServiceTestCase { mGroupHelper.onNotificationPosted( getNotificationRecord(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a", false), false); - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -1744,7 +1744,7 @@ public class GroupHelperTest extends UiServiceTestCase { notificationList.add(r); mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup); } - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -1759,7 +1759,7 @@ public class GroupHelperTest extends UiServiceTestCase { notificationList.add(r); mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup); } - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -1775,7 +1775,7 @@ public class GroupHelperTest extends UiServiceTestCase { notificationList.add(r); mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup); } - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -1791,7 +1791,7 @@ public class GroupHelperTest extends UiServiceTestCase { notificationList.add(r); mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup); } - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -1811,7 +1811,7 @@ public class GroupHelperTest extends UiServiceTestCase { String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, "testGrp", true); notificationList.add(r); mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup); - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -1830,7 +1830,7 @@ public class GroupHelperTest extends UiServiceTestCase { String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.of(7), "testGrp", true); notificationList.add(r); mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup); - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -1853,7 +1853,7 @@ public class GroupHelperTest extends UiServiceTestCase { String.valueOf(AUTOGROUP_AT_COUNT + 1), UserHandle.SYSTEM, "testGrp", false); notificationList.add(child); mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup); - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -1877,7 +1877,7 @@ public class GroupHelperTest extends UiServiceTestCase { notificationList.add(child); summaryByGroup.put(summary.getGroupKey(), summary); mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup); - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -2209,7 +2209,7 @@ public class GroupHelperTest extends UiServiceTestCase { childrenToRemove.add(child); } mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup); - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); // Remove all child notifications from the valid group => summary without children Mockito.reset(mCallback); @@ -2273,7 +2273,7 @@ public class GroupHelperTest extends UiServiceTestCase { } } mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup); - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); // Remove some child notifications from the valid group, transform into a singleton group Mockito.reset(mCallback); @@ -2329,7 +2329,7 @@ public class GroupHelperTest extends UiServiceTestCase { notificationList.add(child); } mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup); - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); // Remove all child notifications from the valid group => summary without children Mockito.reset(mCallback); @@ -2343,7 +2343,7 @@ public class GroupHelperTest extends UiServiceTestCase { mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList, summaryByGroup); // Check that nothing was force grouped - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -3837,7 +3837,7 @@ public class GroupHelperTest extends UiServiceTestCase { mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup); mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup); } - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @@ -3861,7 +3861,7 @@ public class GroupHelperTest extends UiServiceTestCase { mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup); } // FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS is disabled => don't force group - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test @@ -4498,7 +4498,7 @@ public class GroupHelperTest extends UiServiceTestCase { mGroupHelper.onNotificationPostedWithDelay(extra, notifList, summaryByGroupKey); // no autogrouping should have occurred - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 98440ecdad82..9c85b04fc4ff 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -40,10 +40,10 @@ import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index 076e3e9fcc24..aef3d30dba93 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -36,8 +36,8 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index bc01fc4f29c0..8fbd3bda98dd 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -48,7 +48,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; @@ -1114,8 +1113,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { verifyDelayedVibrate( mAttentionHelper.getVibratorHelper().createFallbackVibration( /* insistent= */ false)); - verify(mRingtonePlayer, never()).playAsync(anyObject(), anyObject(), anyBoolean(), - anyObject(), anyFloat()); + verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), + any(), anyFloat()); assertTrue(r.isInterruptive()); assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java index 41011928f8b3..8de2b9c6d3a6 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java @@ -32,9 +32,9 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java index 5aecac2cab78..9e8023ff344f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java @@ -23,9 +23,9 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; 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 dd5c601619a0..bc8b7becc919 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -168,10 +168,10 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.atLeastOnce; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index a02f628ce9b7..43228f434997 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -96,7 +96,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.AppOpsManager; @@ -6372,7 +6372,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { NotificationChannel same = new NotificationChannel("id", "Bah", IMPORTANCE_DEFAULT); mHelper.createNotificationChannel(PKG_P, 0, same, true, false, 0, false); - verifyZeroInteractions(mHandler); + verifyNoMoreInteractions(mHandler); } @Test @@ -6398,7 +6398,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.updateNotificationChannel(PKG_P, 0, same, false, 0, false); - verifyZeroInteractions(mHandler); + verifyNoMoreInteractions(mHandler); } @Test @@ -6412,7 +6412,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void setShowBadge_same_doesNotRequestSort() { mHelper.setShowBadge(PKG_P, 0, true); // true == DEFAULT_SHOW_BADGE - verifyZeroInteractions(mHandler); + verifyNoMoreInteractions(mHandler); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java index ec428d506e7b..91e4fd623cd4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java @@ -25,8 +25,8 @@ import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java index 31436c602e56..62f7471597f0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java @@ -25,7 +25,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java index dcd56e07f0d2..7f05ddefa891 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java @@ -28,9 +28,9 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenConfigTrimmerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenConfigTrimmerTest.java deleted file mode 100644 index 154a905c776b..000000000000 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenConfigTrimmerTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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.notification; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Parcel; -import android.service.notification.ZenModeConfig; -import android.service.notification.ZenModeConfig.ZenRule; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.android.internal.R; -import com.android.server.UiServiceTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class ZenConfigTrimmerTest extends UiServiceTestCase { - - private static final String TRUSTED_PACKAGE = "com.trust.me"; - private static final int ONE_PERCENT = 1_500; - - private ZenConfigTrimmer mTrimmer; - - @Before - public void setUp() { - mContext.getOrCreateTestableResources().addOverride( - R.string.config_defaultDndAccessPackages, TRUSTED_PACKAGE); - - mTrimmer = new ZenConfigTrimmer(mContext); - } - - @Test - public void trimToMaximumSize_belowMax_untouched() { - ZenModeConfig config = new ZenModeConfig(); - addZenRule(config, "1", "pkg1", 10 * ONE_PERCENT); - addZenRule(config, "2", "pkg1", 10 * ONE_PERCENT); - addZenRule(config, "3", "pkg1", 10 * ONE_PERCENT); - addZenRule(config, "4", "pkg2", 20 * ONE_PERCENT); - addZenRule(config, "5", "pkg2", 20 * ONE_PERCENT); - - mTrimmer.trimToMaximumSize(config); - - assertThat(config.automaticRules.keySet()).containsExactly("1", "2", "3", "4", "5"); - } - - @Test - public void trimToMaximumSize_exceedsMax_removesAllRulesOfLargestPackages() { - ZenModeConfig config = new ZenModeConfig(); - addZenRule(config, "1", "pkg1", 10 * ONE_PERCENT); - addZenRule(config, "2", "pkg1", 10 * ONE_PERCENT); - addZenRule(config, "3", "pkg1", 10 * ONE_PERCENT); - addZenRule(config, "4", "pkg2", 20 * ONE_PERCENT); - addZenRule(config, "5", "pkg2", 20 * ONE_PERCENT); - addZenRule(config, "6", "pkg3", 35 * ONE_PERCENT); - addZenRule(config, "7", "pkg4", 38 * ONE_PERCENT); - - mTrimmer.trimToMaximumSize(config); - - assertThat(config.automaticRules.keySet()).containsExactly("1", "2", "3", "6"); - assertThat(config.automaticRules.values().stream().map(r -> r.pkg).distinct()) - .containsExactly("pkg1", "pkg3"); - } - - @Test - public void trimToMaximumSize_keepsRulesFromTrustedPackages() { - ZenModeConfig config = new ZenModeConfig(); - addZenRule(config, "1", "pkg1", 10 * ONE_PERCENT); - addZenRule(config, "2", "pkg1", 10 * ONE_PERCENT); - addZenRule(config, "3", "pkg1", 10 * ONE_PERCENT); - addZenRule(config, "4", TRUSTED_PACKAGE, 60 * ONE_PERCENT); - addZenRule(config, "5", "pkg2", 20 * ONE_PERCENT); - addZenRule(config, "6", "pkg3", 35 * ONE_PERCENT); - - mTrimmer.trimToMaximumSize(config); - - assertThat(config.automaticRules.keySet()).containsExactly("4", "5"); - assertThat(config.automaticRules.values().stream().map(r -> r.pkg).distinct()) - .containsExactly(TRUSTED_PACKAGE, "pkg2"); - } - - /** - * Create a ZenRule that, when serialized to a Parcel, will take <em>approximately</em> - * {@code desiredSize} bytes (within 100 bytes). Try to make the tests not rely on a very tight - * fit. - */ - private static void addZenRule(ZenModeConfig config, String id, String pkg, int desiredSize) { - ZenRule rule = new ZenRule(); - rule.id = id; - rule.pkg = pkg; - config.automaticRules.put(id, rule); - - // Make the ZenRule as large as desired. Not to the exact byte, because otherwise this - // test would have to be adjusted whenever we change the parceling of ZenRule in any way. - // (Still might need adjustment if we change the serialization _significantly_). - int nameLength = desiredSize - id.length() - pkg.length() - 232; - rule.name = "A".repeat(nameLength); - - Parcel verification = Parcel.obtain(); - try { - verification.writeParcelable(rule, 0); - assertThat(verification.dataSize()).isWithin(100).of(desiredSize); - } finally { - verification.recycle(); - } - } -} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 5377102e5a13..bfce647356ec 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -90,7 +90,6 @@ import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED; import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG; import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW; import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW; -import static com.android.server.notification.Flags.FLAG_LIMIT_ZEN_CONFIG_SIZE; import static com.android.server.notification.Flags.FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING; import static com.android.server.notification.ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL; import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE; @@ -238,7 +237,6 @@ import java.util.stream.Collectors; @SmallTest @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. @RunWith(ParameterizedAndroidJunit4.class) -@EnableFlags(FLAG_LIMIT_ZEN_CONFIG_SIZE) // Should be parameterization, but off path does nothing. @TestableLooper.RunWithLooper public class ZenModeHelperTest extends UiServiceTestCase { @@ -7485,45 +7483,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(getZenRule(ruleId).lastActivation).isNull(); } - @Test - @EnableFlags(FLAG_LIMIT_ZEN_CONFIG_SIZE) - public void addAutomaticZenRule_trimsConfiguration() { - mZenModeHelper.mConfig.automaticRules.clear(); - AutomaticZenRule smallRule = new AutomaticZenRule.Builder("Reasonable", CONDITION_ID) - .setConfigurationActivity(new ComponentName(mPkg, "cls")) - .build(); - AutomaticZenRule systemRule = new AutomaticZenRule.Builder("System", CONDITION_ID) - .setOwner(new ComponentName("android", "ScheduleConditionProvider")) - .build(); - - AutomaticZenRule bigRule = new AutomaticZenRule.Builder("Yuge", CONDITION_ID) - .setConfigurationActivity(new ComponentName("evil.package", "cls")) - .setTriggerDescription("0123456789".repeat(6000)) // ~60k bytes utf16. - .build(); - - String systemRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "android", - systemRule, ORIGIN_SYSTEM, "add", SYSTEM_UID); - String smallRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, smallRule, - ORIGIN_APP, "add", CUSTOM_PKG_UID); - String bigRuleId1 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "evil.package", - bigRule, ORIGIN_APP, "add", CUSTOM_PKG_UID); - assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly( - systemRuleId, smallRuleId, bigRuleId1); - - String bigRuleId2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "evil.package", - bigRule, ORIGIN_APP, "add", CUSTOM_PKG_UID); - assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly( - systemRuleId, smallRuleId, bigRuleId1, bigRuleId2); - - // This should go over the threshold - String bigRuleId3 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "evil.package", - bigRule, ORIGIN_APP, "add", CUSTOM_PKG_UID); - - // Rules from evil.package are gone. - assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly( - systemRuleId, smallRuleId); - } - private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java index 79e272b7ec01..01698b5bdd6b 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java @@ -33,7 +33,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.content.ComponentName; @@ -133,7 +132,7 @@ public class VibratorControlServiceTest { mVibratorControlService.registerVibratorController(controller1); mVibratorControlService.unregisterVibratorController(controller2); - verifyZeroInteractions(mMockVibrationScaler); + verifyNoMoreInteractions(mMockVibrationScaler); assertThat(controller1.isLinkedToDeath).isTrue(); } @@ -187,7 +186,7 @@ public class VibratorControlServiceTest { verify(mStatsLoggerMock).logVibrationParamResponseIgnored(); verifyNoMoreInteractions(mStatsLoggerMock); - verifyZeroInteractions(mMockVibrationScaler); + verifyNoMoreInteractions(mMockVibrationScaler); } @Test(expected = IllegalArgumentException.class) @@ -242,7 +241,7 @@ public class VibratorControlServiceTest { mFakeVibratorController); verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat()); - verifyZeroInteractions(mMockVibrationScaler); + verifyNoMoreInteractions(mMockVibrationScaler); } @Test(expected = IllegalArgumentException.class) @@ -280,7 +279,7 @@ public class VibratorControlServiceTest { mFakeVibratorController); verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat()); - verifyZeroInteractions(mMockVibrationScaler); + verifyNoMoreInteractions(mMockVibrationScaler); } @Test diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java index 9a59ede20e23..011971d85f42 100644 --- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java +++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java @@ -28,7 +28,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import android.media.soundtrigger.RecognitionStatus; import android.media.soundtrigger_middleware.RecognitionEventSys; @@ -76,7 +75,7 @@ public class SoundTriggerHalConcurrentCaptureHandlerTest { assertEquals(event.halEventReceivedMillis, -1); assertEquals(event.recognitionEvent.status, RecognitionStatus.ABORTED); assertFalse(event.recognitionEvent.recognitionStillActive); - verifyZeroInteractions(mGlobalCallback); + verifyNoMoreInteractions(mGlobalCallback); clearInvocations(callback, mUnderlying); mNotifier.setActive(false); diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java index d961a6acc9c1..50fc77676958 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java @@ -21,7 +21,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.junit.Assert.assertEquals; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyObject; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; @@ -90,7 +90,7 @@ public class ModifierShortcutManagerTests { XmlResourceParser testBookmarks = mResources.getXml( com.android.frameworks.wmtests.R.xml.bookmarks); - doReturn(mContext).when(mContext).createContextAsUser(anyObject(), anyInt()); + doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); when(mContext.getResources()).thenReturn(mResources); when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mResources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks); @@ -106,7 +106,7 @@ public class ModifierShortcutManagerTests { doReturn(testActivityInfo).when(mPackageManager).getActivityInfo( eq(new ComponentName("com.test", "com.test.BookmarkTest")), anyInt()); - doReturn(testResolveInfo).when(mPackageManager).resolveActivity(anyObject(), anyInt()); + doReturn(testResolveInfo).when(mPackageManager).resolveActivity(any(), anyInt()); doThrow(new PackageManager.NameNotFoundException("com.test3")).when(mPackageManager) .getActivityInfo(eq(new ComponentName("com.test3", "com.test.BookmarkTest")), anyInt()); @@ -140,10 +140,10 @@ public class ModifierShortcutManagerTests { public void test_shortcutInfoFromIntent_appIntent() { Intent mockIntent = mock(Intent.class); ActivityInfo mockActivityInfo = mock(ActivityInfo.class); - when(mockActivityInfo.loadLabel(anyObject())).thenReturn("label"); + when(mockActivityInfo.loadLabel(any())).thenReturn("label"); mockActivityInfo.packageName = "android"; when(mockActivityInfo.getIconResource()).thenReturn(R.drawable.sym_def_app_icon); - when(mockIntent.resolveActivityInfo(anyObject(), anyInt())).thenReturn(mockActivityInfo); + when(mockIntent.resolveActivityInfo(any(), anyInt())).thenReturn(mockActivityInfo); KeyboardShortcutInfo info = mModifierShortcutManager.shortcutInfoFromIntent( 'a', mockIntent, true); @@ -161,7 +161,7 @@ public class ModifierShortcutManagerTests { Intent mockSelector = mock(Intent.class); ActivityInfo mockActivityInfo = mock(ActivityInfo.class); mockActivityInfo.name = com.android.internal.app.ResolverActivity.class.getName(); - when(mockIntent.resolveActivityInfo(anyObject(), anyInt())).thenReturn(mockActivityInfo); + when(mockIntent.resolveActivityInfo(any(), anyInt())).thenReturn(mockActivityInfo); when(mockIntent.getSelector()).thenReturn(mockSelector); when(mockSelector.getCategories()).thenReturn( Collections.singleton(Intent.CATEGORY_APP_BROWSER)); diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java index 8ede9efd32a0..c57adfd69b06 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java @@ -44,7 +44,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyObject; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static java.util.Collections.unmodifiableMap; @@ -111,7 +111,7 @@ class ShortcutKeyTestBase { mContext = spy(getInstrumentation().getTargetContext()); mResources = spy(mContext.getResources()); mPackageManager = spy(mContext.getPackageManager()); - doReturn(mContext).when(mContext).createContextAsUser(anyObject(), anyInt()); + doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); doReturn(mResources).when(mContext).getResources(); doReturn(mSettingsProviderRule.mockContentResolver(mContext)) .when(mContext).getContentResolver(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 8992c2e632b2..7f242dea9f45 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -2021,8 +2021,6 @@ public class ActivityRecordTests extends WindowTestsBase { display.setFixedRotationLaunchingAppUnchecked(activity); displayRotation.updateRotationUnchecked(true /* forceUpdate */); - assertTrue(displayRotation.isRotatingSeamlessly()); - // The launching rotated app should not be cleared when waiting for remote rotation. display.continueUpdateOrientationForDiffOrienLaunchingApp(); assertTrue(display.isFixedRotationLaunchingApp(activity)); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java index 86d901b640ff..862ce51d3459 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -20,6 +20,7 @@ import static android.app.ActivityManager.START_DELIVERED_TO_TOP; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; @@ -75,13 +76,15 @@ import java.util.concurrent.TimeUnit; * Tests for the {@link ActivityTaskSupervisor} class. * * Build/Install/Run: - * atest WmTests:ActivityTaskSupervisorTests + * atest WmTests:ActivityTaskSupervisorTests */ @MediumTest @Presubmit @RunWith(WindowTestRunner.class) public class ActivityTaskSupervisorTests extends WindowTestsBase { private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10); + private static final int DEFAULT_CALLING_PID = -1; + private static final int DEFAULT_CALLING_UID = -1; /** * Ensures that an activity is removed from the stopping activities list once it is resumed. @@ -110,7 +113,7 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { .setCreateTask(true).build(); final ConditionVariable condition = new ConditionVariable(); final WaitResult taskToFrontWait = new WaitResult(); - final ComponentName[] launchedComponent = { null }; + final ComponentName[] launchedComponent = {null}; // Create a new thread so the waiting method in test can be notified. new Thread(() -> { synchronized (mAtm.mGlobalLock) { @@ -408,7 +411,8 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(), anyInt(), any()); - mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions); + mSupervisor.startActivityFromRecents(DEFAULT_CALLING_PID, DEFAULT_CALLING_UID, + activity.getRootTaskId(), safeOptions); assertThat(activity.mLaunchCookie).isEqualTo(launchCookie); verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions)); @@ -426,12 +430,62 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(), anyInt(), any()); - mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions); + mSupervisor.startActivityFromRecents(DEFAULT_CALLING_PID, DEFAULT_CALLING_UID, + activity.getRootTaskId(), safeOptions); assertThat(activity.mLaunchCookie).isNull(); verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions)); } + /** Verifies that launch from recents doesn't set the launch cookie on the activity. */ + @Test + public void testStartActivityFromRecents_inMultiWindowRootTask_homeNotMoved() { + final Task multiWindowRootTask = new TaskBuilder(mSupervisor).setWindowingMode( + WINDOWING_MODE_MULTI_WINDOW).setOnTop(true).build(); + + final ActivityRecord activity = new ActivityBuilder(mAtm).setParentTask( + multiWindowRootTask).setCreateTask(true).build(); + + SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle( + ActivityOptions.makeBasic().toBundle(), + Binder.getCallingPid(), Binder.getCallingUid()); + + doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(), + anyInt(), any()); + + mSupervisor.startActivityFromRecents(DEFAULT_CALLING_PID, DEFAULT_CALLING_UID, + activity.getRootTaskId(), safeOptions); + + verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions)); + verify(mRootWindowContainer.getDefaultTaskDisplayArea(), never()).moveHomeRootTaskToFront( + any()); + verify(multiWindowRootTask.getDisplayArea(), never()).moveHomeRootTaskToFront(any()); + } + + /** Verifies that launch from recents doesn't set the launch cookie on the activity. */ + @Test + public void testStartActivityFromRecents_inFullScreenRootTask_homeMovedToFront() { + final Task fullscreenRootTask = new TaskBuilder(mSupervisor).setWindowingMode( + WINDOWING_MODE_FULLSCREEN).setOnTop(true).build(); + + final ActivityRecord activity = new ActivityBuilder(mAtm).setParentTask( + fullscreenRootTask).setCreateTask(true).build(); + + SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle( + ActivityOptions.makeBasic().toBundle(), + Binder.getCallingPid(), Binder.getCallingUid()); + + doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(), + anyInt(), any()); + + mSupervisor.startActivityFromRecents(DEFAULT_CALLING_PID, DEFAULT_CALLING_UID, + activity.getRootTaskId(), safeOptions); + + verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions)); + verify(mRootWindowContainer.getDefaultTaskDisplayArea()).moveHomeRootTaskToFront(any()); + verify(fullscreenRootTask.getDisplayArea()).moveHomeRootTaskToFront(any()); + } + @Test public void testOpaque_leafTask_occludingActivity_isOpaque() { final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatSafeRegionPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatSafeRegionPolicyTests.java new file mode 100644 index 000000000000..bdd57bce7c09 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatSafeRegionPolicyTests.java @@ -0,0 +1,102 @@ +/* + * 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.wm; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertEquals; + +import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; + +import androidx.annotation.NonNull; + +import com.android.window.flags.Flags; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; + +/** + * Test class for {@link AppCompatSafeRegionPolicy}. + * Build/Install/Run: + * atest WmTests:AppCompatSafeRegionPolicyTests + */ +@Presubmit +@RunWith(WindowTestRunner.class) +@EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) +public class AppCompatSafeRegionPolicyTests extends WindowTestsBase { + + @Test + public void testHasNeedsSafeRegion_returnTrue() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.setNeedsSafeRegionBounds(/*needsSafeRegionBounds*/ true); + + robot.checkNeedsSafeRegionBounds(/* expected */ true); + }); + } + + @Test + public void testDoesNotHaveNeedsSafeRegion_returnFalse() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.setNeedsSafeRegionBounds(/*needsSafeRegionBounds*/ false); + + robot.checkNeedsSafeRegionBounds(/* expected */ false); + }); + } + + /** + * Runs a test scenario providing a Robot. + */ + void runTestScenario(@NonNull Consumer<AppCompatSafeRegionPolicyRobotTest> consumer) { + final AppCompatSafeRegionPolicyRobotTest robot = + new AppCompatSafeRegionPolicyRobotTest(mWm, mAtm, mSupervisor); + consumer.accept(robot); + } + + private static class AppCompatSafeRegionPolicyRobotTest extends AppCompatRobotBase { + + AppCompatSafeRegionPolicyRobotTest(@NonNull WindowManagerService wm, + @NonNull ActivityTaskManagerService atm, + @NonNull ActivityTaskSupervisor supervisor) { + super(wm, atm, supervisor); + } + + @Override + void onPostActivityCreation(@NonNull ActivityRecord activity) { + super.onPostActivityCreation(activity); + spyOn(activity.mAppCompatController.getSafeRegionPolicy()); + } + + AppCompatSafeRegionPolicy getAppCompatSafeRegionPolicy() { + return activity().top().mAppCompatController.getSafeRegionPolicy(); + } + + void setNeedsSafeRegionBounds(boolean needsSafeRegionBounds) { + doReturn(needsSafeRegionBounds).when( + getAppCompatSafeRegionPolicy()).getNeedsSafeRegionBounds(); + } + + void checkNeedsSafeRegionBounds(boolean expected) { + assertEquals(expected, getAppCompatSafeRegionPolicy().getNeedsSafeRegionBounds()); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java index bfd533aa8f79..b2cfbbd23b22 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java @@ -98,6 +98,24 @@ public class AppCompatUtilsTest extends WindowTestsBase { } @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void getLetterboxReasonString_isLetterboxedForSafeRegionOnly() { + runTestScenario((robot) -> { + robot.applyOnActivity((a) -> { + a.createActivityWithComponent(); + a.checkTopActivityInSizeCompatMode(/* inScm */ false); + }); + robot.setIsLetterboxedForFixedOrientationAndAspectRatio( + /* forFixedOrientationAndAspectRatio */ false); + robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false); + robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ false); + robot.setIsLetterboxedForSafeRegionOnlyAllowed(/* safeRegionOnly */ true); + + robot.checkTopActivityLetterboxReason(/* expected */ "SAFE_REGION"); + }); + } + + @Test public void getLetterboxReasonString_aspectRatio() { runTestScenario((robot) -> { robot.applyOnActivity((a) -> { @@ -124,6 +142,7 @@ public class AppCompatUtilsTest extends WindowTestsBase { /* forFixedOrientationAndAspectRatio */ false); robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false); robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ false); + robot.setIsLetterboxedForSafeRegionOnlyAllowed(/* safeRegionOnly */ false); robot.checkTopActivityLetterboxReason(/* expected */ "UNKNOWN_REASON"); }); @@ -253,6 +272,7 @@ public class AppCompatUtilsTest extends WindowTestsBase { void onPostActivityCreation(@NonNull ActivityRecord activity) { super.onPostActivityCreation(activity); spyOn(activity.mAppCompatController.getAspectRatioPolicy()); + spyOn(activity.mAppCompatController.getSafeRegionPolicy()); } @Override @@ -286,6 +306,11 @@ public class AppCompatUtilsTest extends WindowTestsBase { when(mWindowState.isLetterboxedForDisplayCutout()).thenReturn(displayCutout); } + void setIsLetterboxedForSafeRegionOnlyAllowed(boolean safeRegionOnly) { + when(activity().top().mAppCompatController.getSafeRegionPolicy() + .isLetterboxedForSafeRegionOnlyAllowed()).thenReturn(safeRegionOnly); + } + void setFreeformCameraCompatMode(@FreeformCameraCompatMode int mode) { doReturn(mode).when(activity().top().mDisplayContent.mAppCompatCameraPolicy .mCameraCompatFreeformPolicy).getCameraCompatMode(activity().top()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java index 76b994d013f3..ad76662c6c18 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java @@ -31,6 +31,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT; import static com.google.common.truth.Truth.assertThat; @@ -51,6 +52,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.media.projection.StopReason; import android.os.IBinder; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.ContentRecordingSession; import android.view.Display; @@ -558,6 +560,22 @@ public class ContentRecorderTests extends WindowTestsBase { assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); } + @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT) + @Test + public void testStartRecording_shouldShowSystemDecorations_recordingNotStarted() { + defaultInit(); + mContentRecorder.setContentRecordingSession(mTaskSession); + + spyOn(mVirtualDisplayContent.mDisplay); + doReturn(true).when(mVirtualDisplayContent.mDisplay).canHostTasks(); + + // WHEN a recording tries to start. + mContentRecorder.updateRecording(); + + // THEN recording does not start. + assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); + } + @Test public void testOnVisibleRequestedChanged_notifiesCallback() { defaultInit(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java index e87e107cd793..00b617e91bfd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java @@ -41,6 +41,7 @@ import static android.util.DisplayMetrics.DENSITY_DEFAULT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.internal.policy.SystemBarUtils.getDesktopViewAppHeaderHeightPx; import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_INITIAL_BOUNDS_SCALE; import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_LANDSCAPE_APP_PADDING; import static com.android.server.wm.DesktopModeBoundsCalculator.centerInScreen; @@ -382,7 +383,7 @@ public class DesktopModeLaunchParamsModifierTests extends spyOn(mActivity.mAppCompatController.getAspectRatioOverrides()); doReturn(true).when( mActivity.mAppCompatController.getAspectRatioOverrides()) - .isUserFullscreenOverrideEnabled(); + .hasFullscreenOverride(); final int desiredWidth = (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE); @@ -410,7 +411,7 @@ public class DesktopModeLaunchParamsModifierTests extends spyOn(mActivity.mAppCompatController.getAspectRatioOverrides()); doReturn(true).when( mActivity.mAppCompatController.getAspectRatioOverrides()) - .isSystemOverrideToFullscreenEnabled(); + .hasFullscreenOverride(); final int desiredWidth = (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE); @@ -893,9 +894,11 @@ public class DesktopModeLaunchParamsModifierTests extends @Test @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS}) + Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, + Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS}) public void testDefaultLandscapeBounds_landscapeDevice_unResizable_landscapeOrientation() { setupDesktopModeLaunchParamsModifier(); + final int captionHeight = getDesktopViewAppHeaderHeightPx(mContext); final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE, LANDSCAPE_DISPLAY_BOUNDS); @@ -903,11 +906,11 @@ public class DesktopModeLaunchParamsModifierTests extends final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE, task, /* ignoreOrientationRequest */ true); - - final int desiredWidth = - (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE); + final float displayAspectRatio = (float) LANDSCAPE_DISPLAY_BOUNDS.width() + / LANDSCAPE_DISPLAY_BOUNDS.height(); final int desiredHeight = (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE); + final int desiredWidth = (int) ((desiredHeight - captionHeight) * displayAspectRatio); assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task) .setActivity(activity).calculate()); @@ -916,7 +919,8 @@ public class DesktopModeLaunchParamsModifierTests extends } @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + @EnableFlags({Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, + Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS}) public void testUnResizablePortraitBounds_landscapeDevice_unResizable_portraitOrientation() { setupDesktopModeLaunchParamsModifier(); @@ -925,6 +929,7 @@ public class DesktopModeLaunchParamsModifierTests extends final Task task = createTask(display, /* isResizeable */ false); final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT, task, /* ignoreOrientationRequest */ true); + final int captionHeight = getDesktopViewAppHeaderHeightPx(mContext); spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy()); doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController @@ -932,7 +937,7 @@ public class DesktopModeLaunchParamsModifierTests extends final int desiredHeight = (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE); - final int desiredWidth = (int) (desiredHeight / LETTERBOX_ASPECT_RATIO); + final int desiredWidth = (int) ((desiredHeight - captionHeight) / LETTERBOX_ASPECT_RATIO); assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task) .setActivity(activity).calculate()); @@ -1070,7 +1075,8 @@ public class DesktopModeLaunchParamsModifierTests extends @Test @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS}) + Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, + Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS}) public void testDefaultPortraitBounds_portraitDevice_unResizable_portraitOrientation() { setupDesktopModeLaunchParamsModifier(); @@ -1079,12 +1085,14 @@ public class DesktopModeLaunchParamsModifierTests extends final Task task = createTask(display, /* isResizeable */ false); final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT, task, /* ignoreOrientationRequest */ true); + final int captionHeight = getDesktopViewAppHeaderHeightPx(mContext); - - final int desiredWidth = - (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE); + final float displayAspectRatio = (float) PORTRAIT_DISPLAY_BOUNDS.height() + / PORTRAIT_DISPLAY_BOUNDS.width(); final int desiredHeight = - (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE); + (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE); + final int desiredWidth = + (int) ((desiredHeight - captionHeight) / displayAspectRatio); assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task) .setActivity(activity).calculate()); @@ -1093,7 +1101,8 @@ public class DesktopModeLaunchParamsModifierTests extends } @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + @EnableFlags({Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, + Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS}) public void testUnResizableLandscapeBounds_portraitDevice_unResizable_landscapeOrientation() { setupDesktopModeLaunchParamsModifier(); @@ -1102,6 +1111,7 @@ public class DesktopModeLaunchParamsModifierTests extends final Task task = createTask(display, /* isResizeable */ false); final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE, task, /* ignoreOrientationRequest */ true); + final int captionHeight = getDesktopViewAppHeaderHeightPx(mContext); spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy()); doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController @@ -1109,7 +1119,7 @@ public class DesktopModeLaunchParamsModifierTests extends final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width() - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2); - final int desiredHeight = (int) (desiredWidth / LETTERBOX_ASPECT_RATIO); + final int desiredHeight = (int) (desiredWidth / LETTERBOX_ASPECT_RATIO) + captionHeight; assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task) .setActivity(activity).calculate()); @@ -1160,6 +1170,32 @@ public class DesktopModeLaunchParamsModifierTests extends @Test @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX}) + public void testOptionsBoundsSet_flexibleLaunchSizeWithFullscreenOverride_noModifications() { + setupDesktopModeLaunchParamsModifier(); + + final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN); + final Task task = new TaskBuilder(mSupervisor).setActivityType( + ACTIVITY_TYPE_STANDARD).setDisplay(display).build(); + final ActivityOptions options = ActivityOptions.makeBasic() + .setLaunchBounds(new Rect( + DISPLAY_STABLE_BOUNDS.left, + DISPLAY_STABLE_BOUNDS.top, + /* right = */ 500, + /* bottom = */ 500)) + .setFlexibleLaunchSize(true); + spyOn(mActivity.mAppCompatController.getAspectRatioOverrides()); + doReturn(true).when( + mActivity.mAppCompatController.getAspectRatioOverrides()) + .hasFullscreenOverride(); + + assertEquals(RESULT_DONE, + new CalculateRequestBuilder().setTask(task).setOptions(options).calculate()); + assertEquals(options.getLaunchBounds(), mResult.mBounds); + } + + @Test + @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX}) public void testOptionsBoundsSet_flexibleLaunchSize_boundsSizeModified() { setupDesktopModeLaunchParamsModifier(); @@ -1483,6 +1519,24 @@ public class DesktopModeLaunchParamsModifierTests extends assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode); } + @Test + @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX}) + public void testFreeformWindowingModeAppliedIfSourceTaskExists() { + setupDesktopModeLaunchParamsModifier(); + + final Task task = new TaskBuilder(mSupervisor).setActivityType( + ACTIVITY_TYPE_STANDARD).build(); + final Task sourceTask = new TaskBuilder(mSupervisor).setActivityType( + ACTIVITY_TYPE_STANDARD).setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + final ActivityRecord sourceActivity = new ActivityBuilder(task.mAtmService) + .setTask(sourceTask).build(); + + assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task) + .setSource(sourceActivity).calculate()); + assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode); + } + private Task createTask(DisplayContent display, Boolean isResizeable) { final int resizeMode = isResizeable ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE; diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index a30591ea7b15..9486bc522a9c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -25,6 +25,7 @@ import static com.android.server.wm.utils.LastCallVerifier.lastCall; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -432,4 +433,32 @@ public class DimmerTests extends WindowTestsBase { verify(mTransaction, never()).setAlpha(dimLayer, 0.5f); verify(mTransaction).setAlpha(dimLayer, 0.9f); } + + /** + * A window requesting to dim to 0 and without blur would cause the dim to be created and + * destroyed continuously. + * Ensure the dim layer is not created until the window is requesting valid values. + */ + @Test + public void testDimNotCreatedIfNoAlphaNoBlur() { + mDimmer.adjustAppearance(mChild1, 0.0f, 0); + mDimmer.adjustPosition(mChild1, mChild1); + assertNull(mDimmer.getDimLayer()); + mDimmer.updateDims(mTransaction); + assertNull(mDimmer.getDimLayer()); + + mDimmer.adjustAppearance(mChild1, 0.9f, 0); + mDimmer.adjustPosition(mChild1, mChild1); + assertNotNull(mDimmer.getDimLayer()); + } + + /** + * If there is a blur, then the dim layer is created even though alpha is 0 + */ + @Test + public void testDimCreatedIfNoAlphaButHasBlur() { + mDimmer.adjustAppearance(mChild1, 0.0f, 10); + mDimmer.adjustPosition(mChild1, mChild1); + assertNotNull(mDimmer.getDimLayer()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java index 0af41ea1f634..89aa3b9a2443 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java @@ -56,7 +56,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.pm.ActivityInfo; @@ -174,7 +174,7 @@ public class DisplayAreaTest extends WindowTestsBase { da1.reduceOnAllTaskDisplayAreas(callback2, 0); da1.getItemFromTaskDisplayAreas(callback3); - verifyZeroInteractions(da2); + verifyNoMoreInteractions(da2); // Traverse the child if the current DA has type ANY final DisplayArea<WindowContainer> da3 = new DisplayArea<>(mWm, ANY, "DA3"); @@ -207,7 +207,7 @@ public class DisplayAreaTest extends WindowTestsBase { da5.reduceOnAllTaskDisplayAreas(callback2, 0); da5.getItemFromTaskDisplayAreas(callback3); - verifyZeroInteractions(da6); + verifyNoMoreInteractions(da6); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java index 7033d79d0ee1..9ce4a80616ab 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java @@ -43,7 +43,6 @@ import androidx.test.filters.SmallTest; import com.android.server.LocalServices; import com.android.server.wm.TransitionController.OnStartCollect; -import com.android.window.flags.Flags; import org.junit.Before; import org.junit.Test; @@ -218,7 +217,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { @Test public void testWaitForTransition_displaySwitching_waitsForTransitionToBeStarted() { - mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH); mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true); boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker); assertThat(willWait).isTrue(); @@ -241,7 +239,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { @Test public void testWaitForTransition_displayNotSwitching_doesNotWait() { - mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH); mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ false); boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker); @@ -251,19 +248,7 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { } @Test - public void testWaitForTransition_displayIsSwitchingButFlagDisabled_doesNotWait() { - mSetFlagsRule.disableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH); - mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true); - - boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker); - - assertThat(willWait).isFalse(); - verify(mScreenUnblocker, never()).sendToTarget(); - } - - @Test public void testTwoDisplayUpdateAtTheSameTime_bothDisplaysAreUnblocked() { - mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH); prepareSecondaryDisplay(); final WindowState defaultDisplayWindow = newWindowBuilder("DefaultDisplayWindow", diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 4c81f738138a..ed00a9e8e74b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -30,7 +30,10 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.FLAG_ALLOWS_CONTENT_MODE_SWITCH; import static android.view.Display.FLAG_PRIVATE; +import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; +import static android.view.Display.FLAG_TRUSTED; import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; import static android.view.DisplayCutout.fromBoundingRect; import static android.view.Surface.ROTATION_0; @@ -1706,8 +1709,6 @@ public class DisplayContentTests extends WindowTestsBase { app.setVisible(true); doReturn(false).when(app).inTransition(); mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token); - mStatusBarWindow.finishSeamlessRotation(t); - mNavBarWindow.finishSeamlessRotation(t); // The fixed rotation should be cleared and the new rotation is applied to display. assertFalse(app.hasFixedRotationTransform()); @@ -2925,6 +2926,63 @@ public class DisplayContentTests extends WindowTestsBase { assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc)); } + @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT) + @Test + public void testSetShouldShowSystemDecorations_shouldShowSystemDecorationsDisplay() { + // Set up a non-default display with FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS enabled + final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo); + displayInfo.displayId = DEFAULT_DISPLAY + 1; + displayInfo.flags = FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; + final DisplayContent dc = createNewDisplay(displayInfo); + + dc.onDisplayInfoChangeApplied(); + assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc)); + } + + @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT) + @Test + public void testSetShouldShowSystemDecorations_notAllowContentModeSwitchDisplay() { + // Set up a non-default display without FLAG_ALLOWS_CONTENT_MODE_SWITCH enabled + final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo); + displayInfo.displayId = DEFAULT_DISPLAY + 1; + displayInfo.flags = FLAG_TRUSTED; + final DisplayContent dc = createNewDisplay(displayInfo); + + dc.onDisplayInfoChangeApplied(); + assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc)); + } + + @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT) + @Test + public void testSetShouldShowSystemDecorations_untrustedDisplay() { + // Set up a non-default display without FLAG_TRUSTED enabled + final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo); + displayInfo.displayId = DEFAULT_DISPLAY + 1; + displayInfo.flags = FLAG_ALLOWS_CONTENT_MODE_SWITCH; + final DisplayContent dc = createNewDisplay(displayInfo); + + dc.onDisplayInfoChangeApplied(); + assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc)); + } + + @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT) + @Test + public void testSetShouldShowSystemDecorations_nonDefaultNonPrivateDisplay() { + final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo); + displayInfo.displayId = DEFAULT_DISPLAY + 1; + displayInfo.flags = (FLAG_ALLOWS_CONTENT_MODE_SWITCH | FLAG_TRUSTED); + final DisplayContent dc = createNewDisplay(displayInfo); + + spyOn(dc.mDisplay); + doReturn(false).when(dc.mDisplay).canHostTasks(); + dc.onDisplayInfoChangeApplied(); + assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc)); + + doReturn(true).when(dc.mDisplay).canHostTasks(); + dc.onDisplayInfoChangeApplied(); + assertTrue(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc)); + } + @EnableFlags(FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS) @Test public void testForcedDensityRatioSetForExternalDisplays_persistDensityScaleFlagEnabled() { @@ -2993,23 +3051,6 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(320, displayContent.mBaseDisplayDensity); } - @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT) - @Test - public void testSetShouldShowSystemDecorations_nonDefaultNonPrivateDisplay() { - final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo); - displayInfo.displayId = DEFAULT_DISPLAY + 1; - final DisplayContent dc = createNewDisplay(displayInfo); - - spyOn(dc.mDisplay); - doReturn(false).when(dc.mDisplay).canHostTasks(); - dc.onDisplayInfoChangeApplied(); - assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc)); - - doReturn(true).when(dc.mDisplay).canHostTasks(); - dc.onDisplayInfoChangeApplied(); - assertTrue(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc)); - } - private void removeRootTaskTests(Runnable runnable) { final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, 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 81e487a67725..521d8364a31f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java @@ -37,7 +37,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import android.annotation.NonNull; import android.app.WindowConfiguration; diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java index eb6d5cf8bb14..e293c2fbbfb1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java @@ -19,6 +19,9 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN; +import static com.android.server.wm.WindowStateAnimator.NO_SURFACE; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -244,4 +247,33 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { verify(displayWindowInsetsController, times(1)).setImeInputTargetRequestedVisibility( eq(true), any()); } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER) + public void testOnPostLayout_resetServerVisibilityWhenImeIsNotDrawn() { + final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).build(); + final WindowState inputTarget = newWindowBuilder("app", TYPE_APPLICATION).build(); + makeWindowVisibleAndDrawn(ime); + mImeProvider.setWindowContainer(ime, null, null); + mImeProvider.setServerVisible(true); + mImeProvider.setClientVisible(true); + mImeProvider.updateVisibility(); + mImeProvider.updateControlForTarget(inputTarget, true /* force */, null /* statsToken */); + + // Calling onPostLayout, as the drawn state is initially false. + mImeProvider.onPostLayout(); + assertTrue(mImeProvider.isSurfaceVisible()); + + // Reset window's drawn state + ime.mWinAnimator.mDrawState = NO_SURFACE; + mImeProvider.onPostLayout(); + assertFalse(mImeProvider.isServerVisible()); + assertFalse(mImeProvider.isSurfaceVisible()); + + // Set it back to drawn + ime.mWinAnimator.mDrawState = HAS_DRAWN; + mImeProvider.onPostLayout(); + assertTrue(mImeProvider.isServerVisible()); + assertTrue(mImeProvider.isSurfaceVisible()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java index 67a95de8a5c1..ae005f228969 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java @@ -44,6 +44,7 @@ import android.app.ActivityOptions; import android.content.ComponentName; import android.content.pm.ActivityInfo.WindowLayout; import android.graphics.Rect; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.SparseArray; @@ -52,6 +53,7 @@ import androidx.test.filters.MediumTest; import com.android.server.wm.LaunchParamsController.LaunchParams; import com.android.server.wm.LaunchParamsController.LaunchParamsModifier; +import com.android.window.flags.Flags; import org.junit.Before; import org.junit.Test; @@ -115,6 +117,7 @@ public class LaunchParamsControllerTests extends WindowTestsBase { expected.mPreferredTaskDisplayArea = mock(TaskDisplayArea.class); expected.mWindowingMode = WINDOWING_MODE_PINNED; expected.mBounds.set(200, 300, 400, 500); + expected.mNeedsSafeRegionBounds = true; mPersister.putLaunchParams(userId, name, expected); @@ -187,6 +190,7 @@ public class LaunchParamsControllerTests extends WindowTestsBase { params.mWindowingMode = WINDOWING_MODE_FREEFORM; params.mBounds.set(0, 0, 30, 20); params.mPreferredTaskDisplayArea = mock(TaskDisplayArea.class); + params.mNeedsSafeRegionBounds = true; final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE, params); @@ -227,6 +231,158 @@ public class LaunchParamsControllerTests extends WindowTestsBase { } /** + * Tests only needs safe region bounds are not propagated if results are skipped. + */ + @Test + public void testSkip_needsSafeRegionBoundsNotModified() { + final LaunchParams params1 = new LaunchParams(); + params1.mNeedsSafeRegionBounds = true; + final InstrumentedPositioner positioner1 = new InstrumentedPositioner(RESULT_SKIP, params1); + + final LaunchParams params2 = new LaunchParams(); + params2.mNeedsSafeRegionBounds = false; + final InstrumentedPositioner positioner2 = + new InstrumentedPositioner(RESULT_CONTINUE, params2); + + mController.registerModifier(positioner1); + mController.registerModifier(positioner2); + + final LaunchParams result = new LaunchParams(); + + mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/, + null /*options*/, null /*request*/, PHASE_BOUNDS, result); + + assertEquals(result, positioner2.getLaunchParams()); + } + + /** + * Tests only needs safe region bounds are propagated even if results are continued. + */ + @Test + public void testContinue_needsSafeRegionBoundsCarriedOver() { + final LaunchParams params1 = new LaunchParams(); + final InstrumentedPositioner positioner1 = + new InstrumentedPositioner(RESULT_CONTINUE, params1); + + final LaunchParams params2 = new LaunchParams(); + params2.mNeedsSafeRegionBounds = true; + final InstrumentedPositioner positioner2 = + new InstrumentedPositioner(RESULT_CONTINUE, params2); + + mController.registerModifier(positioner1); + mController.registerModifier(positioner2); + + final LaunchParams result = new LaunchParams(); + + mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/, + null /*options*/, null /*request*/, PHASE_BOUNDS, result); + + // Safe region is propagated from positioner1 + assertEquals(result.mNeedsSafeRegionBounds, + positioner2.getLaunchParams().mNeedsSafeRegionBounds); + assertEquals(result.mWindowingMode, positioner1.getLaunchParams().mWindowingMode); + assertEquals(result.mBounds, positioner1.getLaunchParams().mBounds); + assertEquals(result.mPreferredTaskDisplayArea, + positioner1.getLaunchParams().mPreferredTaskDisplayArea); + } + + /** + * Tests needs safe region bounds are modified if results from the next continue have been set. + */ + @Test + public void testContinue_needsSafeRegionBoundsModifiedFromLaterContinue() { + final LaunchParams params1 = new LaunchParams(); + params1.mNeedsSafeRegionBounds = false; + final InstrumentedPositioner positioner1 = + new InstrumentedPositioner(RESULT_CONTINUE, params1); + + final LaunchParams params2 = new LaunchParams(); + params2.mNeedsSafeRegionBounds = true; + final InstrumentedPositioner positioner2 = + new InstrumentedPositioner(RESULT_CONTINUE, params2); + + mController.registerModifier(positioner1); + mController.registerModifier(positioner2); + + final LaunchParams result = new LaunchParams(); + + mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/, + null /*options*/, null /*request*/, PHASE_BOUNDS, result); + + // Safe region is propagated from positioner1 + assertEquals(result.mNeedsSafeRegionBounds, + positioner1.getLaunchParams().mNeedsSafeRegionBounds); + assertEquals(result.mWindowingMode, positioner2.getLaunchParams().mWindowingMode); + assertEquals(result.mBounds, positioner2.getLaunchParams().mBounds); + assertEquals(result.mPreferredTaskDisplayArea, + positioner2.getLaunchParams().mPreferredTaskDisplayArea); + } + + /** + * Tests only needs safe region bounds are propagated to result done even if there are skipped + * and continued results and continue sets true for needs safe region bounds. + */ + @Test + public void testDone_ContinueSetsNeedsSafeRegionBounds() { + final LaunchParams params1 = new LaunchParams(); + final InstrumentedPositioner positioner1 = new InstrumentedPositioner(RESULT_DONE, params1); + + final LaunchParams params2 = new LaunchParams(); + final InstrumentedPositioner positioner2 = + new InstrumentedPositioner(RESULT_CONTINUE, params2); + + final LaunchParams params3 = new LaunchParams(); + final InstrumentedPositioner positioner3 = new InstrumentedPositioner(RESULT_SKIP, params3); + + final LaunchParams params4 = new LaunchParams(); + params4.mNeedsSafeRegionBounds = true; + final InstrumentedPositioner positioner4 = + new InstrumentedPositioner(RESULT_CONTINUE, params4); + + final LaunchParams params5 = new LaunchParams(); + params5.mNeedsSafeRegionBounds = false; + final InstrumentedPositioner positioner5 = new InstrumentedPositioner(RESULT_SKIP, params5); + + mController.registerModifier(positioner1); + mController.registerModifier(positioner2); + mController.registerModifier(positioner3); + mController.registerModifier(positioner4); + mController.registerModifier(positioner5); + + final LaunchParams result = new LaunchParams(); + + mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/, + null /*options*/, null /*request*/, PHASE_BOUNDS, result); + + // Safe region is propagated from positioner4 + assertEquals(result.mNeedsSafeRegionBounds, + positioner4.getLaunchParams().mNeedsSafeRegionBounds); + assertEquals(result.mWindowingMode, positioner1.getLaunchParams().mWindowingMode); + assertEquals(result.mBounds, positioner1.getLaunchParams().mBounds); + assertEquals(result.mPreferredTaskDisplayArea, + positioner1.getLaunchParams().mPreferredTaskDisplayArea); + } + + /** + * Tests only needs safe region bounds are set if results are done. + */ + @Test + public void testDone_needsSafeRegionBoundsModified() { + final LaunchParams params = new LaunchParams(); + params.mNeedsSafeRegionBounds = true; + final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params); + + mController.registerModifier(positioner); + + final LaunchParams result = new LaunchParams(); + + mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/, + null /*options*/, null /*request*/, PHASE_BOUNDS, result); + + assertEquals(result, positioner.getLaunchParams()); + } + + /** * Tests preferred display id calculation for VR. */ @Test @@ -372,6 +528,34 @@ public class LaunchParamsControllerTests extends WindowTestsBase { assertEquals(expected, task.mLastNonFullscreenBounds); } + /** + * Ensures that app bounds are set to exclude freeform caption if window is in freeform. + */ + @Test + @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS) + public void testLayoutTaskBoundsFreeformAppBounds() { + final Rect expected = new Rect(10, 20, 30, 40); + + final LaunchParams params = new LaunchParams(); + params.mBounds.set(expected); + params.mAppBounds.set(expected); + final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params); + final Task task = new TaskBuilder(mAtm.mTaskSupervisor) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + final ActivityOptions options = ActivityOptions.makeBasic().setFlexibleLaunchSize(true); + + mController.registerModifier(positioner); + + assertNotEquals(expected, task.getBounds()); + + layoutTask(task, options); + + // Task will make adjustments to requested bounds. We only need to guarantee that the + // requested bounds are expected. + assertEquals(expected, + task.getRequestedOverrideConfiguration().windowConfiguration.getAppBounds()); + } + public static class InstrumentedPositioner implements LaunchParamsModifier { private final int mReturnVal; @@ -473,4 +657,9 @@ public class LaunchParamsControllerTests extends WindowTestsBase { mController.layoutTask(task, null /* layout */, null /* activity */, null /* source */, null /* options */); } + + private void layoutTask(@NonNull Task task, ActivityOptions options) { + mController.layoutTask(task, null /* layout */, null /* activity */, null /* source */, + options /* options */); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index 66d7963946b9..1356718be4dd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -30,7 +30,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import android.content.ComponentName; import android.content.pm.PackageManagerInternal; diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 8a7e7434e604..2c6884e7a35a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -52,7 +52,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static java.lang.Integer.MAX_VALUE; @@ -1293,7 +1293,7 @@ public class RecentTasksTest extends WindowTestsBase { // Add secondTask to top again mRecentTasks.add(secondTask); - verifyZeroInteractions(controller); + verifyNoMoreInteractions(controller); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index e2c4a1d2dfea..fc70b80790c8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4738,6 +4738,213 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testIsLetterboxedForSafeRegionOnlyAllowed_noManifestProperty_returnsTrue() { + setUpLandscapeLargeScreenDisplayWithApp(); + + assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); + + setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200); + + // For an activity letterboxed only due to safe region, areBoundsLetterboxed will return + // false + assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); + // Since no manifest property is defined, the activity is opted in by default + assertTrue(mActivity.mAppCompatController.getSafeRegionPolicy() + .isLetterboxedForSafeRegionOnlyAllowed()); + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testIsLetterboxedForSafeRegionOnlyAllowed_allowedForActivity_returnsTrue() { + setUpLandscapeLargeScreenDisplayWithApp(); + + assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); + + setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200); + + // Activity can opt-out the safe region letterboxing by component level property. + final ComponentName name = getUniqueComponentName(mContext.getPackageName()); + final PackageManager pm = mContext.getPackageManager(); + spyOn(pm); + updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, true /* value */); + updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */); + final ActivityRecord optOutActivity = new ActivityBuilder(mAtm) + .setComponent(name).setTask(mTask).build(); + optOutActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true); + + // Since activity manifest property is defined as true, the activity can be letterboxed + // for safe region + assertTrue(optOutActivity.mAppCompatController + .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed()); + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testIsLetterboxedForSafeRegionOnlyAllowed_notAllowedForActivity_returnsFalse() { + setUpLandscapeLargeScreenDisplayWithApp(); + + assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); + + setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200); + + final ComponentName name = getUniqueComponentName(mContext.getPackageName()); + final PackageManager pm = mContext.getPackageManager(); + spyOn(pm); + updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */); + updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */); + final ActivityRecord optOutActivity = new ActivityBuilder(mAtm) + .setComponent(name).setTask(mTask).build(); + optOutActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true); + + // Since activity manifest property is defined as false, the activity can not be letterboxed + // for safe region + assertFalse(optOutActivity.mAppCompatController + .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed()); + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testIsLetterboxedForSafeRegionOnlyAllowed_notAllowedForApplication_returnsFalse() { + setUpLandscapeLargeScreenDisplayWithApp(); + + assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); + + setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200); + + final ComponentName name = getUniqueComponentName(mContext.getPackageName()); + final PackageManager pm = mContext.getPackageManager(); + spyOn(pm); + updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */); + updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */); + final ActivityRecord optOutAppActivity = new ActivityBuilder(mAtm) + .setComponent(name).setTask(mTask).build(); + optOutAppActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true); + + // Since application manifest property is defined as false, the activity can not be + // letterboxed for safe region + assertFalse(optOutAppActivity.mAppCompatController + .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed()); + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testIsLetterboxedForSafeRegionOnlyAllowed_allowedForApplication_returnsTrue() { + setUpLandscapeLargeScreenDisplayWithApp(); + + assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); + + setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200); + + final ComponentName name = getUniqueComponentName(mContext.getPackageName()); + final PackageManager pm = mContext.getPackageManager(); + spyOn(pm); + updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */); + updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, true /* value */); + final ActivityRecord optOutAppActivity = new ActivityBuilder(mAtm) + .setComponent(name).setTask(mTask).build(); + optOutAppActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true); + + // Since application manifest property is defined as true, the activity can be letterboxed + // for safe region + assertTrue(optOutAppActivity.mAppCompatController + .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed()); + } + + private void updateApplicationLevelAllowSafeRegionLetterboxingProperty(ComponentName name, + PackageManager pm, boolean propertyValueForApplication) { + final PackageManager.Property propertyForApplication = new PackageManager.Property( + "propertyName", /* value */ propertyValueForApplication, name.getPackageName(), + name.getClassName()); + try { + doReturn(propertyForApplication).when(pm).getPropertyAsUser( + WindowManager.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING, + name.getPackageName(), /* className */ null, /* userId */ 0); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + } + + private void updateActivityLevelAllowSafeRegionLetterboxingProperty(ComponentName name, + PackageManager pm, boolean propertyValueForActivity) { + final PackageManager.Property propertyForActivity = new PackageManager.Property( + "propertyName", /* value */ propertyValueForActivity, name.getPackageName(), + name.getClassName()); + try { + doReturn(propertyForActivity).when(pm).getPropertyAsUser( + WindowManager.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING, + name.getPackageName(), name.getClassName(), /* userId */ 0); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testAreBoundsLetterboxed_letterboxedForSafeRegionAndFixedOrientation_returnTrue() { + setUpLandscapeLargeScreenDisplayWithApp(); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); + + final Rect safeRegionBounds = setupSafeRegionBoundsParameters(/* dw */ 500, /* dh */ 200); + + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + // Activity is letterboxed due to fixed orientation within the safe region + assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy() + .isLetterboxedForFixedOrientationAndAspectRatio()); + assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION); + assertFalse(mActivity.mAppCompatController.getSafeRegionPolicy() + .isLetterboxedForSafeRegionOnlyAllowed()); + assertTrue(safeRegionBounds.contains(mActivity.getBounds())); + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testAreBoundsLetterboxed_letterboxedForSafeRegionAndAspectRatio_returnTrue() { + setUpPortraitLargeScreenDisplayWithApp(); + + assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); + + final Rect safeRegionBounds = setupSafeRegionBoundsParameters(/* dw */ 200, /* dh */ 300); + + prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT); + + // Activity is letterboxed due to min aspect ratio within the safe region + assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy() + .isLetterboxedForFixedOrientationAndAspectRatio()); + assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy() + .isLetterboxedForAspectRatioOnly()); + assertFalse(mActivity.inSizeCompatMode()); + assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO); + assertTrue(safeRegionBounds.contains(mActivity.getBounds())); + } + + private Rect setupSafeRegionBoundsParameters(int dw, int dh) { + final AppCompatController appCompatController = mActivity.mAppCompatController; + final AppCompatSafeRegionPolicy safeRegionPolicy = + appCompatController.getSafeRegionPolicy(); + safeRegionPolicy.setNeedsSafeRegionBounds(true); + spyOn(mTask); + final Rect safeRegionBounds = new Rect(100, 200, 100 + dw, 200 + dh); + doReturn(safeRegionBounds).when(mTask).getSafeRegionBounds(); + return safeRegionBounds; + } + + @Test public void testAreBoundsLetterboxed_letterboxedForSizeCompat_returnsTrue() { setUpDisplaySizeWithApp(1000, 2500); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java index b2c195e8ebaa..547fc04f9fac 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java @@ -153,7 +153,7 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase { path = FILES_DIR.getPath() + "/snapshots/"; } for (int i = 0; i < fileNames.length; i++) { - files[i] = new File(path + fileNames[i]); + files[i] = new File(path, fileNames[i]); } return files; } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 4f310de1e48b..e0e65e67762e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -1431,6 +1431,93 @@ public class WindowContainerTests extends WindowTestsBase { } @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testSetSafeRegionBounds_appliedOnNodeAndChildren() { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm); + final TestWindowContainer root = builder.setLayer(0).build(); + + final TestWindowContainer child1 = root.addChildWindow(); + final TestWindowContainer child2 = root.addChildWindow(); + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child12 = child1.addChildWindow(); + final TestWindowContainer child21 = child2.addChildWindow(); + + assertNull(root.getSafeRegionBounds()); + assertNull(child1.getSafeRegionBounds()); + assertNull(child11.getSafeRegionBounds()); + assertNull(child12.getSafeRegionBounds()); + assertNull(child2.getSafeRegionBounds()); + assertNull(child21.getSafeRegionBounds()); + + final Rect tempSafeRegionBounds1 = new Rect(50, 50, 200, 300); + child1.setSafeRegionBounds(tempSafeRegionBounds1); + + assertNull(root.getSafeRegionBounds()); + assertEquals(tempSafeRegionBounds1, child1.getSafeRegionBounds()); + assertEquals(tempSafeRegionBounds1, child11.getSafeRegionBounds()); + assertEquals(tempSafeRegionBounds1, child12.getSafeRegionBounds()); + assertNull(child2.getSafeRegionBounds()); + assertNull(child21.getSafeRegionBounds()); + + // Set different safe region bounds on child11 + final Rect tempSafeRegionBounds2 = new Rect(30, 30, 200, 200); + child11.setSafeRegionBounds(tempSafeRegionBounds2); + + assertNull(root.getSafeRegionBounds()); + assertEquals(tempSafeRegionBounds1, child1.getSafeRegionBounds()); + assertEquals(tempSafeRegionBounds2, child11.getSafeRegionBounds()); + assertEquals(tempSafeRegionBounds1, child12.getSafeRegionBounds()); + assertNull(child2.getSafeRegionBounds()); + assertNull(child21.getSafeRegionBounds()); + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testSetSafeRegionBounds_resetSafeRegionBounds() { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm); + final TestWindowContainer root = builder.setLayer(0).build(); + + final TestWindowContainer child1 = root.addChildWindow(); + final TestWindowContainer child2 = root.addChildWindow(); + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child12 = child1.addChildWindow(); + final TestWindowContainer child21 = child2.addChildWindow(); + + assertNull(root.getSafeRegionBounds()); + assertNull(child1.getSafeRegionBounds()); + assertNull(child11.getSafeRegionBounds()); + assertNull(child12.getSafeRegionBounds()); + assertNull(child2.getSafeRegionBounds()); + assertNull(child21.getSafeRegionBounds()); + + final Rect tempSafeRegionBounds1 = new Rect(50, 50, 200, 300); + child1.setSafeRegionBounds(tempSafeRegionBounds1); + + assertNull(root.getSafeRegionBounds()); + assertEquals(tempSafeRegionBounds1, child1.getSafeRegionBounds()); + assertEquals(tempSafeRegionBounds1, child11.getSafeRegionBounds()); + assertEquals(tempSafeRegionBounds1, child12.getSafeRegionBounds()); + assertNull(child2.getSafeRegionBounds()); + assertNull(child21.getSafeRegionBounds()); + + // Set different safe region bounds on child11 + final Rect tempSafeRegionBounds2 = new Rect(30, 30, 200, 200); + child11.setSafeRegionBounds(tempSafeRegionBounds2); + + assertEquals(tempSafeRegionBounds2, child11.getSafeRegionBounds()); + + // Reset safe region bounds on child11. Now child11 will use child1 safe region bounds. + child11.setSafeRegionBounds(/* safeRegionBounds */null); + + assertNull(root.getSafeRegionBounds()); + assertEquals(tempSafeRegionBounds1, child1.getSafeRegionBounds()); + assertEquals(tempSafeRegionBounds1, child11.getSafeRegionBounds()); + assertEquals(tempSafeRegionBounds1, child12.getSafeRegionBounds()); + assertNull(child2.getSafeRegionBounds()); + assertNull(child21.getSafeRegionBounds()); + } + + @Test public void testSetExcludeInsetsTypes_appliedOnNodeAndChildren() { final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm); final TestWindowContainer root = builder.setLayer(0).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java index dcb68620e361..c0be214241a0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java @@ -36,8 +36,10 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.times; import android.content.Intent; +import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -46,6 +48,8 @@ import android.window.WindowContainerTransaction.HierarchyOp; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; + import org.junit.Test; import org.junit.runner.RunWith; @@ -62,6 +66,8 @@ import java.util.List; @Presubmit @RunWith(WindowTestRunner.class) public class WindowContainerTransactionTests extends WindowTestsBase { + private final Rect mSafeRegionBounds = new Rect(50, 50, 200, 300); + @Test public void testRemoveTask() { final Task rootTask = createTask(mDisplayContent); @@ -209,6 +215,152 @@ public class WindowContainerTransactionTests extends WindowTestsBase { } @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testSetSafeRegionBoundsOnTaskDisplayArea() { + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final WindowContainerToken token = taskDisplayArea.mRemoteToken.toWindowContainerToken(); + // Set safe region bounds on the task display area + wct.setSafeRegionBounds(token, mSafeRegionBounds); + applyTransaction(wct); + + assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds); + assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds); + assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds); + assertEquals(taskDisplayArea.getSafeRegionBounds(), mSafeRegionBounds); + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testSetSafeRegionBoundsOnRootTask() { + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final WindowContainerToken token = rootTask.mRemoteToken.toWindowContainerToken(); + // Set safe region bounds on the root task + wct.setSafeRegionBounds(token, mSafeRegionBounds); + applyTransaction(wct); + + assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds); + assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds); + assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds); + assertNull(taskDisplayArea.getSafeRegionBounds()); + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testSetSafeRegionBoundsOnTask() { + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final WindowContainerToken token = task.mRemoteToken.toWindowContainerToken(); + // Set safe region bounds on the task + wct.setSafeRegionBounds(token, mSafeRegionBounds); + applyTransaction(wct); + + assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds); + assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds); + assertNull(rootTask.getSafeRegionBounds()); + assertNull(taskDisplayArea.getSafeRegionBounds()); + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testSetSafeRegionBoundsOnTask_resetSafeRegionBounds() { + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final WindowContainerToken token = task.mRemoteToken.toWindowContainerToken(); + // Set safe region bounds on the task + wct.setSafeRegionBounds(token, mSafeRegionBounds); + applyTransaction(wct); + + assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds); + assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds); + assertNull(rootTask.getSafeRegionBounds()); + assertNull(taskDisplayArea.getSafeRegionBounds()); + + // Reset safe region bounds on the task + wct.setSafeRegionBounds(token, /* safeRegionBounds */null); + applyTransaction(wct); + + assertNull(activity.getSafeRegionBounds()); + assertNull(task.getSafeRegionBounds()); + assertNull(rootTask.getSafeRegionBounds()); + assertNull(taskDisplayArea.getSafeRegionBounds()); + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testSetSafeRegionBoundsOnRootTaskAndTask() { + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final WindowContainerToken token = rootTask.mRemoteToken.toWindowContainerToken(); + // Set safe region bounds on the root task + wct.setSafeRegionBounds(token, mSafeRegionBounds); + // Set different safe region bounds on task + final Rect tempSafeRegionBounds = new Rect(30, 30, 200, 200); + wct.setSafeRegionBounds(task.mRemoteToken.toWindowContainerToken(), tempSafeRegionBounds); + applyTransaction(wct); + + assertEquals(activity.getSafeRegionBounds(), tempSafeRegionBounds); + assertEquals(task.getSafeRegionBounds(), tempSafeRegionBounds); + assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds); + assertNull(taskDisplayArea.getSafeRegionBounds()); + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testSetSafeRegionBoundsOnRootTaskAndTask_resetSafeRegionBoundsOnTask() { + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final WindowContainerToken token = rootTask.mRemoteToken.toWindowContainerToken(); + // Set safe region bounds on the root task + wct.setSafeRegionBounds(token, mSafeRegionBounds); + // Set different safe region bounds on task + final Rect mTmpSafeRegionBounds = new Rect(30, 30, 200, 200); + wct.setSafeRegionBounds(task.mRemoteToken.toWindowContainerToken(), mTmpSafeRegionBounds); + applyTransaction(wct); + + // Task and activity will use different safe region bounds + assertEquals(activity.getSafeRegionBounds(), mTmpSafeRegionBounds); + assertEquals(task.getSafeRegionBounds(), mTmpSafeRegionBounds); + assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds); + assertNull(taskDisplayArea.getSafeRegionBounds()); + + // Reset safe region bounds on task + wct.setSafeRegionBounds(task.mRemoteToken.toWindowContainerToken(), + /* safeRegionBounds */null); + applyTransaction(wct); + + assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds); + assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds); + assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds); + assertNull(taskDisplayArea.getSafeRegionBounds()); + } + + @Test public void testDesktopMode_moveTaskToFront() { final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm); TaskDisplayArea tda = desktopOrganizer.mDefaultTDA; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java index 8606581539ff..669f5d28267d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java @@ -24,7 +24,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import android.platform.test.annotations.Presubmit; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 5401a44d7016..6a35fa8e20ad 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -61,6 +61,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -1568,6 +1569,51 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testSetSafeRegionBoundsOnRootTask() { + Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent, WINDOWING_MODE_FULLSCREEN, null); + final Task task1 = createRootTask(); + final Task task2 = createTask(rootTask, false /* fakeDraw */); + WindowContainerTransaction wct = new WindowContainerTransaction(); + Rect safeRegionBounds = new Rect(50, 50, 200, 300); + + wct.setSafeRegionBounds(rootTask.mRemoteToken.toWindowContainerToken(), safeRegionBounds); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + + assertEquals(rootTask.getSafeRegionBounds(), safeRegionBounds); + assertEquals(task2.getSafeRegionBounds(), safeRegionBounds); + assertNull(task1.getSafeRegionBounds()); + } + + @Test + @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING) + public void testSetSafeRegionBoundsOnRootTask_resetSafeRegionBounds() { + Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent, WINDOWING_MODE_FULLSCREEN, null); + final Task task1 = createRootTask(); + final Task task2 = createTask(rootTask, false /* fakeDraw */); + WindowContainerTransaction wct = new WindowContainerTransaction(); + Rect safeRegionBounds = new Rect(50, 50, 200, 300); + + wct.setSafeRegionBounds(rootTask.mRemoteToken.toWindowContainerToken(), safeRegionBounds); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + + assertEquals(rootTask.getSafeRegionBounds(), safeRegionBounds); + assertEquals(task2.getSafeRegionBounds(), safeRegionBounds); + assertNull(task1.getSafeRegionBounds()); + + // Reset safe region bounds on the root task + wct.setSafeRegionBounds(rootTask.mRemoteToken.toWindowContainerToken(), + /* safeRegionBounds */null); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + + assertNull(rootTask.getSafeRegionBounds()); + assertNull(task2.getSafeRegionBounds()); + assertNull(task1.getSafeRegionBounds()); + } + + @Test public void testReparentToOrganizedTask() { final ITaskOrganizer organizer = registerMockOrganizer(); Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask( diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 59ee2f5c8e9f..5624677779a2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -23,9 +23,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.InsetsSource.ID_IME; -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_270; -import static android.view.Surface.ROTATION_90; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; @@ -88,7 +85,6 @@ import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; -import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -673,56 +669,6 @@ public class WindowStateTests extends WindowTestsBase { } @Test - public void testSeamlesslyRotateWindow() { - final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); - final SurfaceControl.Transaction t = spy(StubTransaction.class); - - makeWindowVisible(app); - app.mSurfaceControl = mock(SurfaceControl.class); - final Rect frame = app.getFrame(); - frame.set(10, 20, 60, 80); - app.updateSurfacePosition(t); - assertTrue(app.mLastSurfacePosition.equals(frame.left, frame.top)); - app.seamlesslyRotateIfAllowed(t, ROTATION_0, ROTATION_90, true /* requested */); - assertTrue(app.mSeamlesslyRotated); - - // Verify we un-rotate the window state surface. - final Matrix matrix = new Matrix(); - // Un-rotate 90 deg. - matrix.setRotate(270); - // Translate it back to origin. - matrix.postTranslate(0, mDisplayInfo.logicalWidth); - verify(t).setMatrix(eq(app.mSurfaceControl), eq(matrix), any(float[].class)); - - // Verify we update the position as well. - final float[] curSurfacePos = {app.mLastSurfacePosition.x, app.mLastSurfacePosition.y}; - matrix.mapPoints(curSurfacePos); - verify(t).setPosition(eq(app.mSurfaceControl), eq(curSurfacePos[0]), eq(curSurfacePos[1])); - - app.finishSeamlessRotation(t); - assertFalse(app.mSeamlesslyRotated); - assertNull(app.mPendingSeamlessRotate); - - // Simulate the case with deferred layout and animation. - app.resetSurfacePositionForAnimationLeash(t); - clearInvocations(t); - mWm.mWindowPlacerLocked.deferLayout(); - app.updateSurfacePosition(t); - // Because layout is deferred, the position should keep the reset value. - assertTrue(app.mLastSurfacePosition.equals(0, 0)); - - app.seamlesslyRotateIfAllowed(t, ROTATION_0, ROTATION_270, true /* requested */); - // The last position must be updated so the surface can be unrotated properly. - assertTrue(app.mLastSurfacePosition.equals(frame.left, frame.top)); - matrix.setRotate(90); - matrix.postTranslate(mDisplayInfo.logicalHeight, 0); - curSurfacePos[0] = frame.left; - curSurfacePos[1] = frame.top; - matrix.mapPoints(curSurfacePos); - verify(t).setPosition(eq(app.mSurfaceControl), eq(curSurfacePos[0]), eq(curSurfacePos[1])); - } - - @Test public void testVisibilityChangeSwitchUser() { final WindowState window = newWindowBuilder("app", TYPE_APPLICATION).build(); window.mHasSurface = true; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java index a02c3db1e636..8907a72e0b34 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java @@ -17,6 +17,8 @@ package com.android.server.wm; import static android.view.InsetsSource.ID_IME; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_90; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -35,16 +37,19 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Configuration; +import android.graphics.Matrix; import android.os.Bundle; import android.os.IBinder; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.view.SurfaceControl; import android.view.WindowInsets; import android.window.WindowContext; @@ -335,6 +340,31 @@ public class WindowTokenTests extends WindowTestsBase { } @Test + public void testSeamlesslyRotate() { + final SurfaceControl.Transaction t = mTransaction; + final TestWindowToken token = createTestWindowToken(0, mDisplayContent); + token.mLastSurfacePosition.x = 10; + token.mLastSurfacePosition.y = 20; + final SeamlessRotator rotator = new SeamlessRotator(ROTATION_0, ROTATION_90, + mDisplayContent.getDisplayInfo(), false /* applyFixedTransformationHint */); + clearInvocations(t); + rotator.unrotate(t, token); + + // Verify surface is un-rotated. + final Matrix matrix = new Matrix(); + // Un-rotate 90 deg. + matrix.setRotate(270); + // Translate it back to origin. + matrix.postTranslate(0, mDisplayInfo.logicalWidth); + verify(t).setMatrix(eq(token.mSurfaceControl), eq(matrix), any(float[].class)); + + final float[] curSurfacePos = {token.mLastSurfacePosition.x, token.mLastSurfacePosition.y}; + matrix.mapPoints(curSurfacePos); + verify(t).setPosition(eq(token.mSurfaceControl), + eq(curSurfacePos[0]), eq(curSurfacePos[1])); + } + + @Test @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API) public void onDisplayChanged_differentDisplay_reparented() { final TestWindowToken token = createTestWindowToken(0, mDisplayContent); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java index a1d35a7d447c..0749c0b94537 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java @@ -22,7 +22,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -118,7 +118,7 @@ public class WindowTracingLegacyTest { @Test public void trace_discared_whenNotTracing() { mWindowTracing.logState("where"); - verifyZeroInteractions(mWmMock); + verifyNoMoreInteractions(mWmMock); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java index 9367941e32a3..3da279bb9663 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java @@ -22,7 +22,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -128,7 +128,7 @@ public class WindowTracingPerfettoTest { @Test public void trace_ignoresLogStateCalls_ifTracingIsDisabled() { sWindowTracing.logState("where"); - verifyZeroInteractions(sWmMock); + verifyNoMoreInteractions(sWmMock); } @Test diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 50c5a6b68c7b..14915109999c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -3918,6 +3918,22 @@ public class CarrierConfigManager { "5g_icon_display_secondary_grace_period_string"; /** + * When an NR advanced connection is lost and a Physical Cell ID (PCI) change occurs within + * the primary timer{@link #KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING}, delay updating the network + * icon. + * + * <p>This delay is implemented because a rapid PCI change often indicates the device is + * switching to a nearby cell tower to quickly restore the NR advanced connection. Displaying + * an intermediate network icon (like 4G/LTE) might be misleading if the 5G connection is + * restored shortly after. This value sets the delay in seconds; 0 disables the feature.</p> + * + * @hide + */ + public static final String KEY_NR_ADVANCED_PCI_CHANGE_SECONDARY_TIMER_SECONDS_INT = + "nr_advanced_pci_change_secondary_timer_seconds_int"; + + + /** * The secondary grace periods in seconds to use if NR advanced icon was shown due to connecting * to bands specified in {@link #KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY}. * @@ -11222,6 +11238,7 @@ public class CarrierConfigManager { + "not_restricted_rrc_con:5G"); sDefaults.putString(KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, ""); sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, ""); + sDefaults.putInt(KEY_NR_ADVANCED_PCI_CHANGE_SECONDARY_TIMER_SECONDS_INT, 0); sDefaults.putInt(KEY_NR_ADVANCED_BANDS_SECONDARY_TIMER_SECONDS_INT, 0); sDefaults.putBoolean(KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL, false); sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, false); diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index f5282639ae6c..6e23edf936c7 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -614,7 +614,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P /** @hide */ public static int convertRssiAsuToDBm(int rssiAsu) { - if (rssiAsu == SIGNAL_STRENGTH_LTE_RSSI_ASU_UNKNOWN) { + if (rssiAsu == SIGNAL_STRENGTH_LTE_RSSI_ASU_UNKNOWN || rssiAsu == Integer.MAX_VALUE) { return CellInfo.UNAVAILABLE; } if ((rssiAsu < SIGNAL_STRENGTH_LTE_RSSI_VALID_ASU_MIN_VALUE diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java index e6515f13b858..850ce3e2bbdc 100644 --- a/telephony/java/android/telephony/PreciseDataConnectionState.java +++ b/telephony/java/android/telephony/PreciseDataConnectionState.java @@ -16,7 +16,6 @@ package android.telephony; -import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -39,7 +38,6 @@ import android.telephony.data.ApnSetting; import android.telephony.data.DataCallResponse; import android.telephony.data.Qos; -import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.util.TelephonyUtils; import java.lang.annotation.Retention; @@ -89,35 +87,30 @@ public final class PreciseDataConnectionState implements Parcelable { * Unsupported. The unsupported state is used when the data network cannot support the network * validation function for the current data connection state. */ - @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) public static final int NETWORK_VALIDATION_UNSUPPORTED = 0; /** * Not Requested. The not requested status is used when the data network supports the network * validation function, but no network validation is being performed yet. */ - @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) public static final int NETWORK_VALIDATION_NOT_REQUESTED = 1; /** * In progress. The in progress state is used when the network validation process for the data * network is in progress. This state is followed by either success or failure. */ - @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) public static final int NETWORK_VALIDATION_IN_PROGRESS = 2; /** * Success. The Success status is used when network validation has been completed for the data * network and the result is successful. */ - @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) public static final int NETWORK_VALIDATION_SUCCESS = 3; /** * Failure. The Failure status is used when network validation has been completed for the data * network and the result is failure. */ - @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) public static final int NETWORK_VALIDATION_FAILURE = 4; /** @@ -360,7 +353,6 @@ public final class PreciseDataConnectionState implements Parcelable { * * @return the network validation status of the data call */ - @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) public @NetworkValidationStatus int getNetworkValidationStatus() { return mNetworkValidationStatus; } @@ -615,7 +607,6 @@ public final class PreciseDataConnectionState implements Parcelable { * @param networkValidationStatus the network validation status of the data call * @return The builder */ - @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) public @NonNull Builder setNetworkValidationStatus( @NetworkValidationStatus int networkValidationStatus) { mNetworkValidationStatus = networkValidationStatus; diff --git a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java index 7356cdc8889b..42d09cfc2e43 100644 --- a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java +++ b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java @@ -31,7 +31,6 @@ import android.telephony.euicc.EuiccManager; import android.telephony.ims.ImsManager; import android.telephony.satellite.SatelliteManager; -import com.android.internal.telephony.flags.Flags; import com.android.internal.util.Preconditions; @@ -77,9 +76,6 @@ public class TelephonyFrameworkInitializer { // also check through Compatibility framework a few lines below). @SuppressWarnings("AndroidFrameworkCompatChange") private static boolean hasSystemFeature(Context context, String feature) { - // Check release status of this change in behavior. - if (!Flags.minimalTelephonyManagersConditionalOnFeatures()) return true; - // Check SDK version of the vendor partition. final int vendorApiLevel = SystemProperties.getInt( "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 14d567d141cb..2983e4442a78 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -691,7 +691,7 @@ public class TelephonyManager { case UNKNOWN: modemCount = 1; // check for voice and data support, 0 if not supported - if (!isVoiceCapable() && !isSmsCapable() && !isDataCapable()) { + if (!isDeviceVoiceCapable() && !isSmsCapable() && !isDataCapable()) { modemCount = 0; } break; @@ -2814,7 +2814,7 @@ public class TelephonyManager { */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY) public int getPhoneType() { - if (!isVoiceCapable() && !isDataCapable()) { + if (!isDeviceVoiceCapable() && !isDataCapable()) { return PHONE_TYPE_NONE; } return getCurrentPhoneType(); diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index b6f9e1f4c3af..0e7030688c70 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -17,7 +17,6 @@ package android.telephony.data; -import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -32,7 +31,6 @@ import android.telephony.PreciseDataConnectionState; import android.telephony.data.ApnSetting.ProtocolType; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.flags.Flags; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; @@ -455,7 +453,6 @@ public final class DataCallResponse implements Parcelable { * * @return The network validation status of data connection. */ - @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) public @PreciseDataConnectionState.NetworkValidationStatus int getNetworkValidationStatus() { return mNetworkValidationStatus; } @@ -936,7 +933,6 @@ public final class DataCallResponse implements Parcelable { * @param status The network validation status. * @return The same instance of the builder. */ - @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) public @NonNull Builder setNetworkValidationStatus( @PreciseDataConnectionState.NetworkValidationStatus int status) { mNetworkValidationStatus = status; diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index f04e1c9b221d..5baf46388fc4 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -17,7 +17,6 @@ package android.telephony.data; import android.annotation.CallbackExecutor; -import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -40,7 +39,6 @@ import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IIntegerConsumer; -import com.android.internal.telephony.flags.Flags; import com.android.internal.util.FunctionalUtils; import com.android.telephony.Rlog; @@ -414,7 +412,6 @@ public abstract class DataService extends Service { * @param resultCodeCallback Listener for the {@link DataServiceCallback.ResultCode} that * request validation to the DataService and checks if the request has been submitted. */ - @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) public void requestNetworkValidation(int cid, @NonNull @CallbackExecutor Executor executor, @NonNull @DataServiceCallback.ResultCode Consumer<Integer> resultCodeCallback) { diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java index f775de6ebef4..c42b29c143aa 100644 --- a/telephony/java/android/telephony/data/QualifiedNetworksService.java +++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java @@ -17,7 +17,6 @@ package android.telephony.data; import android.annotation.CallbackExecutor; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.app.Service; @@ -41,7 +40,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IIntegerConsumer; import com.android.internal.telephony.flags.FeatureFlags; import com.android.internal.telephony.flags.FeatureFlagsImpl; -import com.android.internal.telephony.flags.Flags; import com.android.internal.util.FunctionalUtils; import com.android.telephony.Rlog; @@ -290,7 +288,6 @@ public abstract class QualifiedNetworksService extends Service { * @param resultCodeCallback A callback to determine whether the request was successfully * submitted or not. */ - @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) public void requestNetworkValidation( @NetCapability int networkCapability, @NonNull @CallbackExecutor Executor executor, @@ -298,15 +295,6 @@ public abstract class QualifiedNetworksService extends Service { Objects.requireNonNull(executor, "executor cannot be null"); Objects.requireNonNull(resultCodeCallback, "resultCodeCallback cannot be null"); - if (!sFeatureFlag.networkValidation()) { - loge("networkValidation feature is disabled"); - executor.execute( - () -> - resultCodeCallback.accept( - DataServiceCallback.RESULT_ERROR_UNSUPPORTED)); - return; - } - IIntegerConsumer callback = new IIntegerConsumer.Stub() { @Override public void accept(int result) { diff --git a/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java index d16e90e26aaa..9d60e7c8b0b0 100644 --- a/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java +++ b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java @@ -20,7 +20,7 @@ import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStat import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyObject; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; @@ -123,7 +123,7 @@ public class AppLocaleCollectorTest { doReturn(mAllAppActiveLocales).when(mAppLocaleCollector).getAllAppActiveLocales(); doReturn(mImeLocales).when(mAppLocaleCollector).getActiveImeLocales(); doReturn(mSystemSupportedLocales).when(mAppLocaleCollector).getSystemSupportedLocale( - anyObject(), eq(null), eq(true)); + any(), eq(null), eq(true)); doReturn(mSystemCurrentLocales).when( mAppLocaleCollector).getSystemCurrentLocales(); @@ -159,7 +159,7 @@ public class AppLocaleCollectorTest { doReturn(mAllAppActiveLocales).when(mAppLocaleCollector).getAllAppActiveLocales(); doReturn(mImeLocales).when(mAppLocaleCollector).getActiveImeLocales(); doReturn(mSystemSupportedLocales).when(mAppLocaleCollector).getSystemSupportedLocale( - anyObject(), eq(null), eq(true)); + any(), eq(null), eq(true)); doReturn(mSystemCurrentLocales).when( mAppLocaleCollector).getSystemCurrentLocales(); diff --git a/tests/PackageWatchdog/src/com/android/server/RescuePartyTest.java b/tests/PackageWatchdog/src/com/android/server/RescuePartyTest.java new file mode 100644 index 000000000000..eda5e8613dba --- /dev/null +++ b/tests/PackageWatchdog/src/com/android/server/RescuePartyTest.java @@ -0,0 +1,523 @@ +/* + * Copyright (C) 2019 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; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS; +import static com.android.server.RescueParty.DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN; +import static com.android.server.RescueParty.LEVEL_FACTORY_RESET; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.VersionedPackage; +import android.os.RecoverySystem; +import android.os.SystemProperties; +import android.provider.DeviceConfig; +import android.provider.Settings; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.server.PackageWatchdog.PackageHealthObserverImpact; +import com.android.server.RescueParty.RescuePartyObserver; +import com.android.server.am.SettingsToPropertiesMapper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.TimeUnit; + + +/** + * Test RescueParty. + */ +public class RescuePartyTest { + private static final long CURRENT_NETWORK_TIME_MILLIS = 0L; + + private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1); + private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; + private static final String PERSISTENT_PACKAGE = "com.persistent.package"; + private static final String NON_PERSISTENT_PACKAGE = "com.nonpersistent.package"; + private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = + "persist.device_config.configuration.disable_rescue_party"; + private static final String PROP_DISABLE_FACTORY_RESET_FLAG = + "persist.device_config.configuration.disable_rescue_party_factory_reset"; + + private MockitoSession mSession; + private HashMap<String, String> mSystemSettingsMap; + private HashMap<String, String> mCrashRecoveryPropertiesMap; + //Records the namespaces wiped by setProperties(). + private HashSet<String> mNamespacesWiped; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mMockContext; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PackageWatchdog mMockPackageWatchdog; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private ContentResolver mMockContentResolver; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PackageManager mPackageManager; + + // Mock only sysprop apis + private PackageWatchdog.BootThreshold mSpyBootThreshold; + + @Before + public void setUp() throws Exception { + mSession = + ExtendedMockito.mockitoSession().initMocks( + this) + .strictness(Strictness.LENIENT) + .spyStatic(DeviceConfig.class) + .spyStatic(SystemProperties.class) + .spyStatic(Settings.Global.class) + .spyStatic(Settings.Secure.class) + .spyStatic(SettingsToPropertiesMapper.class) + .spyStatic(RecoverySystem.class) + .spyStatic(RescueParty.class) + .spyStatic(PackageWatchdog.class) + .startMocking(); + mSystemSettingsMap = new HashMap<>(); + mNamespacesWiped = new HashSet<>(); + + when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); + when(mMockContext.getPackageManager()).thenReturn(mPackageManager); + ApplicationInfo persistentApplicationInfo = new ApplicationInfo(); + persistentApplicationInfo.flags |= + ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT; + + // If the package name is PERSISTENT_PACKAGE, then set the flags to be persistent and + // system. Don't set any flags otherwise. + when(mPackageManager.getApplicationInfo(eq(PERSISTENT_PACKAGE), + anyInt())).thenReturn(persistentApplicationInfo); + when(mPackageManager.getApplicationInfo(eq(NON_PERSISTENT_PACKAGE), + anyInt())).thenReturn(new ApplicationInfo()); + // Reset observer instance to get new mock context on every run + RescuePartyObserver.reset(); + + // Mock SystemProperties setter and various getters + doAnswer((Answer<Void>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + String value = invocationOnMock.getArgument(1); + + mSystemSettingsMap.put(key, value); + return null; + } + ).when(() -> SystemProperties.set(anyString(), anyString())); + + doAnswer((Answer<Boolean>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + boolean defaultValue = invocationOnMock.getArgument(1); + + String storedValue = mSystemSettingsMap.get(key); + return storedValue == null ? defaultValue : Boolean.parseBoolean(storedValue); + } + ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean())); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + int defaultValue = invocationOnMock.getArgument(1); + + String storedValue = mSystemSettingsMap.get(key); + return storedValue == null ? defaultValue : Integer.parseInt(storedValue); + } + ).when(() -> SystemProperties.getInt(anyString(), anyInt())); + + doAnswer((Answer<Long>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + long defaultValue = invocationOnMock.getArgument(1); + + String storedValue = mSystemSettingsMap.get(key); + return storedValue == null ? defaultValue : Long.parseLong(storedValue); + } + ).when(() -> SystemProperties.getLong(anyString(), anyLong())); + + // Mock DeviceConfig + doAnswer((Answer<Boolean>) invocationOnMock -> true) + .when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(), + anyBoolean())); + doAnswer((Answer<Void>) invocationOnMock -> null) + .when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString())); + doAnswer((Answer<Boolean>) invocationOnMock -> { + DeviceConfig.Properties properties = invocationOnMock.getArgument(0); + String namespace = properties.getNamespace(); + // record a wipe + if (properties.getKeyset().isEmpty()) { + mNamespacesWiped.add(namespace); + } + return true; + } + ).when(() -> DeviceConfig.setProperties(any(DeviceConfig.Properties.class))); + + // Mock PackageWatchdog + doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog) + .when(() -> PackageWatchdog.getInstance(mMockContext)); + mockCrashRecoveryProperties(mMockPackageWatchdog); + + doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime()); + + setCrashRecoveryPropRescueBootCount(0); + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); + SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false)); + } + + @After + public void tearDown() throws Exception { + mSession.finishMocking(); + } + + @Test + public void testBootLoopNoFlags() { + // this is old test where the flag needs to be disabled + noteBoot(1); + assertTrue(RescueParty.isRebootPropertySet()); + + setCrashRecoveryPropAttemptingReboot(false); + noteBoot(2); + assertTrue(RescueParty.isFactoryResetPropertySet()); + } + + @Test + public void testPersistentAppCrashNoFlags() { + // this is old test where the flag needs to be disabled + noteAppCrash(1, true); + assertTrue(RescueParty.isRebootPropertySet()); + + setCrashRecoveryPropAttemptingReboot(false); + noteAppCrash(2, true); + assertTrue(RescueParty.isFactoryResetPropertySet()); + } + + @Test + public void testIsRecoveryTriggeredReboot() { + for (int i = 0; i < LEVEL_FACTORY_RESET; i++) { + noteBoot(i + 1); + } + assertFalse(RescueParty.isFactoryResetPropertySet()); + setCrashRecoveryPropAttemptingReboot(false); + noteBoot(LEVEL_FACTORY_RESET + 1); + assertTrue(RescueParty.isRecoveryTriggeredReboot()); + assertTrue(RescueParty.isFactoryResetPropertySet()); + } + + @Test + public void testIsRecoveryTriggeredRebootOnlyAfterRebootCompleted() { + for (int i = 0; i < LEVEL_FACTORY_RESET; i++) { + noteBoot(i + 1); + } + int mitigationCount = LEVEL_FACTORY_RESET + 1; + assertFalse(RescueParty.isFactoryResetPropertySet()); + noteBoot(mitigationCount++); + assertFalse(RescueParty.isFactoryResetPropertySet()); + noteBoot(mitigationCount++); + assertFalse(RescueParty.isFactoryResetPropertySet()); + noteBoot(mitigationCount++); + setCrashRecoveryPropAttemptingReboot(false); + noteBoot(mitigationCount + 1); + assertTrue(RescueParty.isRecoveryTriggeredReboot()); + assertTrue(RescueParty.isFactoryResetPropertySet()); + } + + @Test + public void testThrottlingOnBootFailures() { + setCrashRecoveryPropAttemptingReboot(false); + long now = System.currentTimeMillis(); + long beforeTimeout = now - TimeUnit.MINUTES.toMillis( + DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN - 1); + setCrashRecoveryPropLastFactoryReset(beforeTimeout); + for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) { + noteBoot(i); + } + assertFalse(RescueParty.isRecoveryTriggeredReboot()); + } + + @Test + public void testThrottlingOnAppCrash() { + setCrashRecoveryPropAttemptingReboot(false); + long now = System.currentTimeMillis(); + long beforeTimeout = now - TimeUnit.MINUTES.toMillis( + DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN - 1); + setCrashRecoveryPropLastFactoryReset(beforeTimeout); + for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) { + noteAppCrash(i + 1, true); + } + assertFalse(RescueParty.isRecoveryTriggeredReboot()); + } + + @Test + public void testNotThrottlingAfterTimeoutOnBootFailures() { + setCrashRecoveryPropAttemptingReboot(false); + long now = System.currentTimeMillis(); + long afterTimeout = now - TimeUnit.MINUTES.toMillis( + DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN + 1); + setCrashRecoveryPropLastFactoryReset(afterTimeout); + for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) { + noteBoot(i); + } + assertTrue(RescueParty.isRecoveryTriggeredReboot()); + } + + @Test + public void testNotThrottlingAfterTimeoutOnAppCrash() { + when(mMockContext.getPackageManager()).thenReturn(mPackageManager); + setCrashRecoveryPropAttemptingReboot(false); + long now = System.currentTimeMillis(); + long afterTimeout = now - TimeUnit.MINUTES.toMillis( + DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN + 1); + setCrashRecoveryPropLastFactoryReset(afterTimeout); + for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) { + noteAppCrash(i + 1, true); + } + assertTrue(RescueParty.isRecoveryTriggeredReboot()); + } + + @Test + public void testExplicitlyEnablingAndDisablingRescue() { + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); + SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true)); + assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation( + sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), + MITIGATION_RESULT_SKIPPED); + + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); + assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation( + sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), + MITIGATION_RESULT_SUCCESS); + } + + @Test + public void testDisablingRescueByDeviceConfigFlag() { + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); + SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true)); + + assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation( + sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), + MITIGATION_RESULT_SKIPPED); + + // Restore the property value initialized in SetUp() + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); + SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false)); + } + + @Test + public void testDisablingFactoryResetByDeviceConfigFlag() { + SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, Boolean.toString(true)); + + for (int i = 0; i < LEVEL_FACTORY_RESET; i++) { + noteBoot(i + 1); + } + assertFalse(RescueParty.isFactoryResetPropertySet()); + + // Restore the property value initialized in SetUp() + SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, ""); + } + + @Test + public void testHealthCheckLevelsNoFlags() { + // this is old test where the flag needs to be disabled + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); + + // Ensure that no action is taken for cases where the failure reason is unknown + assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1), + PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); + + // Ensure the correct user impact is returned for each mitigation count. + assertEquals(observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), + PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); + + assertEquals(observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2), + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); + } + + @Test + public void testBootLoopLevelsNoFlags() { + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); + + assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); + assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); + } + + + private void noteBoot(int mitigationCount) { + RescuePartyObserver.getInstance(mMockContext).onExecuteBootLoopMitigation(mitigationCount); + } + + private void noteAppCrash(int mitigationCount, boolean isPersistent) { + String packageName = isPersistent ? PERSISTENT_PACKAGE : NON_PERSISTENT_PACKAGE; + RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation( + new VersionedPackage(packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, + mitigationCount); + } + + // Mock CrashRecoveryProperties as they cannot be accessed due to SEPolicy restrictions + private void mockCrashRecoveryProperties(PackageWatchdog watchdog) { + // mock properties in RescueParty + try { + + doAnswer((Answer<Boolean>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.attempting_factory_reset", "false"); + return Boolean.parseBoolean(storedValue); + }).when(() -> RescueParty.isFactoryResetPropertySet()); + doAnswer((Answer<Void>) invocationOnMock -> { + boolean value = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_factory_reset", + Boolean.toString(value)); + return null; + }).when(() -> RescueParty.setFactoryResetProperty(anyBoolean())); + + doAnswer((Answer<Boolean>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.attempting_reboot", "false"); + return Boolean.parseBoolean(storedValue); + }).when(() -> RescueParty.isRebootPropertySet()); + doAnswer((Answer<Void>) invocationOnMock -> { + boolean value = invocationOnMock.getArgument(0); + setCrashRecoveryPropAttemptingReboot(value); + return null; + }).when(() -> RescueParty.setRebootProperty(anyBoolean())); + + doAnswer((Answer<Long>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("persist.crashrecovery.last_factory_reset", "0"); + return Long.parseLong(storedValue); + }).when(() -> RescueParty.getLastFactoryResetTimeMs()); + doAnswer((Answer<Void>) invocationOnMock -> { + long value = invocationOnMock.getArgument(0); + setCrashRecoveryPropLastFactoryReset(value); + return null; + }).when(() -> RescueParty.setLastFactoryResetTimeMs(anyLong())); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.max_rescue_level_attempted", "0"); + return Integer.parseInt(storedValue); + }).when(() -> RescueParty.getMaxRescueLevelAttempted()); + doAnswer((Answer<Void>) invocationOnMock -> { + int value = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.max_rescue_level_attempted", + Integer.toString(value)); + return null; + }).when(() -> RescueParty.setMaxRescueLevelAttempted(anyInt())); + + } catch (Exception e) { + // tests will fail, just printing the error + System.out.println("Error while mocking crashrecovery properties " + e.getMessage()); + } + + // mock properties in BootThreshold + try { + mSpyBootThreshold = spy(watchdog.new BootThreshold( + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS)); + mCrashRecoveryPropertiesMap = new HashMap<>(); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.rescue_boot_count", "0"); + return Integer.parseInt(storedValue); + }).when(mSpyBootThreshold).getCount(); + doAnswer((Answer<Void>) invocationOnMock -> { + int count = invocationOnMock.getArgument(0); + setCrashRecoveryPropRescueBootCount(count); + return null; + }).when(mSpyBootThreshold).setCount(anyInt()); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.boot_mitigation_count", "0"); + return Integer.parseInt(storedValue); + }).when(mSpyBootThreshold).getMitigationCount(); + doAnswer((Answer<Void>) invocationOnMock -> { + int count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_count", + Integer.toString(count)); + return null; + }).when(mSpyBootThreshold).setMitigationCount(anyInt()); + + doAnswer((Answer<Long>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.rescue_boot_start", "0"); + return Long.parseLong(storedValue); + }).when(mSpyBootThreshold).getStart(); + doAnswer((Answer<Void>) invocationOnMock -> { + long count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_start", + Long.toString(count)); + return null; + }).when(mSpyBootThreshold).setStart(anyLong()); + + doAnswer((Answer<Long>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.boot_mitigation_start", "0"); + return Long.parseLong(storedValue); + }).when(mSpyBootThreshold).getMitigationStart(); + doAnswer((Answer<Void>) invocationOnMock -> { + long count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_start", + Long.toString(count)); + return null; + }).when(mSpyBootThreshold).setMitigationStart(anyLong()); + + Field mBootThresholdField = watchdog.getClass().getDeclaredField("mBootThreshold"); + mBootThresholdField.setAccessible(true); + mBootThresholdField.set(watchdog, mSpyBootThreshold); + } catch (Exception e) { + // tests will fail, just printing the error + System.out.println("Error while spying BootThreshold " + e.getMessage()); + } + } + + private void setCrashRecoveryPropRescueBootCount(int count) { + mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_count", + Integer.toString(count)); + } + + private void setCrashRecoveryPropAttemptingReboot(boolean value) { + mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_reboot", + Boolean.toString(value)); + } + + private void setCrashRecoveryPropLastFactoryReset(long value) { + mCrashRecoveryPropertiesMap.put("persist.crashrecovery.last_factory_reset", + Long.toString(value)); + } +} diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt index 0c3c7e2af6f2..e868a6cc5a80 100644 --- a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt +++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt @@ -34,7 +34,7 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain import org.junit.runner.RunWith -import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.Mockito.verifyNoMoreInteractions /** * Test for testing revokeTrust & grantTrust for non-renewable trust. @@ -120,7 +120,7 @@ class GrantAndRevokeTrustTest { trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0, callback) await() - verifyZeroInteractions(callback) + verifyNoMoreInteractions(callback) } companion object { diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java index f5d4b0c5e345..cc7eebca1d00 100644 --- a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java +++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java @@ -33,7 +33,7 @@ import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; @@ -120,7 +120,7 @@ public class UsbServiceTest { verify(mUsbPortManager).enableUsbData(TEST_PORT_ID, enable, TEST_TRANSACTION_ID, mCallback, null); - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); clearInvocations(mUsbPortManager); clearInvocations(mCallback); @@ -131,7 +131,7 @@ public class UsbServiceTest { assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable, TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest)); - verifyZeroInteractions(mUsbPortManager); + verifyNoMoreInteractions(mUsbPortManager); verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL); clearInvocations(mUsbPortManager); @@ -188,7 +188,7 @@ public class UsbServiceTest { mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID, mCallback, TEST_SECOND_CALLER_ID, false); - verifyZeroInteractions(mUsbPortManager); + verifyNoMoreInteractions(mUsbPortManager); verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL); } @@ -203,7 +203,7 @@ public class UsbServiceTest { verify(mUsbPortManager).enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID, mCallback, null); - verifyZeroInteractions(mCallback); + verifyNoMoreInteractions(mCallback); } /** diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java index fd5c4caca484..a7e0125eb3d4 100644 --- a/tests/testables/tests/src/android/testing/TestableLooperTest.java +++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java @@ -17,8 +17,8 @@ package android.testing; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; diff --git a/tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java b/tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java index 6205b98d4e79..05f237f0002f 100644 --- a/tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java +++ b/tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java @@ -19,7 +19,7 @@ package android.os.test; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 6608dda95a4b..a34908051360 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -37,10 +37,10 @@ import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java index f6123d29f35a..e1a572e7a481 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java @@ -21,7 +21,7 @@ import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 8374fd944568..7f0cabf7d159 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -24,8 +24,8 @@ import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; diff --git a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java index 2b92428918db..0185931d628f 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java @@ -18,9 +18,9 @@ package com.android.server.vcn; import static android.net.NetworkProvider.NetworkOfferCallback; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java index bd4aeba761da..c12adcbab08a 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -31,10 +31,10 @@ import static com.android.server.vcn.Vcn.VcnContentResolver; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java index 27c1bc105bde..3ca84cf05cd1 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java @@ -28,7 +28,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -83,7 +82,7 @@ public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase { .thenReturn(mIpSecPacketLossDetector); when(mCarrierConfig.getIntArray( - eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject())) + eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), any())) .thenReturn(new int[] {PENALTY_TIMEOUT_MIN}); mNetworkEvaluator = newValidUnderlyingNetworkEvaluator(); @@ -309,7 +308,7 @@ public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase { public void testSetCarrierConfig() throws Exception { final int additionalTimeoutMin = 10; when(mCarrierConfig.getIntArray( - eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject())) + eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), any())) .thenReturn(new int[] {PENALTY_TIMEOUT_MIN + additionalTimeoutMin}); // Update evaluator and penalize the network diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java index 02da835d1002..c30e7bfdfa50 100644 --- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java +++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java @@ -23,7 +23,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Matchers.argThat; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.doNothing; |