diff options
847 files changed, 23421 insertions, 6048 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/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 251776e907d8..44e4999ccf44 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -5369,7 +5369,9 @@ public class AlarmManagerService extends SystemService { // to do any wakelock or stats tracking, so we have nothing // left to do here but go on to the next thing. mSendFinishCount++; - if (Flags.acquireWakelockBeforeSend()) { + if (Flags.acquireWakelockBeforeSend() && mBroadcastRefCount == 0) { + // No other alarms are in-flight and this dispatch failed. We will + // acquire the wakelock again before the next dispatch. mWakeLock.release(); } return; @@ -5409,7 +5411,9 @@ public class AlarmManagerService extends SystemService { // stats management to do. It threw before we posted the delayed // timeout message, so we're done here. mListenerFinishCount++; - if (Flags.acquireWakelockBeforeSend()) { + if (Flags.acquireWakelockBeforeSend() && mBroadcastRefCount == 0) { + // No other alarms are in-flight and this dispatch failed. We will + // acquire the wakelock again before the next dispatch. mWakeLock.release(); } return; 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..b9d61cd334e3 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(); } @@ -16442,7 +16443,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 +16480,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 +16560,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 +16622,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/ActivityThread.java b/core/java/android/app/ActivityThread.java index e193d2ddd5a7..0a2b1eaaad6b 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4002,6 +4002,10 @@ public final class ActivityThread extends ClientTransactionHandler + (fromIpc ? " (from ipc" : "")); } } + if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { + Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, + "updateProcessState: processState=" + processState); + } } /** Converts a process state to a VM process state. */ 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/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 eed6c0f32a9d..6f0eafe487af 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -63,6 +63,16 @@ flag { } flag { + name: "modes_ui_dnd_tile" + namespace: "systemui" + description: "Shows a dedicated tile for the DND mode; dependent on modes_ui" + bug: "401217520" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "modes_hsum" namespace: "systemui" description: "Fixes for modes (and DND/Zen in general) with HSUM or secondary users" @@ -366,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/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/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/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java index 132bdd1e56b8..1cf57de08d5f 100644 --- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java +++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java @@ -786,8 +786,8 @@ public final class MessageQueue { } /** - * Get the timestamp of the next executable message in our priority queue. - * Returns null if there are no messages ready for delivery. + * Get the timestamp of the next message in our priority queue. + * Returns null if there are no messages in the queue. * * Caller must ensure that this doesn't race 'next' from the Looper thread. */ @@ -799,8 +799,8 @@ public final class MessageQueue { } /** - * Return the next executable message in our priority queue. - * Returns null if there are no messages ready for delivery + * Return the next message in our priority queue. + * Returns null if there are no messages in the queue. * * Caller must ensure that this doesn't race 'next' from the Looper thread. */ 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/TestLooperManager.java b/core/java/android/os/TestLooperManager.java index 1a54f4df58fb..204e3444c547 100644 --- a/core/java/android/os/TestLooperManager.java +++ b/core/java/android/os/TestLooperManager.java @@ -96,8 +96,8 @@ public class TestLooperManager { } /** - * Retrieves and removes the next message that should be executed by this queue. - * If the queue is empty or no messages are deliverable, returns null. + * Retrieves and removes the next message in this queue. + * If the queue is empty, returns null. * This method never blocks. * * <p>Callers should always call {@link #recycle(Message)} on the message when all interactions @@ -112,9 +112,9 @@ public class TestLooperManager { } /** - * Retrieves, but does not remove, the values of {@link Message#when} of next message that - * should be executed by this queue. - * If the queue is empty or no messages are deliverable, returns null. + * Retrieves, but does not remove, the values of {@link Message#when} of next message in the + * queue. + * If the queue is empty, returns null. * This method never blocks. */ @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY) 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..0433c76fbbf4 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 actualDeviceId = resolveDeviceIdForPermissionCheck(mContext, deviceId, permission); return sPermissionRequestStateCache.query( - new PermissionRequestStateQuery(packageName, permission, deviceId)); + new PermissionRequestStateQuery(packageName, permission, actualDeviceId)); } /** @@ -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 actualDeviceId = resolveDeviceIdForPermissionCheck(mContext, deviceId, permName); + String persistentDeviceId = getPersistentDeviceId(actualDeviceId); 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/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/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/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/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..67e54423414c 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -214,3 +214,13 @@ 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 + } +} 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/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/CoreDocumentAccessibility.java b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java index d3a496db2ca9..f70f4cbceb70 100644 --- a/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java +++ b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java @@ -179,7 +179,7 @@ public class CoreDocumentAccessibility implements RemoteComposeDocumentAccessibi * @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/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java index 3cc998576741..caf19e1ed34a 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,7 @@ 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.5f; @NonNull ArrayList<Operation> mOperations = new ArrayList<>(); @@ -442,6 +447,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 { /** @@ -911,7 +1004,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 +1013,7 @@ public class CoreDocument implements Serializable { } for (IdActionCallback listener : mIdActionListeners) { - listener.onAction(id, ""); + listener.onAction(id, metadata); } Component component = getComponent(id); 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..ac9f98bd6b15 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -104,6 +104,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; @@ -115,6 +116,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier 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 +249,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; @@ -278,6 +281,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; @@ -385,6 +389,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 +412,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/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/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java index 76bb96d1b61a..f1158d91f94b 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; @@ -1131,14 +1132,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..6163d8099b8c 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)) { 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/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/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/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..e1f2924021a4 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 @@ -197,7 +197,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..0bc99abc17bc 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 @@ -154,7 +154,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/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..43486f85f5d6 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. --> 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..bab4a3d178cb 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" /> 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/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/OWNERS b/libs/WindowManager/Shell/aconfig/OWNERS index 9eba0f2dea7b..eacadeacab36 100644 --- a/libs/WindowManager/Shell/aconfig/OWNERS +++ b/libs/WindowManager/Shell/aconfig/OWNERS @@ -1,3 +1,4 @@ # Owners for flag changes madym@google.com -hwwang@google.com
\ No newline at end of file +hwwang@google.com +sqsun@google.com
\ No newline at end of file diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index ab2804626361..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 } @@ -203,4 +203,11 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "enable_magnetic_split_divider" + namespace: "multitasking" + description: "Makes the split divider snap 'magnetically' to available snap points during drag" + bug: "383631946" +} diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt index 2d6df43f67e0..3597ce0041d4 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt @@ -22,14 +22,14 @@ import com.google.common.truth.Subject import com.google.common.truth.Truth /** Subclass of [Subject] to simplify verifying [FakeUiEvent] data */ -class UiEventSubject(metadata: FailureMetadata, private val actual: FakeUiEvent) : +class UiEventSubject(metadata: FailureMetadata, private val actual: FakeUiEvent?) : Subject(metadata, actual) { /** Check that [FakeUiEvent] contains the expected data from the [bubble] passed id */ fun hasBubbleInfo(bubble: Bubble) { - check("uid").that(actual.uid).isEqualTo(bubble.appUid) - check("packageName").that(actual.packageName).isEqualTo(bubble.packageName) - check("instanceId").that(actual.instanceId).isEqualTo(bubble.instanceId) + check("uid").that(actual?.uid).isEqualTo(bubble.appUid) + check("packageName").that(actual?.packageName).isEqualTo(bubble.packageName) + check("instanceId").that(actual?.instanceId).isEqualTo(bubble.instanceId) } companion object { diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml index b35dc022e210..56e5dc717a7b 100644 --- a/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml @@ -18,9 +18,9 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" - android:viewportHeight="24" - android:viewportWidth="24"> + android:viewportWidth="960" + android:viewportHeight="960"> <path android:fillColor="#FF000000" - android:pathData="M6,21V19H18V21Z"/> + android:pathData="M160,800L160,720L800,720L800,800L160,800Z"/> </vector> 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 16e098b39004..ed8b54397076 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 @@ -39,8 +39,8 @@ <ImageView android:id="@+id/application_icon" - android:layout_width="@dimen/desktop_mode_caption_icon_radius" - android:layout_height="@dimen/desktop_mode_caption_icon_radius" + android:layout_width="@dimen/desktop_mode_handle_menu_icon_radius" + android:layout_height="@dimen/desktop_mode_handle_menu_icon_radius" android:layout_marginStart="10dp" android:layout_marginEnd="12dp" android:contentDescription="@string/app_icon_text" diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index b8f0e3d40f08..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> @@ -570,7 +577,10 @@ <dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen> <!-- The radius of the caption menu icon. --> - <dimen name="desktop_mode_caption_icon_radius">32dp</dimen> + <dimen name="desktop_mode_caption_icon_radius">24dp</dimen> + + <!-- The radius of the icon in the header menu's app info pill. --> + <dimen name="desktop_mode_handle_menu_icon_radius">32dp</dimen> <!-- The radius of the caption menu shadow. --> <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen> 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..6dcc0deb1da1 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, ) { @@ -128,14 +131,25 @@ class DesktopDisplayModeController( return 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 } 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..50f5beb4166a 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,60 @@ 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) } /** Move a task with given `taskId` to fullscreen */ @@ -1256,8 +1268,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 +1852,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 +1873,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 +2986,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 +3757,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 +3832,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..40737120f364 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -23,12 +23,12 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERL import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; +import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP; -import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEdgeResizePermitted; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEventFromTouchscreen; @@ -127,7 +127,9 @@ class DragResizeInputListener implements AutoCloseable { Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, DisplayController displayController, - DesktopModeEventLogger desktopModeEventLogger) { + DesktopModeEventLogger desktopModeEventLogger, + InputChannel inputChannel, + InputChannel sinkInputChannel) { mContext = context; mWindowSession = windowSession; mBgExecutor = bgExecutor; @@ -154,9 +156,13 @@ class DragResizeInputListener implements AutoCloseable { final InputSetUpResult result = setUpInputChannels(mDisplayId, mWindowSession, mDecorationSurface, mClientToken, mSinkClientToken, mSurfaceControlBuilderSupplier, - mSurfaceControlTransactionSupplier); + mSurfaceControlTransactionSupplier, inputChannel, sinkInputChannel); mainExecutor.execute(() -> { if (mClosed) { + result.mInputChannel.dispose(); + result.mSinkInputChannel.dispose(); + mSurfaceControlTransactionSupplier.get().remove( + result.mInputSinkSurface).apply(); return; } mInputSinkSurface = result.mInputSinkSurface; @@ -208,7 +214,7 @@ class DragResizeInputListener implements AutoCloseable { new DefaultTaskResizeInputEventReceiverFactory(), taskInfo, handler, choreographer, displayId, decorationSurface, callback, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, - displayController, desktopModeEventLogger); + displayController, desktopModeEventLogger, new InputChannel(), new InputChannel()); } DragResizeInputListener( @@ -251,11 +257,11 @@ class DragResizeInputListener implements AutoCloseable { @NonNull IBinder clientToken, @NonNull IBinder sinkClientToken, @NonNull Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, - @NonNull Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) { + @NonNull Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, + @NonNull InputChannel inputChannel, + @NonNull InputChannel sinkInputChannel) { Trace.beginSection("DragResizeInputListener#setUpInputChannels"); final InputTransferToken inputTransferToken = new InputTransferToken(); - final InputChannel inputChannel = new InputChannel(); - final InputChannel sinkInputChannel = new InputChannel(); try { windowSession.grantInputChannel( displayId, diff --git a/libs/WindowManager/Shell/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..a8a7032d0b86 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,12 +108,9 @@ 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 + 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( @@ -398,8 +395,7 @@ 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) 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..105941079095 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,37 @@ 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.doReturn +import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never +import com.android.dx.mockito.inline.extended.StaticMockitoSession +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 +69,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 +78,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 +90,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 +102,12 @@ class DesktopDisplayModeControllerTest( TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build() private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) private val wallpaperToken = MockToken().token() + 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) + .spyStatic(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,50 @@ 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(EXTERNAL_DISPLAY_ID)).thenReturn(externalDisplay) setTabletModeStatus(SwitchState.UNKNOWN) } + @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) - 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()) - } + 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()) } } @@ -176,15 +209,10 @@ class DesktopDisplayModeControllerTest( disconnectExternalDisplay() } setTabletModeStatus(tabletModeStatus) + setExtendedMode(param.extendedDisplayEnabled) - ExtendedDisplaySettingsSession( - context.contentResolver, - if (param.extendedDisplayEnabled) 1 else 0, - ) - .use { - assertThat(controller.getTargetWindowingModeForDefaultDisplay()) - .isEqualTo(param.expectedWindowingMode) - } + assertThat(controller.getTargetWindowingModeForDefaultDisplay()) + .isEqualTo(param.expectedWindowingMode) } @Test @@ -199,15 +227,10 @@ class DesktopDisplayModeControllerTest( disconnectExternalDisplay() } setTabletModeStatus(param.tabletModeStatus) + setExtendedMode(param.extendedDisplayEnabled) - ExtendedDisplaySettingsSession( - context.contentResolver, - if (param.extendedDisplayEnabled) 1 else 0, - ) - .use { - assertThat(controller.getTargetWindowingModeForDefaultDisplay()) - .isEqualTo(param.expectedWindowingMode) - } + assertThat(controller.getTargetWindowingModeForDefaultDisplay()) + .isEqualTo(param.expectedWindowingMode) } @Test @@ -215,18 +238,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 +257,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 +285,27 @@ 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()) { + doReturn(enabled).`when` { + DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, externalDisplay) + } + } 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 +315,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, ) } } 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..7efcd4fc3c8f 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,90 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @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() @@ -4983,6 +5104,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 +7442,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 +7745,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/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index e246329446dc..5dff21860ef4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -211,11 +211,19 @@ public class StageCoordinatorTests extends ShellTestCase { when(mSplitLayout.getDividerLeash()).thenReturn(dividerLeash); mRootTask = new TestRunningTaskInfoBuilder().build(); - SurfaceControl rootLeash = new SurfaceControl.Builder().setName("test").build(); + SurfaceControl rootLeash = new SurfaceControl.Builder().setName("splitRoot").build(); mStageCoordinator.onTaskAppeared(mRootTask, rootLeash); mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); + SurfaceControl mainRootLeash = new SurfaceControl.Builder().setName("mainRoot").build(); + SurfaceControl sideRootLeash = new SurfaceControl.Builder().setName("sideRoot").build(); + mMainStage.mRootLeash = mainRootLeash; + mSideStage.mRootLeash = sideRootLeash; + SurfaceControl mainDimLayer = new SurfaceControl.Builder().setName("mainDim").build(); + SurfaceControl sideDimLayer = new SurfaceControl.Builder().setName("sideDim").build(); + mMainStage.mDimLayer = mainDimLayer; + mSideStage.mDimLayer = sideDimLayer; doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager(); doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager(); 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..e23d0ad55b04 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt @@ -63,6 +63,8 @@ class DragResizeInputListenerTest : ShellTestCase() { private val testBgExecutor = TestShellExecutor() private val mockWindowSession = mock<IWindowSession>() private val mockInputEventReceiver = mock<TaskResizeInputEventReceiver>() + private val inputChannel = mock<InputChannel>() + private val sinkInputChannel = mock<InputChannel>() @Test fun testGrantInputChannelOffMainThread() { @@ -143,6 +145,16 @@ class DragResizeInputListenerTest : ShellTestCase() { verify(mockWindowSession).remove(inputListener.mSinkClientToken) } + @Test + fun testClose_afterBgSetup_disposesOfInputChannels() { + val inputListener = create() + testBgExecutor.flushAll() + inputListener.close() + testMainExecutor.flushAll() + verify(inputChannel).dispose() + verify(sinkInputChannel).dispose() + } + private fun verifyNoInputChannelGrantRequests() { verify(mockWindowSession, never()) .grantInputChannel( @@ -178,6 +190,8 @@ class DragResizeInputListenerTest : ShellTestCase() { { StubTransaction() }, mock<DisplayController>(), mock<DesktopModeEventLogger>(), + inputChannel, + sinkInputChannel, ) private class TestInitializationCallback : Runnable { diff --git a/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/GnssClock.java b/location/java/android/location/GnssClock.java index 62f50b57520c..6930f365adc1 100644 --- a/location/java/android/location/GnssClock.java +++ b/location/java/android/location/GnssClock.java @@ -349,7 +349,7 @@ public final class GnssClock implements Parcelable { * Gets the clock's Drift in nanoseconds per second. * * <p>This value is the instantaneous time-derivative of the value provided by - * {@link #getBiasNanos()}. + * the sum of {@link #getFullBiasNanos()} and {@link #getBiasNanos()}. * * <p>A positive value indicates that the frequency is higher than the nominal (e.g. GPS master * clock) frequency. The error estimate for this reported drift is diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index f8eb41826c6f..496ba501e49c 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -186,3 +186,11 @@ flag { bug: "398254728" is_fixed_read_only: true } + +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/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/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..5f88bcd8d65d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1863,10 +1863,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/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..0121d31b9f35 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, 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..5eb6af62d2c3 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); 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/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/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..80cbbea7659f 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,10 +95,20 @@ 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 { /** @@ -540,6 +550,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,28 +578,34 @@ 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 { 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..a03d769c9c36 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,35 @@ 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() + } + private fun ComposeContentTestRule.setContentWithTouchSlop( content: @Composable () -> Unit ): Float { @@ -996,7 +1025,8 @@ 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 }, ) : NestedDraggable { var shouldStartDrag = true var onDragStartedCalled = false @@ -1042,8 +1072,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/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt index 8ad96a5bcb37..62b134279267 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt @@ -77,6 +77,16 @@ internal constructor( list.apply { add(toIndex, removeAt(fromIndex)) } } + /** Swap the two items in the list with the given indices. */ + fun swapItems(index1: Int, index2: Int) { + list.apply { + val item1 = get(index1) + val item2 = get(index2) + set(index2, item1) + set(index1, item2) + } + } + /** Remove widget from the list and the database. */ fun onRemove(indexToRemove: Int) { if (list[indexToRemove].isWidgetContent()) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt index 0aef7f2c7063..dda388aeeac6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt @@ -18,8 +18,10 @@ package com.android.systemui.communal.ui.compose import android.content.ClipDescription import android.view.DragEvent +import androidx.compose.animation.core.tween import androidx.compose.foundation.draganddrop.dragAndDropTarget import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.animateScrollBy import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.runtime.Composable @@ -37,6 +39,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import com.android.systemui.Flags.communalWidgetResizing +import com.android.systemui.Flags.glanceableHubV2 import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset import com.android.systemui.communal.util.WidgetPickerIntentUtils @@ -51,13 +54,14 @@ import kotlinx.coroutines.launch * @see dragAndDropTarget */ @Composable -internal fun rememberDragAndDropTargetState( +fun rememberDragAndDropTargetState( gridState: LazyGridState, contentOffset: Offset, contentListState: ContentListState, ): DragAndDropTargetState { val scope = rememberCoroutineScope() val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() } + val state = remember(gridState, contentOffset, contentListState, autoScrollThreshold, scope) { DragAndDropTargetState( @@ -68,11 +72,9 @@ internal fun rememberDragAndDropTargetState( scope = scope, ) } - LaunchedEffect(state) { - for (diff in state.scrollChannel) { - gridState.scrollBy(diff) - } - } + + LaunchedEffect(state) { state.processScrollRequests(scope) } + return state } @@ -83,7 +85,7 @@ internal fun rememberDragAndDropTargetState( * @see DragEvent */ @Composable -internal fun Modifier.dragAndDropTarget(dragDropTargetState: DragAndDropTargetState): Modifier { +fun Modifier.dragAndDropTarget(dragDropTargetState: DragAndDropTargetState): Modifier { val state by rememberUpdatedState(dragDropTargetState) return this then @@ -132,13 +134,79 @@ internal fun Modifier.dragAndDropTarget(dragDropTargetState: DragAndDropTargetSt * other activities. [GridDragDropState] on the other hand, handles dragging of existing items in * the communal hub grid. */ -internal class DragAndDropTargetState( +class DragAndDropTargetState( + state: LazyGridState, + contentOffset: Offset, + contentListState: ContentListState, + autoScrollThreshold: Float, + scope: CoroutineScope, +) { + private val dragDropState: DragAndDropTargetStateInternal = + if (glanceableHubV2()) { + DragAndDropTargetStateV2( + state = state, + contentListState = contentListState, + scope = scope, + autoScrollThreshold = autoScrollThreshold, + contentOffset = contentOffset, + ) + } else { + DragAndDropTargetStateV1( + state = state, + contentListState = contentListState, + scope = scope, + autoScrollThreshold = autoScrollThreshold, + contentOffset = contentOffset, + ) + } + + fun onStarted() = dragDropState.onStarted() + + fun onMoved(event: DragAndDropEvent) = dragDropState.onMoved(event) + + fun onDrop(event: DragAndDropEvent) = dragDropState.onDrop(event) + + fun onEnded() = dragDropState.onEnded() + + fun onExited() = dragDropState.onExited() + + suspend fun processScrollRequests(coroutineScope: CoroutineScope) = + dragDropState.processScrollRequests(coroutineScope) +} + +/** + * A private interface defining the API for handling drag-and-drop operations. There will be two + * implementations of this interface: V1 for devices that do not have the glanceable_hub_v2 flag + * enabled, and V2 for devices that do have that flag enabled. + * + * TODO(b/400789179): Remove this interface and the V1 implementation once glanceable_hub_v2 has + * shipped. + */ +private interface DragAndDropTargetStateInternal { + fun onStarted() = Unit + + fun onMoved(event: DragAndDropEvent) = Unit + + fun onDrop(event: DragAndDropEvent): Boolean = false + + fun onEnded() = Unit + + fun onExited() = Unit + + suspend fun processScrollRequests(coroutineScope: CoroutineScope) = Unit +} + +/** + * The V1 implementation of DragAndDropTargetStateInternal to be used when the glanceable_hub_v2 + * flag is disabled. + */ +private class DragAndDropTargetStateV1( private val state: LazyGridState, private val contentOffset: Offset, private val contentListState: ContentListState, private val autoScrollThreshold: Float, private val scope: CoroutineScope, -) { +) : DragAndDropTargetStateInternal { /** * The placeholder item that is treated as if it is being dragged across the grid. It is added * to grid once drag and drop event is started and removed when event ends. @@ -147,15 +215,21 @@ internal class DragAndDropTargetState( private var placeHolderIndex: Int? = null private var previousTargetItemKey: Any? = null - internal val scrollChannel = Channel<Float>() + private val scrollChannel = Channel<Float>() - fun onStarted() { + override suspend fun processScrollRequests(coroutineScope: CoroutineScope) { + for (diff in scrollChannel) { + state.scrollBy(diff) + } + } + + override fun onStarted() { // assume item will be added to the end. contentListState.list.add(placeHolder) placeHolderIndex = contentListState.list.size - 1 } - fun onMoved(event: DragAndDropEvent) { + override fun onMoved(event: DragAndDropEvent) { val dragOffset = event.toOffset() val targetItem = @@ -201,7 +275,7 @@ internal class DragAndDropTargetState( } } - fun onDrop(event: DragAndDropEvent): Boolean { + override fun onDrop(event: DragAndDropEvent): Boolean { return placeHolderIndex?.let { dropIndex -> val widgetExtra = event.maybeWidgetExtra() ?: return false val (componentName, user) = widgetExtra @@ -219,13 +293,13 @@ internal class DragAndDropTargetState( } ?: false } - fun onEnded() { + override fun onEnded() { placeHolderIndex = null previousTargetItemKey = null contentListState.list.remove(placeHolder) } - fun onExited() { + override fun onExited() { onEnded() } @@ -257,16 +331,186 @@ internal class DragAndDropTargetState( contentListState.onMove(currentIndex, index) } } +} +/** + * The V2 implementation of DragAndDropTargetStateInternal to be used when the glanceable_hub_v2 + * flag is enabled. + */ +private class DragAndDropTargetStateV2( + private val state: LazyGridState, + private val contentOffset: Offset, + private val contentListState: ContentListState, + private val autoScrollThreshold: Float, + private val scope: CoroutineScope, +) : DragAndDropTargetStateInternal { /** - * Parses and returns the intent extra associated with the widget that is dropped into the grid. - * - * Returns null if the drop event didn't include intent information. + * The placeholder item that is treated as if it is being dragged across the grid. It is added + * to grid once drag and drop event is started and removed when event ends. */ - private fun DragAndDropEvent.maybeWidgetExtra(): WidgetPickerIntentUtils.WidgetExtra? { - val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 } - return clipData?.getItemAt(0)?.intent?.let { intent -> getWidgetExtraFromIntent(intent) } + private var placeHolder = CommunalContentModel.WidgetPlaceholder() + private var placeHolderIndex: Int? = null + private var previousTargetItemKey: Any? = null + private var dragOffset = Offset.Zero + private var columnWidth = 0 + + private val scrollChannel = Channel<Float>() + + override suspend fun processScrollRequests(coroutineScope: CoroutineScope) { + while (true) { + val amount = scrollChannel.receive() + + if (state.isScrollInProgress) { + // Ignore overscrolling if a scroll is already in progress (but we still want to + // consume the scroll event so that we don't end up processing a bunch of old + // events after scrolling has finished). + continue + } + + // Perform the rest of the drag operation after scrolling has finished (or immediately + // if there will be no scrolling). + if (amount != 0f) { + scope.launch { + state.animateScrollBy(amount, tween(delayMillis = 250, durationMillis = 1000)) + performDragAction() + } + } else { + performDragAction() + } + } + } + + override fun onStarted() { + // assume item will be added to the end. + contentListState.list.add(placeHolder) + placeHolderIndex = contentListState.list.size - 1 + + // Use the width of the first item as the column width. + columnWidth = + state.layoutInfo.visibleItemsInfo.first().size.width + + state.layoutInfo.beforeContentPadding + + state.layoutInfo.afterContentPadding } - private fun DragAndDropEvent.toOffset() = this.toAndroidDragEvent().run { Offset(x, y) } + override fun onMoved(event: DragAndDropEvent) { + dragOffset = event.toOffset() + scrollChannel.trySend(computeAutoscroll(dragOffset)) + } + + override fun onDrop(event: DragAndDropEvent): Boolean { + return placeHolderIndex?.let { dropIndex -> + val widgetExtra = event.maybeWidgetExtra() ?: return false + val (componentName, user) = widgetExtra + if (componentName != null && user != null) { + // Placeholder isn't removed yet to allow the setting the right rank for items + // before adding in the new item. + contentListState.onSaveList( + newItemComponentName = componentName, + newItemUser = user, + newItemIndex = dropIndex, + ) + return@let true + } + return false + } ?: false + } + + override fun onEnded() { + placeHolderIndex = null + previousTargetItemKey = null + contentListState.list.remove(placeHolder) + } + + override fun onExited() { + onEnded() + } + + private fun performDragAction() { + val targetItem = + state.layoutInfo.visibleItemsInfo + .asSequence() + .filter { item -> contentListState.isItemEditable(item.index) } + .firstItemAtOffset(dragOffset - contentOffset) + + if ( + targetItem != null && + (!communalWidgetResizing() || targetItem.key != previousTargetItemKey) + ) { + if (communalWidgetResizing()) { + // Keep track of the previous target item, to avoid rapidly oscillating between + // items if the target item doesn't visually move as a result of the index change. + // In this case, even after the index changes, we'd still be colliding with the + // element, so it would be selected as the target item the next time this function + // runs again, which would trigger us to revert the index change we recently made. + previousTargetItemKey = targetItem.key + } + + val scrollToIndex = + if (targetItem.index == state.firstVisibleItemIndex) { + placeHolderIndex + } else if (placeHolderIndex == state.firstVisibleItemIndex) { + targetItem.index + } else { + null + } + + if (scrollToIndex != null) { + scope.launch { + state.scrollToItem(scrollToIndex, state.firstVisibleItemScrollOffset) + movePlaceholderTo(targetItem.index) + } + } else { + movePlaceholderTo(targetItem.index) + } + + placeHolderIndex = targetItem.index + } else if (targetItem == null) { + previousTargetItemKey = null + } + } + + private fun computeAutoscroll(dragOffset: Offset): Float { + val orientation = state.layoutInfo.orientation + val distanceFromStart = + if (orientation == Orientation.Horizontal) { + dragOffset.x + } else { + dragOffset.y + } + val distanceFromEnd = + if (orientation == Orientation.Horizontal) { + state.layoutInfo.viewportEndOffset - dragOffset.x + } else { + state.layoutInfo.viewportEndOffset - dragOffset.y + } + + return when { + distanceFromEnd < autoScrollThreshold -> { + (columnWidth - state.layoutInfo.beforeContentPadding).toFloat() + } + distanceFromStart < autoScrollThreshold -> { + -(columnWidth - state.layoutInfo.afterContentPadding).toFloat() + } + else -> 0f + } + } + + private fun movePlaceholderTo(index: Int) { + val currentIndex = contentListState.list.indexOf(placeHolder) + if (currentIndex != index) { + contentListState.swapItems(currentIndex, index) + } + } } + +/** + * Parses and returns the intent extra associated with the widget that is dropped into the grid. + * + * Returns null if the drop event didn't include intent information. + */ +private fun DragAndDropEvent.maybeWidgetExtra(): WidgetPickerIntentUtils.WidgetExtra? { + val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 } + return clipData?.getItemAt(0)?.intent?.let { intent -> getWidgetExtraFromIntent(intent) } +} + +private fun DragAndDropEvent.toOffset() = this.toAndroidDragEvent().run { Offset(x, y) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt index c972d3e3cf15..2a5addeb4951 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt @@ -19,7 +19,10 @@ package com.android.systemui.communal.ui.compose import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.animateScrollBy import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.layout.Box @@ -37,13 +40,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round import androidx.compose.ui.unit.toOffset import androidx.compose.ui.unit.toSize import com.android.systemui.Flags.communalWidgetResizing +import com.android.systemui.Flags.glanceableHubV2 import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset @@ -62,22 +68,22 @@ fun rememberGridDragDropState( contentListState: ContentListState, updateDragPositionForRemove: (boundingBox: IntRect) -> Boolean, ): GridDragDropState { - val scope = rememberCoroutineScope() + val coroutineScope = rememberCoroutineScope() + val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() } + val state = remember(gridState, contentListState, updateDragPositionForRemove) { GridDragDropState( - state = gridState, + gridState = gridState, contentListState = contentListState, - scope = scope, + coroutineScope = coroutineScope, + autoScrollThreshold = autoScrollThreshold, updateDragPositionForRemove = updateDragPositionForRemove, ) } - LaunchedEffect(state) { - while (true) { - val diff = state.scrollChannel.receive() - gridState.scrollBy(diff) - } - } + + LaunchedEffect(state) { state.processScrollRequests(coroutineScope) } + return state } @@ -89,36 +95,86 @@ fun rememberGridDragDropState( * to remove the dragged item if condition met and call [ContentListState.onSaveList] to persist any * change in ordering. */ -class GridDragDropState -internal constructor( - private val state: LazyGridState, - private val contentListState: ContentListState, - private val scope: CoroutineScope, +class GridDragDropState( + val gridState: LazyGridState, + contentListState: ContentListState, + coroutineScope: CoroutineScope, + autoScrollThreshold: Float, private val updateDragPositionForRemove: (draggingBoundingBox: IntRect) -> Boolean, ) { - var draggingItemKey by mutableStateOf<String?>(null) - private set + private val dragDropState: GridDragDropStateInternal = + if (glanceableHubV2()) { + GridDragDropStateV2( + gridState = gridState, + contentListState = contentListState, + scope = coroutineScope, + autoScrollThreshold = autoScrollThreshold, + updateDragPositionForRemove = updateDragPositionForRemove, + ) + } else { + GridDragDropStateV1( + gridState = gridState, + contentListState = contentListState, + scope = coroutineScope, + updateDragPositionForRemove = updateDragPositionForRemove, + ) + } - var isDraggingToRemove by mutableStateOf(false) - private set + val draggingItemKey: String? + get() = dragDropState.draggingItemKey - internal val scrollChannel = Channel<Float>() + val isDraggingToRemove: Boolean + get() = dragDropState.isDraggingToRemove - private var draggingItemDraggedDelta by mutableStateOf(Offset.Zero) - private var draggingItemInitialOffset by mutableStateOf(Offset.Zero) + val draggingItemOffset: Offset + get() = dragDropState.draggingItemOffset - private val spacer = CommunalContentModel.Spacer(CommunalContentSize.Responsive(1)) - private var spacerIndex: Int? = null + /** + * Called when dragging is initiated. + * + * @return {@code True} if dragging a grid item, {@code False} otherwise. + */ + fun onDragStart( + offset: Offset, + screenWidth: Int, + layoutDirection: LayoutDirection, + contentOffset: Offset, + ): Boolean = dragDropState.onDragStart(offset, screenWidth, layoutDirection, contentOffset) - private var previousTargetItemKey: Any? = null + fun onDragInterrupted() = dragDropState.onDragInterrupted() + + fun onDrag(offset: Offset, layoutDirection: LayoutDirection) = + dragDropState.onDrag(offset, layoutDirection) + + suspend fun processScrollRequests(coroutineScope: CoroutineScope) = + dragDropState.processScrollRequests(coroutineScope) +} + +/** + * A private base class defining the API for handling drag-and-drop operations. There will be two + * implementations of this class: V1 for devices that do not have the glanceable_hub_v2 flag + * enabled, and V2 for devices that do have that flag enabled. + * + * TODO(b/400789179): Remove this class and the V1 implementation once glanceable_hub_v2 has + * shipped. + */ +private open class GridDragDropStateInternal(protected val state: LazyGridState) { + var draggingItemKey by mutableStateOf<String?>(null) + protected set - internal val draggingItemOffset: Offset + var isDraggingToRemove by mutableStateOf(false) + protected set + + var draggingItemDraggedDelta by mutableStateOf(Offset.Zero) + var draggingItemInitialOffset by mutableStateOf(Offset.Zero) + + val draggingItemOffset: Offset get() = draggingItemLayoutInfo?.let { item -> draggingItemInitialOffset + draggingItemDraggedDelta - item.offset.toOffset() } ?: Offset.Zero - private val draggingItemLayoutInfo: LazyGridItemInfo? + val draggingItemLayoutInfo: LazyGridItemInfo? get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.key == draggingItemKey } /** @@ -126,7 +182,45 @@ internal constructor( * * @return {@code True} if dragging a grid item, {@code False} otherwise. */ - internal fun onDragStart( + open fun onDragStart( + offset: Offset, + screenWidth: Int, + layoutDirection: LayoutDirection, + contentOffset: Offset, + ): Boolean = false + + open fun onDragInterrupted() = Unit + + open fun onDrag(offset: Offset, layoutDirection: LayoutDirection) = Unit + + open suspend fun processScrollRequests(coroutineScope: CoroutineScope) = Unit +} + +/** + * The V1 implementation of GridDragDropStateInternal to be used when the glanceable_hub_v2 flag is + * disabled. + */ +private class GridDragDropStateV1( + val gridState: LazyGridState, + private val contentListState: ContentListState, + private val scope: CoroutineScope, + private val updateDragPositionForRemove: (draggingBoundingBox: IntRect) -> Boolean, +) : GridDragDropStateInternal(gridState) { + private val scrollChannel = Channel<Float>() + + private val spacer = CommunalContentModel.Spacer(CommunalContentSize.Responsive(1)) + private var spacerIndex: Int? = null + + private var previousTargetItemKey: Any? = null + + override suspend fun processScrollRequests(coroutineScope: CoroutineScope) { + while (true) { + val diff = scrollChannel.receive() + state.scrollBy(diff) + } + } + + override fun onDragStart( offset: Offset, screenWidth: Int, layoutDirection: LayoutDirection, @@ -162,7 +256,7 @@ internal constructor( return false } - internal fun onDragInterrupted() { + override fun onDragInterrupted() { draggingItemKey?.let { if (isDraggingToRemove) { contentListState.onRemove( @@ -185,7 +279,7 @@ internal constructor( } } - internal fun onDrag(offset: Offset, layoutDirection: LayoutDirection) { + override fun onDrag(offset: Offset, layoutDirection: LayoutDirection) { // Adjust offset to match the layout direction draggingItemDraggedDelta += Offset(offset.x.directional(LayoutDirection.Ltr, layoutDirection), offset.y) @@ -282,6 +376,249 @@ internal constructor( } } +/** + * The V2 implementation of GridDragDropStateInternal to be used when the glanceable_hub_v2 flag is + * enabled. + */ +private class GridDragDropStateV2( + val gridState: LazyGridState, + private val contentListState: ContentListState, + private val scope: CoroutineScope, + private val autoScrollThreshold: Float, + private val updateDragPositionForRemove: (draggingBoundingBox: IntRect) -> Boolean, +) : GridDragDropStateInternal(gridState) { + + private val scrollChannel = Channel<Float>(Channel.UNLIMITED) + + // Used to keep track of the dragging item during scrolling (because it might be off screen + // and no longer in the list of visible items). + private var draggingItemWhileScrolling: LazyGridItemInfo? by mutableStateOf(null) + + private val spacer = CommunalContentModel.Spacer(CommunalContentSize.Responsive(1)) + private var spacerIndex: Int? = null + + private var previousTargetItemKey: Any? = null + + // Basically, the location of the user's finger on the screen. + private var currentDragPositionOnScreen by mutableStateOf(Offset.Zero) + // The offset of the grid from the top of the screen. + private var contentOffset = Offset.Zero + + // The width of one column in the grid (needed in order to auto-scroll one column at a time). + private var columnWidth = 0 + + override suspend fun processScrollRequests(coroutineScope: CoroutineScope) { + while (true) { + val amount = scrollChannel.receive() + + if (state.isScrollInProgress) { + // Ignore overscrolling if a scroll is already in progress (but we still want to + // consume the scroll event so that we don't end up processing a bunch of old + // events after scrolling has finished). + continue + } + + // We perform the rest of the drag action after scrolling has finished (or immediately + // if there will be no scrolling). + if (amount != 0f) { + coroutineScope.launch { + state.animateScrollBy(amount, tween(delayMillis = 250, durationMillis = 1000)) + performDragAction() + } + } else { + performDragAction() + } + } + } + + override fun onDragStart( + offset: Offset, + screenWidth: Int, + layoutDirection: LayoutDirection, + contentOffset: Offset, + ): Boolean { + val normalizedOffset = + Offset( + if (layoutDirection == LayoutDirection.Ltr) offset.x else screenWidth - offset.x, + offset.y, + ) + + currentDragPositionOnScreen = normalizedOffset + this.contentOffset = contentOffset + + state.layoutInfo.visibleItemsInfo + .filter { item -> contentListState.isItemEditable(item.index) } + // grid item offset is based off grid content container so we need to deduct + // before content padding from the initial pointer position + .firstItemAtOffset(normalizedOffset - contentOffset) + ?.apply { + draggingItemKey = key as String + draggingItemWhileScrolling = this + draggingItemInitialOffset = this.offset.toOffset() + columnWidth = + this.size.width + + state.layoutInfo.beforeContentPadding + + state.layoutInfo.afterContentPadding + // Add a spacer after the last widget if it is larger than the dragging widget. + // This allows overscrolling, enabling the dragging widget to be placed beyond it. + val lastWidget = contentListState.list.lastOrNull { it.isWidgetContent() } + if ( + lastWidget != null && + draggingItemLayoutInfo != null && + lastWidget.size.span > draggingItemLayoutInfo!!.span + ) { + contentListState.list.add(spacer) + spacerIndex = contentListState.list.size - 1 + } + return true + } + + return false + } + + override fun onDragInterrupted() { + draggingItemKey?.let { + if (isDraggingToRemove) { + contentListState.onRemove( + contentListState.list.indexOfFirst { it.key == draggingItemKey } + ) + isDraggingToRemove = false + updateDragPositionForRemove(IntRect.Zero) + } + // persist list editing changes on dragging ends + contentListState.onSaveList() + draggingItemKey = null + } + previousTargetItemKey = null + draggingItemDraggedDelta = Offset.Zero + draggingItemInitialOffset = Offset.Zero + currentDragPositionOnScreen = Offset.Zero + draggingItemWhileScrolling = null + // Remove spacer, if any, when a drag gesture finishes. + spacerIndex?.let { + contentListState.list.removeAt(it) + spacerIndex = null + } + } + + override fun onDrag(offset: Offset, layoutDirection: LayoutDirection) { + // Adjust offset to match the layout direction + val delta = Offset(offset.x.directional(LayoutDirection.Ltr, layoutDirection), offset.y) + draggingItemDraggedDelta += delta + currentDragPositionOnScreen += delta + + scrollChannel.trySend(computeAutoscroll(currentDragPositionOnScreen)) + } + + fun performDragAction() { + val draggingItem = draggingItemLayoutInfo ?: draggingItemWhileScrolling + if (draggingItem == null) { + return + } + + val draggingBoundingBox = + IntRect(draggingItem.offset + draggingItemOffset.round(), draggingItem.size) + val curDragPositionInGrid = (currentDragPositionOnScreen - contentOffset) + + val targetItem = + if (communalWidgetResizing()) { + val lastVisibleItemIndex = state.layoutInfo.visibleItemsInfo.last().index + state.layoutInfo.visibleItemsInfo.findLast( + fun(item): Boolean { + val itemBoundingBox = IntRect(item.offset, item.size) + return draggingItemKey != item.key && + contentListState.isItemEditable(item.index) && + itemBoundingBox.contains(curDragPositionInGrid.round()) && + // If we swap with the last visible item, and that item doesn't fit + // in the gap created by moving the current item, then the current item + // will get placed after the last visible item. In this case, it gets + // placed outside of the viewport. We avoid this here, so the user + // has to scroll first before the swap can happen. + (item.index != lastVisibleItemIndex || item.span <= draggingItem.span) + } + ) + } else { + state.layoutInfo.visibleItemsInfo + .asSequence() + .filter { item -> contentListState.isItemEditable(item.index) } + .filter { item -> draggingItem.index != item.index } + .firstItemAtOffset(curDragPositionInGrid) + } + + if ( + targetItem != null && + (!communalWidgetResizing() || targetItem.key != previousTargetItemKey) + ) { + val scrollToIndex = + if (targetItem.index == state.firstVisibleItemIndex) { + draggingItem.index + } else if (draggingItem.index == state.firstVisibleItemIndex) { + targetItem.index + } else { + null + } + if (communalWidgetResizing()) { + // Keep track of the previous target item, to avoid rapidly oscillating between + // items if the target item doesn't visually move as a result of the index change. + // In this case, even after the index changes, we'd still be colliding with the + // element, so it would be selected as the target item the next time this function + // runs again, which would trigger us to revert the index change we recently made. + previousTargetItemKey = targetItem.key + } + if (scrollToIndex != null) { + scope.launch { + // this is needed to neutralize automatic keeping the first item first. + state.scrollToItem(scrollToIndex, state.firstVisibleItemScrollOffset) + contentListState.swapItems(draggingItem.index, targetItem.index) + } + } else { + contentListState.swapItems(draggingItem.index, targetItem.index) + } + draggingItemWhileScrolling = targetItem + isDraggingToRemove = false + } else if (targetItem == null) { + isDraggingToRemove = checkForRemove(draggingBoundingBox) + previousTargetItemKey = null + } + } + + /** Calculate the amount dragged out of bound on both sides. Returns 0f if not overscrolled. */ + private fun computeAutoscroll(dragOffset: Offset): Float { + val orientation = state.layoutInfo.orientation + val distanceFromStart = + if (orientation == Orientation.Horizontal) { + dragOffset.x + } else { + dragOffset.y + } + val distanceFromEnd = + if (orientation == Orientation.Horizontal) { + state.layoutInfo.viewportEndOffset - dragOffset.x + } else { + state.layoutInfo.viewportEndOffset - dragOffset.y + } + + return when { + distanceFromEnd < autoScrollThreshold -> { + (columnWidth - state.layoutInfo.beforeContentPadding).toFloat() + } + distanceFromStart < autoScrollThreshold -> { + -(columnWidth - state.layoutInfo.afterContentPadding).toFloat() + } + else -> 0f + } + } + + /** Calls the callback with the updated drag position and returns whether to remove the item. */ + private fun checkForRemove(draggingItemBoundingBox: IntRect): Boolean { + return if (draggingItemDraggedDelta.y < 0) { + updateDragPositionForRemove(draggingItemBoundingBox) + } else { + false + } + } +} + fun Modifier.dragContainer( dragDropState: GridDragDropState, layoutDirection: LayoutDirection, 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/verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json index 57f67665242c..57f67665242c 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json index 01bc852cf7f4..01bc852cf7f4 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json 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..b6e423afc6c4 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragOpen.json diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json index a82db346ed58..a82db346ed58 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json 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..6dc5a0e79e81 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingOpen.json 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..1cd971aa2898 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_magneticDetachAndReattach.json diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealCloseTransition.json index 1030455e873f..1030455e873f 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealCloseTransition.json diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealOpenTransition.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealOpenTransition.json index 622c29eebfb4..622c29eebfb4 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealOpenTransition.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealOpenTransition.json 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..59e8b51412b8 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json @@ -0,0 +1,634 @@ +{ + "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 + ], + "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": 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 + } + ] + }, + { + "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": 188, + "height": 64.8 + }, + { + "width": 188, + "height": 44.4 + }, + { + "width": 188, + "height": 29.2 + }, + { + "width": 188, + "height": 18.4 + }, + { + "width": 188, + "height": 10.8 + }, + { + "width": 188, + "height": 5.6 + }, + { + "width": 188, + "height": 2.4 + }, + { + "width": 188, + "height": 0.4 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "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 + ] + } + ] +}
\ 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..210ff0985e78 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json @@ -0,0 +1,614 @@ +{ + "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 + ], + "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": 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": [ + { + "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": 188, + "height": 68 + }, + { + "width": 188, + "height": 49.6 + }, + { + "width": 188, + "height": 35.2 + }, + { + "width": 188, + "height": 24.4 + }, + { + "width": 188, + "height": 16.4 + }, + { + "width": 188, + "height": 10.4 + }, + { + "width": 188, + "height": 6.4 + }, + { + "width": 188, + "height": 3.6 + }, + { + "width": 188, + "height": 1.6 + }, + { + "width": 188, + "height": 0.4 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "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 + ] + } + ] +}
\ 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..d186df22dda0 --- /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": 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": 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" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "width": 188, + "height": 1.6 + }, + { + "width": 188, + "height": 1.6 + }, + { + "width": 188, + "height": 3.2 + }, + { + "width": 188, + "height": 4.8 + }, + { + "width": 188, + "height": 6.4 + }, + { + "width": 188, + "height": 8 + }, + { + "width": 188, + "height": 9.6 + }, + { + "width": 188, + "height": 11.2 + }, + { + "width": 188, + "height": 12.8 + }, + { + "width": 188, + "height": 14.4 + }, + { + "width": 188, + "height": 14.4 + }, + { + "width": 188, + "height": 16.4 + }, + { + "width": 188, + "height": 18 + }, + { + "width": 188, + "height": 19.6 + }, + { + "width": 188, + "height": 21.2 + }, + { + "width": 188, + "height": 22.8 + }, + { + "width": 188, + "height": 24.4 + }, + { + "width": 188, + "height": 26 + }, + { + "width": 188, + "height": 27.6 + }, + { + "width": 188, + "height": 27.6 + }, + { + "width": 188, + "height": 29.2 + }, + { + "width": 188, + "height": 30.8 + }, + { + "width": 188, + "height": 32.4 + }, + { + "width": 188, + "height": 34 + }, + { + "width": 188, + "height": 40.4 + }, + { + "width": 188, + "height": 52.4 + }, + { + "width": 188, + "height": 64.8 + }, + { + "width": 188, + "height": 83.2 + }, + { + "width": 188, + "height": 96 + }, + { + "width": 188, + "height": 114.8 + }, + { + "width": 188, + "height": 132 + }, + { + "width": 188, + "height": 148 + }, + { + "width": 188, + "height": 162 + }, + { + "width": 188, + "height": 168.4 + }, + { + "width": 188, + "height": 192.8 + }, + { + "width": 188, + "height": 229.6 + }, + { + "width": 188, + "height": 268 + }, + { + "width": 188, + "height": 302 + }, + { + "width": 188, + "height": 330 + }, + { + "width": 188, + "height": 351.6 + }, + { + "width": 188, + "height": 367.6 + }, + { + "width": 188, + "height": 379.2 + }, + { + "width": 188, + "height": 387.2 + }, + { + "width": 188, + "height": 392.4 + }, + { + "width": 188, + "height": 395.6 + }, + { + "width": 188, + "height": 398 + }, + { + "width": 188, + "height": 398.8 + }, + { + "width": 188, + "height": 399.6 + }, + { + "width": 188, + "height": 400 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + 0, + 0, + 0, + 0, + 0.0067873597, + 0.0612576, + 0.19080025, + 0.39327443, + 0.5711931, + 0.70855826, + 0.8074064, + 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..a9c24fa87089 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json @@ -0,0 +1,444 @@ +{ + "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": 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 + } + ] + }, + { + "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": 188, + "height": 71.2 + }, + { + "width": 188, + "height": 41.6 + }, + { + "width": 188, + "height": 21.6 + }, + { + "width": 188, + "height": 8.4 + }, + { + "width": 188, + "height": 0.4 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "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/floating_verticalReveal_gesture_flingOpen.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingOpen.json new file mode 100644 index 000000000000..f9279f1fae5c --- /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": 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 + } + ] + }, + { + "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": 188, + "height": 2 + }, + { + "width": 188, + "height": 4.4 + }, + { + "width": 188, + "height": 6.8 + }, + { + "width": 188, + "height": 10 + }, + { + "width": 188, + "height": 13.2 + }, + { + "width": 188, + "height": 16.8 + }, + { + "width": 188, + "height": 16.8 + }, + { + "width": 188, + "height": 25.2 + }, + { + "width": 188, + "height": 53.2 + }, + { + "width": 188, + "height": 119.6 + }, + { + "width": 188, + "height": 182 + }, + { + "width": 188, + "height": 235.6 + }, + { + "width": 188, + "height": 279.2 + }, + { + "width": 188, + "height": 313.2 + }, + { + "width": 188, + "height": 338.8 + }, + { + "width": 188, + "height": 357.6 + }, + { + "width": 188, + "height": 371.2 + }, + { + "width": 188, + "height": 380.8 + }, + { + "width": 188, + "height": 387.6 + }, + { + "width": 188, + "height": 392 + }, + { + "width": 188, + "height": 395.2 + }, + { + "width": 188, + "height": 396.8 + }, + { + "width": 188, + "height": 398 + }, + { + "width": 188, + "height": 398.8 + }, + { + "width": 188, + "height": 399.2 + }, + { + "width": 188, + "height": 399.6 + }, + { + "width": 188, + "height": 399.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..2504e57a927b --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_magneticDetachAndReattach.json @@ -0,0 +1,714 @@ +{ + "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 + ], + "features": [ + { + "name": "RevealElement_position", + "type": "dpOffset", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "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": 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 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "width": 188, + "height": 1.6 + }, + { + "width": 188, + "height": 2.8 + }, + { + "width": 188, + "height": 4 + }, + { + "width": 188, + "height": 5.2 + }, + { + "width": 188, + "height": 6.4 + }, + { + "width": 188, + "height": 7.6 + }, + { + "width": 188, + "height": 8.8 + }, + { + "width": 188, + "height": 10 + }, + { + "width": 188, + "height": 10.8 + }, + { + "width": 188, + "height": 12 + }, + { + "width": 188, + "height": 12.8 + }, + { + "width": 188, + "height": 13.6 + }, + { + "width": 188, + "height": 14.8 + }, + { + "width": 188, + "height": 15.6 + }, + { + "width": 188, + "height": 16.4 + }, + { + "width": 188, + "height": 17.2 + }, + { + "width": 188, + "height": 17.6 + }, + { + "width": 188, + "height": 18.4 + }, + { + "width": 188, + "height": 19.2 + }, + { + "width": 188, + "height": 19.6 + }, + { + "width": 188, + "height": 20 + }, + { + "width": 188, + "height": 20.4 + }, + { + "width": 188, + "height": 20.8 + }, + { + "width": 188, + "height": 21.2 + }, + { + "width": 188, + "height": 21.6 + }, + { + "width": 188, + "height": 21.6 + }, + { + "width": 188, + "height": 21.6 + }, + { + "width": 188, + "height": 21.6 + }, + { + "width": 188, + "height": 21.6 + }, + { + "width": 188, + "height": 21.6 + }, + { + "width": 188, + "height": 21.6 + }, + { + "width": 188, + "height": 21.6 + }, + { + "width": 188, + "height": 21.2 + }, + { + "width": 188, + "height": 20.8 + }, + { + "width": 188, + "height": 20.4 + }, + { + "width": 188, + "height": 20 + }, + { + "width": 188, + "height": 19.6 + }, + { + "width": 188, + "height": 19.2 + }, + { + "width": 188, + "height": 18.4 + }, + { + "width": 188, + "height": 17.6 + }, + { + "width": 188, + "height": 17.2 + }, + { + "width": 188, + "height": 16.4 + }, + { + "width": 188, + "height": 15.6 + }, + { + "width": 188, + "height": 14.8 + }, + { + "width": 188, + "height": 13.6 + }, + { + "width": 188, + "height": 12.8 + }, + { + "width": 188, + "height": 12 + }, + { + "width": 188, + "height": 10.8 + }, + { + "width": 188, + "height": 10 + }, + { + "width": 188, + "height": 8.8 + }, + { + "width": 188, + "height": 7.6 + }, + { + "width": 188, + "height": 6.4 + }, + { + "width": 188, + "height": 5.2 + }, + { + "width": 188, + "height": 4 + }, + { + "width": 188, + "height": 2.8 + }, + { + "width": 188, + "height": 1.6 + }, + { + "width": 188, + "height": 0.4 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + } + ] + }, + { + "name": "RevealElement_alpha", + "type": "float", + "data_points": [ + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + { + "type": "not_found" + }, + 0, + 0, + 0, + 0, + 0.012518823, + 0.0741024, + 0.2254293, + 0.42628878, + 0.5976641, + 0.7280312, + 0.82100236, + 0.8845844, + 0.9267946, + 0.95419544, + 0.9716705, + 0.98265487, + 0.98947525, + 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.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 + ] + } + ] +}
\ 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..86fac739372e --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_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": 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 + } + ] + }, + { + "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": 188, + "height": 58.8 + }, + { + "width": 188, + "height": 34.4 + }, + { + "width": 188, + "height": 18 + }, + { + "width": 188, + "height": 7.6 + }, + { + "width": 188, + "height": 0.8 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "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 + ] + } + ] +}
\ 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..ad282f216f8f --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealOpenTransition.json @@ -0,0 +1,244 @@ +{ + "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": 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 + } + ] + }, + { + "name": "RevealElement_size", + "type": "dpSize", + "data_points": [ + { + "type": "not_found" + }, + { + "width": 188, + "height": 0 + }, + { + "width": 188, + "height": 6.8 + }, + { + "width": 188, + "height": 21.6 + }, + { + "width": 188, + "height": 52.8 + }, + { + "width": 188, + "height": 129.6 + }, + { + "width": 188, + "height": 196.4 + }, + { + "width": 188, + "height": 250.4 + }, + { + "width": 188, + "height": 293.2 + }, + { + "width": 188, + "height": 325.2 + }, + { + "width": 188, + "height": 348.8 + }, + { + "width": 188, + "height": 365.6 + }, + { + "width": 188, + "height": 377.2 + }, + { + "width": 188, + "height": 385.6 + }, + { + "width": 188, + "height": 391.2 + }, + { + "width": 188, + "height": 394.8 + }, + { + "width": 188, + "height": 396.8 + }, + { + "width": 188, + "height": 398 + }, + { + "width": 188, + "height": 398.8 + }, + { + "width": 188, + "height": 399.2 + }, + { + "width": 188, + "height": 399.6 + }, + { + "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..ed73d100dc6b 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 @@ -98,7 +119,8 @@ class ContentRevealTest { }, gestureDurationMillis, ) - } + }, + "verticalReveal_gesture_magneticDetachAndReattach", ) } @@ -107,7 +129,8 @@ class ContentRevealTest { assertVerticalContainerRevealMotion( GestureRevealMotion(SceneClosed) { swipeDown(endY = 200.dp.toPx(), durationMillis = 500) - } + }, + "verticalReveal_gesture_dragOpen", ) } @@ -117,7 +140,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 +150,8 @@ class ContentRevealTest { assertVerticalContainerRevealMotion( GestureRevealMotion(SceneOpen) { swipeUp(200.dp.toPx(), 0.dp.toPx(), durationMillis = 500) - } + }, + "verticalReveal_gesture_dragFullyClose", ) } @@ -135,7 +160,8 @@ class ContentRevealTest { assertVerticalContainerRevealMotion( GestureRevealMotion(SceneOpen) { swipeUp(350.dp.toPx(), 100.dp.toPx(), durationMillis = 500) - } + }, + "verticalReveal_gesture_dragHalfClose", ) } @@ -146,7 +172,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 +191,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) } } @@ -241,7 +271,7 @@ class ContentRevealTest { recordingSpec, ) - assertThat(motion).timeSeriesMatchesGolden() + assertThat(motion).timeSeriesMatchesGolden(goldenName) } @Composable @@ -256,7 +286,7 @@ class ContentRevealTest { modifier = Modifier.element(RevealElement) .size(ContainerSize) - .edgeContainerExpansionBackground(Color.DarkGray, MotionSpec) + .verticalExpandContainerBackground(Color.DarkGray, motionSpec) ) } } @@ -266,6 +296,8 @@ class ContentRevealTest { } companion object { + @get:Parameters @JvmStatic val parameterValues = listOf(true, false) + val ContainerSize = DpSize(200.dp, 400.dp) val FlingVelocity = 1000.dp // dp/sec @@ -274,6 +306,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/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/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/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt index 046d92d58978..2ab36501d87d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt @@ -443,4 +443,24 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { Truth.assertThat(currentScene).isEqualTo(CommunalScenes.Communal) assertThat(transitionRepository).noTransitionsStarted() } + + @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun testDoNotTransitionToGlanceableHub_onWakeUpFromAodDueToMotion() = + kosmos.runTest { + setCommunalV2Available(true) + + val currentScene by collectLastValue(communalSceneInteractor.currentScene) + fakeCommunalSceneRepository.changeScene(CommunalScenes.Blank) + + // Communal is not showing + Truth.assertThat(currentScene).isEqualTo(CommunalScenes.Blank) + + powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LIFT) + testScope.advanceTimeBy(100) // account for debouncing + + Truth.assertThat(currentScene).isEqualTo(CommunalScenes.Blank) + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt index 096c3dafd01c..c3d18a3d893c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt @@ -20,7 +20,6 @@ import android.os.PowerManager import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization -import android.provider.Settings import android.service.dream.dreamManager import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState @@ -30,8 +29,6 @@ import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.Flags.glanceableHubV2 import com.android.systemui.SysuiTestCase -import com.android.systemui.common.data.repository.batteryRepository -import com.android.systemui.common.data.repository.fake import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository import com.android.systemui.communal.data.repository.communalSceneRepository import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository @@ -61,8 +58,6 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos -import com.android.systemui.user.data.repository.fakeUserRepository -import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth import junit.framework.Assert.assertEquals import kotlinx.coroutines.flow.flowOf @@ -171,15 +166,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT fun testTransitionToLockscreen_onWake_canDream_ktfRefactor() = kosmos.runTest { setCommunalAvailable(true) - if (glanceableHubV2()) { - val user = fakeUserRepository.asMainUser() - fakeSettings.putIntForUser( - Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, - 1, - user.id, - ) - batteryRepository.fake.setDevicePluggedIn(true) - } else { + if (!glanceableHubV2()) { whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true) } @@ -226,15 +213,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT fun testTransitionToGlanceableHub_onWakeup_ifAvailable() = kosmos.runTest { setCommunalAvailable(true) - if (glanceableHubV2()) { - val user = fakeUserRepository.asMainUser() - fakeSettings.putIntForUser( - Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, - 1, - user.id, - ) - batteryRepository.fake.setDevicePluggedIn(true) - } else { + if (!glanceableHubV2()) { whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true) } @@ -250,6 +229,25 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT } @Test + @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_SCENE_CONTAINER) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun testTransitionToLockscreen_onWakeupFromLift() = + kosmos.runTest { + setCommunalAvailable(true) + if (!glanceableHubV2()) { + whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true) + } + + // Device turns on. + powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LIFT) + testScope.advanceTimeBy(51L) + + // We transition to the lockscreen instead of the hub. + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN) + } + + @Test @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR) fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() = kosmos.runTest { 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/qs/tiles/ModesDndTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesDndTileTest.kt new file mode 100644 index 000000000000..1adba6fcd45d --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesDndTileTest.kt @@ -0,0 +1,211 @@ +/* + * 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.qs.tiles + +import android.app.Flags +import android.os.Handler +import android.platform.test.annotations.EnableFlags +import android.service.quicksettings.Tile +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.settingslib.notification.modes.TestModeBuilder +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.kosmos.mainCoroutineContext +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.qs.QSTile.BooleanState +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.shared.QSSettingsPackageRepository +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileDataInteractor +import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel +import com.android.systemui.qs.tiles.impl.modes.ui.ModesDndTileMapper +import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider +import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.data.repository.zenModeRepository +import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor +import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any +import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.SecureSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runCurrent +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.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.whenever + +@EnableFlags(Flags.FLAG_MODES_UI) +@SmallTest +@RunWith(AndroidJUnit4::class) +@RunWithLooper(setAsMainLooper = true) +class ModesDndTileTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val testDispatcher = kosmos.testDispatcher + + @Mock private lateinit var qsHost: QSHost + + @Mock private lateinit var metricsLogger: MetricsLogger + + @Mock private lateinit var statusBarStateController: StatusBarStateController + + @Mock private lateinit var activityStarter: ActivityStarter + + @Mock private lateinit var qsLogger: QSLogger + + @Mock private lateinit var uiEventLogger: QsEventLogger + + @Mock private lateinit var qsTileConfigProvider: QSTileConfigProvider + + @Mock private lateinit var dialogDelegate: ModesDialogDelegate + + @Mock private lateinit var settingsPackageRepository: QSSettingsPackageRepository + + private val inputHandler = FakeQSTileIntentUserInputHandler() + private val zenModeRepository = kosmos.zenModeRepository + private val tileDataInteractor = + ModesDndTileDataInteractor(context, kosmos.zenModeInteractor, testDispatcher) + private val mapper = ModesDndTileMapper(context.resources, context.theme) + + private lateinit var userActionInteractor: ModesDndTileUserActionInteractor + private lateinit var secureSettings: SecureSettings + private lateinit var testableLooper: TestableLooper + private lateinit var underTest: ModesDndTile + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + secureSettings = FakeSettings() + + // Allow the tile to load resources + whenever(qsHost.context).thenReturn(context) + whenever(qsHost.userContext).thenReturn(context) + + whenever(qsTileConfigProvider.getConfig(any())) + .thenReturn( + QSTileConfigTestBuilder.build { + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_dnd_icon_off, + labelRes = R.string.quick_settings_dnd_label, + ) + } + ) + + userActionInteractor = + ModesDndTileUserActionInteractor( + kosmos.mainCoroutineContext, + inputHandler, + dialogDelegate, + kosmos.zenModeInteractor, + kosmos.modesDialogEventLogger, + settingsPackageRepository, + ) + + underTest = + ModesDndTile( + qsHost, + uiEventLogger, + testableLooper.looper, + Handler(testableLooper.looper), + FalsingManagerFake(), + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + qsTileConfigProvider, + tileDataInteractor, + mapper, + userActionInteractor, + ) + + underTest.initialize() + underTest.setListening(Object(), true) + + testableLooper.processAllMessages() + } + + @After + fun tearDown() { + underTest.destroy() + testableLooper.processAllMessages() + } + + @Test + fun stateUpdatesOnChange() = + testScope.runTest { + assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE) + + zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND) + runCurrent() + testableLooper.processAllMessages() + + assertThat(underTest.state.state).isEqualTo(Tile.STATE_ACTIVE) + } + + @Test + fun handleUpdateState_withModel_updatesState() = + testScope.runTest { + val tileState = + BooleanState().apply { + state = Tile.STATE_INACTIVE + secondaryLabel = "Old secondary label" + } + val model = ModesDndTileModel(isActivated = true) + + underTest.handleUpdateState(tileState, model) + + assertThat(tileState.state).isEqualTo(Tile.STATE_ACTIVE) + assertThat(tileState.secondaryLabel).isEqualTo("On") + } + + @Test + fun handleUpdateState_withNull_updatesState() = + testScope.runTest { + val tileState = + BooleanState().apply { + state = Tile.STATE_INACTIVE + secondaryLabel = "Old secondary label" + } + zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND) + runCurrent() + + underTest.handleUpdateState(tileState, null) + + assertThat(tileState.state).isEqualTo(Tile.STATE_ACTIVE) + assertThat(tileState.secondaryLabel).isEqualTo("On") + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractorTest.kt new file mode 100644 index 000000000000..23d7b86df875 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractorTest.kt @@ -0,0 +1,118 @@ +/* + * 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.qs.tiles.impl.modes.domain.interactor + +import android.app.Flags +import android.os.UserHandle +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.notification.modes.TestModeBuilder +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository +import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toCollection +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@EnableFlags(Flags.FLAG_MODES_UI) +@RunWith(AndroidJUnit4::class) +class ModesDndTileDataInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val dispatcher = kosmos.testDispatcher + private val zenModeRepository = kosmos.fakeZenModeRepository + + private val underTest by lazy { + ModesDndTileDataInteractor(context, kosmos.zenModeInteractor, dispatcher) + } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI_DND_TILE) + fun availability_flagOn_isTrue() = + testScope.runTest { + val availability = underTest.availability(TEST_USER).toCollection(mutableListOf()) + + assertThat(availability).containsExactly(true) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI_DND_TILE) + fun availability_flagOff_isFalse() = + testScope.runTest { + val availability = underTest.availability(TEST_USER).toCollection(mutableListOf()) + + assertThat(availability).containsExactly(false) + } + + @Test + fun tileData_dndChanges_updateActivated() = + testScope.runTest { + val model by + collectLastValue( + underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + runCurrent() + assertThat(model!!.isActivated).isFalse() + + zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND) + runCurrent() + assertThat(model!!.isActivated).isTrue() + + zenModeRepository.deactivateMode(TestModeBuilder.MANUAL_DND) + runCurrent() + assertThat(model!!.isActivated).isFalse() + } + + @Test + fun tileData_otherModeChanges_notActivated() = + testScope.runTest { + val model by + collectLastValue( + underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + runCurrent() + assertThat(model!!.isActivated).isFalse() + + zenModeRepository.addMode("Other mode") + runCurrent() + assertThat(model!!.isActivated).isFalse() + + zenModeRepository.activateMode("Other mode") + runCurrent() + assertThat(model!!.isActivated).isFalse() + } + + private companion object { + val TEST_USER = UserHandle.of(1)!! + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..0a35b428bbc9 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractorTest.kt @@ -0,0 +1,134 @@ +/* + * 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.qs.tiles.impl.modes.domain.interactor + +import android.platform.test.annotations.EnableFlags +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.mainCoroutineContext +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.shared.QSSettingsPackageRepository +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.modes.domain.model.ModesDndTileModel +import com.android.systemui.statusbar.policy.data.repository.zenModeRepository +import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor +import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +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.mock +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableFlags(android.app.Flags.FLAG_MODES_UI) +class ModesDndTileUserActionInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val inputHandler = kosmos.qsTileIntentUserInputHandler + private val mockDialogDelegate = kosmos.mockModesDialogDelegate + private val zenModeRepository = kosmos.zenModeRepository + private val zenModeInteractor = kosmos.zenModeInteractor + private val settingsPackageRepository = mock<QSSettingsPackageRepository>() + + private val underTest = + ModesDndTileUserActionInteractor( + kosmos.mainCoroutineContext, + inputHandler, + mockDialogDelegate, + zenModeInteractor, + kosmos.modesDialogEventLogger, + settingsPackageRepository, + ) + + @Before + fun setUp() { + whenever(settingsPackageRepository.getSettingsPackageName()).thenReturn(SETTINGS_PACKAGE) + } + + @Test + fun handleClick_dndActive_deactivatesDnd() = + testScope.runTest { + val dndMode by collectLastValue(zenModeInteractor.dndMode) + zenModeRepository.activateMode(MANUAL_DND) + assertThat(dndMode?.isActive).isTrue() + + underTest.handleInput(QSTileInputTestKtx.click(data = ModesDndTileModel(true))) + + assertThat(dndMode?.isActive).isFalse() + } + + @Test + fun handleClick_dndInactive_activatesDnd() = + testScope.runTest { + val dndMode by collectLastValue(zenModeInteractor.dndMode) + assertThat(dndMode?.isActive).isFalse() + + underTest.handleInput(QSTileInputTestKtx.click(data = ModesDndTileModel(false))) + + assertThat(dndMode?.isActive).isTrue() + } + + @Test + fun handleLongClick_active_opensSettings() = + testScope.runTest { + zenModeRepository.activateMode(MANUAL_DND) + runCurrent() + + underTest.handleInput(QSTileInputTestKtx.longClick(ModesDndTileModel(true))) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.`package`).isEqualTo(SETTINGS_PACKAGE) + assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS) + assertThat(it.intent.getStringExtra(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID)) + .isEqualTo(MANUAL_DND.id) + } + } + + @Test + fun handleLongClick_inactive_opensSettings() = + testScope.runTest { + zenModeRepository.activateMode(MANUAL_DND) + zenModeRepository.deactivateMode(MANUAL_DND) + runCurrent() + + underTest.handleInput(QSTileInputTestKtx.longClick(ModesDndTileModel(false))) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.`package`).isEqualTo(SETTINGS_PACKAGE) + assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS) + assertThat(it.intent.getStringExtra(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID)) + .isEqualTo(MANUAL_DND.id) + } + } + + companion object { + private const val SETTINGS_PACKAGE = "the.settings.package" + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapperTest.kt new file mode 100644 index 000000000000..29f642a4325d --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapperTest.kt @@ -0,0 +1,80 @@ +/* + * 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.qs.tiles.impl.modes.ui + +import android.app.Flags +import android.graphics.drawable.TestStubDrawable +import android.platform.test.annotations.EnableFlags +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.qs.tiles.impl.modes.domain.model.ModesDndTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.res.R +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableFlags(Flags.FLAG_MODES_UI) +class ModesDndTileMapperTest : SysuiTestCase() { + val config = + QSTileConfigTestBuilder.build { + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_dnd_icon_off, + labelRes = R.string.quick_settings_modes_label, + ) + } + + val underTest = + ModesDndTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_dnd_icon_on, TestStubDrawable()) + addOverride(R.drawable.qs_dnd_icon_off, TestStubDrawable()) + } + .resources, + context.theme, + ) + + @Test + fun map_inactiveState() { + val model = ModesDndTileModel(isActivated = false) + + val state = underTest.map(config, model) + + assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE) + assertThat((state.icon as Icon.Loaded).res).isEqualTo(R.drawable.qs_dnd_icon_off) + assertThat(state.secondaryLabel).isEqualTo("Off") + } + + @Test + fun map_activeState() { + val model = ModesDndTileModel(isActivated = true) + + val state = underTest.map(config, model) + + assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE) + assertThat((state.icon as Icon.Loaded).res).isEqualTo(R.drawable.qs_dnd_icon_on) + assertThat(state.secondaryLabel).isEqualTo("On") + } +} 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/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/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/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/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/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index 65763a359c0f..f9405af3f85d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -16,7 +16,9 @@ package com.android.systemui.statusbar.notification.collection.coordinator; +import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; @@ -340,6 +342,13 @@ public class RankingCoordinatorTest extends SysuiTestCase { } @Test + public void testAlertingSectioner_rejectsBundle() { + for (String id : SYSTEM_RESERVED_IDS) { + assertFalse(mAlertingSectioner.isInSection(makeClassifiedNotifEntry(id))); + } + } + + @Test public void statusBarStateCallbackTest() { mStatusBarStateCallback.onDozeAmountChanged(1f, 1f); verify(mInvalidationListener, times(1)) @@ -392,4 +401,11 @@ public class RankingCoordinatorTest extends SysuiTestCase { .build()); assertEquals(ambient, mEntry.getRanking().isAmbient()); } + + private NotificationEntry makeClassifiedNotifEntry(String channelId) { + NotificationChannel channel = new NotificationChannel(channelId, channelId, IMPORTANCE_LOW); + return new NotificationEntryBuilder() + .updateRanking((rankingBuilder -> rankingBuilder.setChannel(channel))) + .build(); + } } 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..b5f3269903b8 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 @@ -64,6 +65,7 @@ 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 @@ -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/shared/TestActiveNotificationModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt index 531b30b9547a..0fb0548582ce 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt @@ -18,27 +18,27 @@ package com.android.systemui.statusbar.notification.shared import com.google.common.truth.Correspondence val byKey: Correspondence<ActiveNotificationModel, String> = - Correspondence.transforming({ it?.key }, "has a key of") + Correspondence.transforming({ it.key }, "has a key of") val byIsAmbient: Correspondence<ActiveNotificationModel, Boolean> = - Correspondence.transforming({ it?.isAmbient }, "has an isAmbient value of") + Correspondence.transforming({ it.isAmbient }, "has an isAmbient value of") val byIsSuppressedFromStatusBar: Correspondence<ActiveNotificationModel, Boolean> = Correspondence.transforming( - { it?.isSuppressedFromStatusBar }, + { it.isSuppressedFromStatusBar }, "has an isSuppressedFromStatusBar value of", ) val byIsSilent: Correspondence<ActiveNotificationModel, Boolean> = - Correspondence.transforming({ it?.isSilent }, "has an isSilent value of") + Correspondence.transforming({ it.isSilent }, "has an isSilent value of") val byIsRowDismissed: Correspondence<ActiveNotificationModel, Boolean> = - Correspondence.transforming({ it?.isRowDismissed }, "has an isRowDismissed value of") + Correspondence.transforming({ it.isRowDismissed }, "has an isRowDismissed value of") val byIsLastMessageFromReply: Correspondence<ActiveNotificationModel, Boolean> = Correspondence.transforming( - { it?.isLastMessageFromReply }, + { it.isLastMessageFromReply }, "has an isLastMessageFromReply value of", ) val byIsPulsing: Correspondence<ActiveNotificationModel, Boolean> = - Correspondence.transforming({ it?.isPulsing }, "has an isPulsing value of") + Correspondence.transforming({ it.isPulsing }, "has an isPulsing value of") val byIsPromoted: Correspondence<ActiveNotificationModel, Boolean> = Correspondence.transforming( - { it?.promotedContent != null }, + { it.promotedContent != null }, "has (or doesn't have) a promoted content model", ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 08ecbac1582c..41cca19346f0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy import com.android.systemui.testKosmos import com.google.common.truth.Expect import com.google.common.truth.Truth.assertThat +import kotlin.math.roundToInt import org.junit.Assume import org.junit.Before import org.junit.Rule @@ -1572,7 +1573,11 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { fullStackHeight: Float = 3000f, ) { ambientState.headsUpTop = headsUpTop - ambientState.headsUpBottom = headsUpBottom + if (NotificationsHunSharedAnimationValues.isEnabled) { + headsUpAnimator.headsUpAppearHeightBottom = headsUpBottom.roundToInt() + } else { + ambientState.headsUpBottom = headsUpBottom + } ambientState.stackTop = stackTop ambientState.stackCutoff = stackCutoff 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/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/config.xml b/packages/SystemUI/res/values/config.xml index 78e719f6289a..549fdefd8f7a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -115,7 +115,7 @@ <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" --> <string name="quick_settings_tiles_stock" translatable="false"> - internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes,desktopeffects + internet,bt,flashlight,dnd,modes_dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes,desktopeffects </string> <!-- The tiles to display in QuickSettings --> 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/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml index faf06f3d39f0..bcd49b91d894 100644 --- a/packages/SystemUI/res/values/tiles_states_strings.xml +++ b/packages/SystemUI/res/values/tiles_states_strings.xml @@ -85,6 +85,16 @@ <item>On</item> </string-array> + <!-- State names for dnd (Do not disturb) mode tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_modes_dnd"> + <item>Unavailable</item> + <item>Off</item> + <item>On</item> + </string-array> + <!-- State names for flashlight tile: unavailable, off, on. This subtitle is shown when the tile is in that particular state but does not set its own subtitle, so some of these may never appear on screen. They should still be translated as 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/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/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt index 39f55803bb73..c4e1ccf6b62e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt @@ -31,7 +31,7 @@ import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.biometrics.shared.model.toSensorStrength import com.android.systemui.biometrics.shared.model.toSensorType import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt index 230b30bc548e..cce33fdf16c1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt @@ -21,7 +21,7 @@ import android.util.Log import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt index 40313e3158aa..6484116233ca 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt @@ -21,7 +21,7 @@ import android.content.res.Configuration import com.android.systemui.biometrics.data.repository.DisplayStateRepository import com.android.systemui.biometrics.shared.model.DisplayRotation import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.display.data.repository.DisplayRepository diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt index 7f1cb5da474d..dea3c472a476 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt @@ -21,7 +21,7 @@ import android.util.Log import com.android.settingslib.bluetooth.BluetoothCallback import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background 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/bluetooth/qsdialog/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt index 55d4d3efbe27..9e0f10277197 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt @@ -22,7 +22,7 @@ import android.bluetooth.BluetoothAdapter.STATE_ON import com.android.settingslib.bluetooth.BluetoothCallback import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt index b606c19b3503..e458b8092cda 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt @@ -24,7 +24,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.volume.domain.interactor.AudioModeInteractor import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt index b9e1c55fbade..89208364178d 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt @@ -28,7 +28,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.bouncer.data.model.SimBouncerModel import com.android.systemui.bouncer.data.model.SimPukInputModel import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt index e6d6293733d4..636b3ab66dd5 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt @@ -24,7 +24,7 @@ import com.android.systemui.brightness.shared.model.BrightnessLog import com.android.systemui.brightness.shared.model.LinearBrightness import com.android.systemui.brightness.shared.model.formatBrightness import com.android.systemui.brightness.shared.model.logDiffForTable -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt index 183a3cc26b95..724670d955dd 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt @@ -32,7 +32,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.BroadcastRunning import com.android.systemui.dagger.qualifiers.Main diff --git a/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt index 7816a1487c01..dac5b7efaade 100644 --- a/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt @@ -19,7 +19,7 @@ package com.android.systemui.camera.data.repository import android.hardware.SensorPrivacyManager import android.hardware.SensorPrivacyManager.Sensors.CAMERA import android.os.UserHandle -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background 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/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt index ae89b39175c1..0d7a2d9707d7 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt @@ -17,7 +17,7 @@ package com.android.systemui.communal.domain.interactor import android.content.pm.UserInfo -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN import com.android.systemui.communal.data.model.FEATURE_ENABLED import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN 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/controls/settings/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt index 6f579a3986c8..d7ffbb2e76b8 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt @@ -18,7 +18,7 @@ package com.android.systemui.controls.settings import android.provider.Settings -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt index 69378b475938..449a995b782a 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt @@ -27,7 +27,7 @@ import com.android.systemui.Dumpable import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt index 675f00a89d23..b7315cc994a8 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt @@ -1,7 +1,7 @@ package com.android.systemui.deviceentry.data.repository import com.android.internal.widget.LockPatternUtils -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background 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/DeviceStateRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt index 29044d017d2d..f4db2cc71b38 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt @@ -27,7 +27,7 @@ import android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFI import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.feature.flags.Flags as DeviceStateManagerFlags import com.android.internal.R -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState import java.util.concurrent.Executor diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt index cef45dcae43e..3c554b9ff66b 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt @@ -19,7 +19,7 @@ package com.android.systemui.display.data.repository import android.content.Context import android.content.res.Configuration import android.util.DisplayMetrics -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer 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/flags/PluggedInCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt index dc08570447a5..e5920924a4be 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt @@ -16,7 +16,7 @@ package com.android.systemui.flags -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.statusbar.policy.BatteryController import dagger.Lazy import javax.inject.Inject 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/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt index 922bc15c0633..4e7164ff12d7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt @@ -21,7 +21,7 @@ import android.hardware.input.InputManager.StickyModifierStateListener import android.hardware.input.StickyModifierState import android.provider.Settings import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.stickykeys.StickyKeysLogger 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/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/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt index 07ed194dd68f..40861929add7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt @@ -30,7 +30,7 @@ import com.android.settingslib.notification.modes.EnableDndDialogFactory import com.android.settingslib.notification.modes.EnableDndDialogMetricsLogger import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt index e2642a0964c1..683c11a88b89 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt @@ -20,7 +20,7 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt index 3555f06ce96f..a1dafb1438ca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt @@ -24,7 +24,7 @@ import androidx.annotation.DrawableRes import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.controls.ControlsServiceInfo diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt index ad79177fdd76..01ff0e1344c6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt @@ -23,7 +23,7 @@ import android.content.SharedPreferences import com.android.systemui.backup.BackupHelper import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt index 1c9bc9f39663..10fc4c2a02ff 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt @@ -23,7 +23,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.animation.Expandable -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt index d12c42a754f0..7c33e29bf25a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt @@ -21,7 +21,7 @@ import android.content.Context import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt index 760adbf58d93..56ea26e88b23 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt @@ -26,7 +26,7 @@ import android.service.quickaccesswallet.WalletCard import android.util.Log import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index 0f5f31302670..30476b991baf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -35,7 +35,7 @@ import com.android.systemui.biometrics.data.repository.FingerprintPropertyReposi import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt index 4d999df69588..396f60645f00 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt @@ -24,7 +24,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.shared.model.AuthenticationReason import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt index 7c430920cb46..59e6a08c4511 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt @@ -17,7 +17,7 @@ package com.android.systemui.keyguard.data.repository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.shared.model.DevicePosture diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index affcd33b7170..cd0efdae337d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -24,7 +24,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main 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/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt index c5a6fa199c58..63a0286832d0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt @@ -20,7 +20,7 @@ import android.app.trust.TrustManager import com.android.keyguard.TrustGrantFlags import com.android.keyguard.logging.TrustRepositoryLogger import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 54af8f5b9806..f53421d539fe 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -101,14 +101,14 @@ constructor( ) .collect { ( - _, + detailedWakefulness, startedStep, canWakeDirectlyToGone, ) -> val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value val biometricUnlockMode = keyguardInteractor.biometricUnlockState.value.mode val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value - val shouldShowCommunal = communalSettingsInteractor.autoOpenEnabled.value + val autoOpenCommunal = communalSettingsInteractor.autoOpenEnabled.value if (!maybeHandleInsecurePowerGesture()) { val shouldTransitionToLockscreen = @@ -135,8 +135,12 @@ constructor( (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen()) || (KeyguardWmStateRefactor.isEnabled && canWakeDirectlyToGone) + // Avoid transitioning to communal automatically if the device is waking + // up due to motion. val shouldTransitionToCommunal = - communalSettingsInteractor.isV2FlagEnabled() && shouldShowCommunal + communalSettingsInteractor.isV2FlagEnabled() && + autoOpenCommunal && + !detailedWakefulness.isAwakeFromMotionOrLift() if (shouldTransitionToGone) { // TODO(b/360368320): Adapt for scene framework diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 1fc41085f772..4aaa1fab4c65 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -34,8 +34,9 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakefulnessModel import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.util.kotlin.Utils.Companion.sample +import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -121,9 +122,10 @@ constructor( private fun shouldTransitionToCommunal( shouldShowCommunal: Boolean, isCommunalAvailable: Boolean, + wakefulness: WakefulnessModel, ) = if (communalSettingsInteractor.isV2FlagEnabled()) { - shouldShowCommunal + shouldShowCommunal && !wakefulness.isAwakeFromMotionOrLift() } else { isCommunalAvailable && dreamManager.canStartDreaming(false) } @@ -148,14 +150,14 @@ constructor( } scope.launch { - powerInteractor.isAwake + powerInteractor.detailedWakefulness .debounce(50L) - .filterRelevantKeyguardStateAnd { isAwake -> isAwake } - .sample( + .filterRelevantKeyguardStateAnd { wakefulness -> wakefulness.isAwake() } + .sampleCombine( communalInteractor.isCommunalAvailable, communalSettingsInteractor.autoOpenEnabled, ) - .collect { (_, isCommunalAvailable, shouldShowCommunal) -> + .collect { (detailedWakefulness, isCommunalAvailable, shouldShowCommunal) -> val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value val isKeyguardGoingAway = keyguardInteractor.isKeyguardGoingAway.value @@ -186,7 +188,11 @@ constructor( } else if (isKeyguardOccludedLegacy) { startTransitionTo(KeyguardState.OCCLUDED) } else if ( - shouldTransitionToCommunal(shouldShowCommunal, isCommunalAvailable) + shouldTransitionToCommunal( + shouldShowCommunal, + isCommunalAvailable, + detailedWakefulness, + ) ) { if (!SceneContainerFlag.isEnabled) { transitionToGlanceableHub() @@ -208,7 +214,7 @@ constructor( scope.launch { powerInteractor.detailedWakefulness .filterRelevantKeyguardStateAnd { it.isAwake() } - .sample( + .sampleCombine( communalSettingsInteractor.autoOpenEnabled, communalInteractor.isCommunalAvailable, keyguardInteractor.biometricUnlockState, @@ -217,7 +223,7 @@ constructor( ) .collect { ( - _, + detailedWakefulness, shouldShowCommunal, isCommunalAvailable, biometricUnlockState, @@ -245,7 +251,11 @@ constructor( ) } } else if ( - shouldTransitionToCommunal(shouldShowCommunal, isCommunalAvailable) + shouldTransitionToCommunal( + shouldShowCommunal, + isCommunalAvailable, + detailedWakefulness, + ) ) { if (!SceneContainerFlag.isEnabled) { transitionToGlanceableHub() 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..07a31e16384c 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,33 @@ 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() + } + + val iter: MutableIterator<ShowLockscreenCallback> = callbacks.listIterator() + while (iter.hasNext()) { + val callback = iter.next() + iter.remove() + if (callback.userId != selectedUserInteractor.getSelectedUserId()) { + Log.i(TAG, "Not notifying lockNowCallback due to user mismatch") + continue + } + 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/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt index 4ff54d4eae65..42d27619f60f 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt @@ -26,7 +26,7 @@ import android.os.IBinder import android.util.Log import android.view.Display import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt index 43bd6aa37b5a..faa77e51ec24 100644 --- a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt @@ -24,7 +24,7 @@ import android.content.IntentFilter import android.os.PowerManager import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.power.shared.model.DozeScreenStateModel diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt index 297c6af5a4a7..f368c53c5b39 100644 --- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt +++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt @@ -61,6 +61,11 @@ data class WakefulnessModel( (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE) } + fun isAwakeFromMotionOrLift(): Boolean { + return isAwake() && + (lastWakeReason == WakeSleepReason.MOTION || lastWakeReason == WakeSleepReason.LIFT) + } + override fun logDiffs(prevVal: WakefulnessModel, row: TableRowLogger) { row.logChange(columnName = "wakefulness", value = toString()) } 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/footer/data/repository/ForegroundServicesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt index bd9d70c13572..eb99fec7a0a8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt @@ -17,7 +17,7 @@ package com.android.systemui.qs.footer.data.repository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.FgsManagerController import javax.inject.Inject 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/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/pipeline/data/repository/QSSettingsRestoredRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt index 1cd5d100ec00..e3a8ffd0f480 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt @@ -6,7 +6,7 @@ import android.os.UserHandle import android.provider.Settings import android.util.Log import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.common.shared.model.PackageChangeModel.Empty.user import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt index 88a49ee109aa..53d2554f0e82 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt @@ -16,7 +16,7 @@ package com.android.systemui.qs.pipeline.domain.autoaddable -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking import com.android.systemui.qs.pipeline.domain.model.AutoAddable diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt index 76bfad936116..a3b4c71c7641 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt @@ -16,7 +16,7 @@ package com.android.systemui.qs.pipeline.domain.autoaddable -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt index e9c91ca0db12..66af6d8b3e18 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt @@ -19,7 +19,7 @@ package com.android.systemui.qs.pipeline.domain.autoaddable import android.content.Context import android.hardware.display.ColorDisplayManager import android.hardware.display.NightDisplayListener -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.NightDisplayListenerModule import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt index 88d7f06dfada..ff3fd3781181 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt @@ -21,7 +21,7 @@ import android.content.pm.PackageManager import android.content.res.Resources import android.text.TextUtils import com.android.systemui.res.R -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt index 3f619c08261d..c66c9dc9ba6a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.pipeline.domain.autoaddable import android.content.pm.UserInfo import android.os.UserHandle -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt index 61a8fa3d2a6e..cd0b70e5e988 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt @@ -27,6 +27,7 @@ object SubtitleArrayMapping { subtitleIdsMap["cell"] = R.array.tile_states_cell subtitleIdsMap["battery"] = R.array.tile_states_battery subtitleIdsMap["dnd"] = R.array.tile_states_dnd + subtitleIdsMap["modes_dnd"] = R.array.tile_states_modes_dnd subtitleIdsMap["flashlight"] = R.array.tile_states_flashlight subtitleIdsMap["rotation"] = R.array.tile_states_rotation subtitleIdsMap["bt"] = R.array.tile_states_bt diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesDndTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesDndTile.kt new file mode 100644 index 000000000000..52b02066c35a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesDndTile.kt @@ -0,0 +1,135 @@ +/* + * 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.qs.tiles + +import android.content.Intent +import android.os.Handler +import android.os.Looper +import androidx.annotation.DrawableRes +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.coroutineScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.internal.logging.MetricsLogger +import com.android.systemui.animation.Expandable +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.modes.shared.ModesUi +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.qs.QSTile.BooleanState +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.asQSTileIcon +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileDataInteractor +import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel +import com.android.systemui.qs.tiles.impl.modes.ui.ModesDndTileMapper +import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.runBlocking + +/** + * Standalone tile used to control the DND Mode. Contrast to [ModesTile] (the tile that opens a + * dialog showing the list of all modes) and [DndTile] (the tile used to toggle interruption + * filtering in the pre-MODES_UI world). + */ +class ModesDndTile +@Inject +constructor( + host: QSHost, + uiEventLogger: QsEventLogger, + @Background backgroundLooper: Looper, + @Main mainHandler: Handler, + falsingManager: FalsingManager, + metricsLogger: MetricsLogger, + statusBarStateController: StatusBarStateController, + activityStarter: ActivityStarter, + qsLogger: QSLogger, + qsTileConfigProvider: QSTileConfigProvider, + private val dataInteractor: ModesDndTileDataInteractor, + private val tileMapper: ModesDndTileMapper, + private val userActionInteractor: ModesDndTileUserActionInteractor, +) : + QSTileImpl<BooleanState>( + host, + uiEventLogger, + backgroundLooper, + mainHandler, + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + ) { + + private lateinit var tileState: QSTileState + private val config = qsTileConfigProvider.getConfig(TILE_SPEC) + + init { + lifecycle.coroutineScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) { + dataInteractor.tileData().collect { refreshState(it) } + } + } + } + + override fun isAvailable(): Boolean = ModesUi.isEnabled && android.app.Flags.modesUiDndTile() + + override fun getTileLabel(): CharSequence = + mContext.getString(R.string.quick_settings_dnd_label) + + override fun newTileState(): BooleanState = BooleanState() + + override fun handleClick(expandable: Expandable?) = runBlocking { + userActionInteractor.handleClick() + } + + override fun getLongClickIntent(): Intent? = userActionInteractor.getSettingsIntent() + + @VisibleForTesting + public override fun handleUpdateState(state: BooleanState?, arg: Any?) { + val model = arg as? ModesDndTileModel ?: dataInteractor.getCurrentTileModel() + + tileState = tileMapper.map(config, model) + state?.apply { + value = model.isActivated + this.state = tileState.activationState.legacyState + icon = + tileState.icon?.asQSTileIcon() + ?: maybeLoadResourceIcon(iconResId(model.isActivated)) + label = tileLabel + secondaryLabel = tileState.secondaryLabel + contentDescription = tileState.contentDescription + expandedAccessibilityClassName = tileState.expandedAccessibilityClassName + } + } + + @DrawableRes + private fun iconResId(activated: Boolean): Int = + if (activated) R.drawable.qs_dnd_icon_on else R.drawable.qs_dnd_icon_off + + companion object { + const val TILE_SPEC = "modes_dnd" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt index 1544804c3291..38eb5947bd71 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt @@ -17,7 +17,7 @@ package com.android.systemui.qs.tiles.impl.flashlight.domain.interactor import android.os.UserHandle -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractor.kt new file mode 100644 index 000000000000..b1ae3ba4381a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractor.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.modes.domain.interactor + +import android.content.Context +import android.os.UserHandle +import com.android.app.tracing.coroutines.flow.flowName +import com.android.settingslib.notification.modes.ZenMode +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.modes.shared.ModesUi +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel +import com.android.systemui.shade.ShadeDisplayAware +import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +class ModesDndTileDataInteractor +@Inject +constructor( + @ShadeDisplayAware val context: Context, + val zenModeInteractor: ZenModeInteractor, + @Background val bgDispatcher: CoroutineDispatcher, +) : QSTileDataInteractor<ModesDndTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger>, + ): Flow<ModesDndTileModel> = tileData() + + /** + * An adapted version of the base class' [tileData] method for use in an old-style tile. + * + * TODO(b/299909989): Remove after the transition. + */ + fun tileData() = + zenModeInteractor.dndMode + .filterNotNull() + .map { dndMode -> buildTileData(dndMode) } + .flowName("tileData") + .flowOn(bgDispatcher) + .distinctUntilChanged() + + fun getCurrentTileModel() = buildTileData(zenModeInteractor.getDndMode()) + + private fun buildTileData(dndMode: ZenMode): ModesDndTileModel { + return ModesDndTileModel(isActivated = dndMode.isActive) + } + + override fun availability(user: UserHandle): Flow<Boolean> = + flowOf(ModesUi.isEnabled && android.app.Flags.modesUiDndTile()) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractor.kt new file mode 100644 index 000000000000..e8fcea070ede --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractor.kt @@ -0,0 +1,113 @@ +/* + * 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.qs.tiles.impl.modes.domain.interactor + +import android.content.Intent +import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS +import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID +import android.util.Log +import com.android.systemui.animation.Expandable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.shared.QSSettingsPackageRepository +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor +import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.withContext + +@SysUISingleton +class ModesDndTileUserActionInteractor +@Inject +constructor( + @Main private val mainContext: CoroutineContext, + private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler, + // TODO(b/353896370): The domain layer should not have to depend on the UI layer. + private val dialogDelegate: ModesDialogDelegate, + private val zenModeInteractor: ZenModeInteractor, + private val dialogEventLogger: ModesDialogEventLogger, + private val settingsPackageRepository: QSSettingsPackageRepository, +) : QSTileUserActionInteractor<ModesDndTileModel> { + + override suspend fun handleInput(input: QSTileInput<ModesDndTileModel>) { + with(input) { + when (action) { + is QSTileUserAction.Click, + is QSTileUserAction.ToggleClick -> { + handleClick() + } + is QSTileUserAction.LongClick -> { + handleLongClick(action.expandable) + } + } + } + } + + suspend fun handleClick() { + val dnd = zenModeInteractor.dndMode.value + if (dnd == null) { + Log.wtf(TAG, "No DND!?") + return + } + + if (!dnd.isActive) { + if (zenModeInteractor.shouldAskForZenDuration(dnd)) { + dialogEventLogger.logOpenDurationDialog(dnd) + withContext(mainContext) { + // NOTE: The dialog handles turning on the mode itself. + val dialog = dialogDelegate.makeDndDurationDialog() + dialog.show() + } + } else { + dialogEventLogger.logModeOn(dnd) + zenModeInteractor.activateMode(dnd) + } + } else { + dialogEventLogger.logModeOff(dnd) + zenModeInteractor.deactivateMode(dnd) + } + } + + private fun handleLongClick(expandable: Expandable?) { + val intent = getSettingsIntent() + if (intent != null) { + qsTileIntentUserInputHandler.handle(expandable, intent) + } + } + + fun getSettingsIntent(): Intent? { + val dnd = zenModeInteractor.dndMode.value + if (dnd == null) { + Log.wtf(TAG, "No DND!?") + return null + } + + return Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS) + .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, dnd.id) + .setPackage(settingsPackageRepository.getSettingsPackageName()) + } + + companion object { + const val TAG = "ModesDndTileUserActionInteractor" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesDndTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesDndTileModel.kt new file mode 100644 index 000000000000..eab798897aa3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesDndTileModel.kt @@ -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 com.android.systemui.qs.tiles.impl.modes.domain.model + +data class ModesDndTileModel(val isActivated: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapper.kt new file mode 100644 index 000000000000..4869b6f74554 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapper.kt @@ -0,0 +1,61 @@ +/* + * 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.qs.tiles.impl.modes.ui + +import android.content.res.Resources +import android.widget.Switch +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware +import javax.inject.Inject + +class ModesDndTileMapper +@Inject +constructor(@ShadeDisplayAware private val resources: Resources, val theme: Resources.Theme) : + QSTileDataToStateMapper<ModesDndTileModel> { + override fun map(config: QSTileConfig, data: ModesDndTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + val iconResource = + if (data.isActivated) R.drawable.qs_dnd_icon_on else R.drawable.qs_dnd_icon_off + icon = + Icon.Loaded( + resources.getDrawable(iconResource, theme), + res = iconResource, + contentDescription = null, + ) + + activationState = + if (data.isActivated) { + QSTileState.ActivationState.ACTIVE + } else { + QSTileState.ActivationState.INACTIVE + } + label = resources.getString(R.string.quick_settings_dnd_label) + secondaryLabel = + resources.getString( + if (data.isActivated) R.string.zen_mode_on else R.string.zen_mode_off + ) + contentDescription = label + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + expandedAccessibilityClass = Switch::class + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt index 7117629622e6..a8e9c5663f39 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt @@ -22,7 +22,7 @@ import android.hardware.SensorPrivacyManager.Sensors.Sensor import android.os.UserHandle import android.provider.DeviceConfig import android.util.Log -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor 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/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 7bb831baec20..3ad0867192d3 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -33,7 +33,7 @@ import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor import com.android.systemui.bouncer.shared.logging.BouncerUiEvent import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorActual -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.DisplayId diff --git a/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt b/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt index 7e967f436ecb..0b039fecc19e 100644 --- a/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt @@ -17,7 +17,7 @@ package com.android.systemui.security.data.repository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.security.data.model.SecurityModel diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt index 91c92cc8cd2f..ce74cb7d54d2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt @@ -20,7 +20,7 @@ import android.content.IntentFilter import android.os.UserHandle import android.safetycenter.SafetyCenterManager import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background 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/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 657c86b10f16..e66b21866902 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -1088,14 +1088,18 @@ public class KeyguardIndicationController { if (!TextUtils.equals(mTopIndicationView.getText(), newIndication)) { mWakeLock.setAcquired(true); + final KeyguardIndication.Builder builder = new KeyguardIndication.Builder() + .setMessage(newIndication) + .setTextColor(ColorStateList.valueOf( + useMisalignmentColor + ? mContext.getColor(R.color.misalignment_text_color) + : Color.WHITE)); + if (mBiometricMessage != null && newIndication == mBiometricMessage) { + builder.setForceAccessibilityLiveRegionAssertive(); + } + mTopIndicationView.switchIndication(newIndication, - new KeyguardIndication.Builder() - .setMessage(newIndication) - .setTextColor(ColorStateList.valueOf( - useMisalignmentColor - ? mContext.getColor(R.color.misalignment_text_color) - : Color.WHITE)) - .build(), + builder.build(), true, () -> mWakeLock.setAcquired(false)); } return; 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/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/StatusBarStateControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt index 6148b407d3bf..8fc95092be10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.plugins.statusbar.StatusBarStateController import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow 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/OngoingActivityChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt index 7080c3402b08..407849b9fae0 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 @@ -36,18 +36,18 @@ 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 { + 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 +56,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..d37a46e58882 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,14 @@ 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.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. */ @@ -105,6 +107,19 @@ 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 isHidden: Boolean = false, 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/disableflags/data/repository/DisableFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt index aeeb0427d24b..e91e9777d48e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt @@ -14,7 +14,7 @@ package com.android.systemui.statusbar.disableflags.data.repository -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.DisplayId 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/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 427301ca2267..765d444a5c95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -29,8 +29,6 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; -import static com.android.systemui.statusbar.notification.collection.BundleEntry.ROOT_BUNDLES; -import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY; import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED; import static java.util.Objects.requireNonNull; @@ -41,7 +39,6 @@ import android.app.Notification; import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationManager.Policy; -import android.app.PendingIntent; import android.app.Person; import android.app.RemoteInput; import android.app.RemoteInputHistoryItem; @@ -68,7 +65,6 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.headsup.PinnedStatus; import com.android.systemui.statusbar.notification.icon.IconPack; import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel; @@ -81,14 +77,14 @@ import com.android.systemui.statusbar.notification.row.shared.NotificationRowCon import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.util.ListenerSet; -import kotlinx.coroutines.flow.MutableStateFlow; -import kotlinx.coroutines.flow.StateFlow; -import kotlinx.coroutines.flow.StateFlowKt; - import java.util.ArrayList; import java.util.List; import java.util.Objects; +import kotlinx.coroutines.flow.MutableStateFlow; +import kotlinx.coroutines.flow.StateFlow; +import kotlinx.coroutines.flow.StateFlowKt; + /** * Represents a notification that the system UI knows about * @@ -497,7 +493,8 @@ public final class NotificationEntry extends ListEntry { public @Nullable List<NotificationEntry> getAttachedNotifChildren() { if (NotificationBundleUi.isEnabled()) { if (isGroupSummary()) { - return ((GroupEntry) getParent()).getChildren(); + GroupEntry parent = (GroupEntry) getParent(); + return parent != null ? new ArrayList<>(parent.getChildren()) : null; } } else { if (row == null) { 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/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 69a63c281489..3110db65ca3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -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)); } } 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/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index 1f32b945ce7e..cda535de86c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator; +import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS; + import android.annotation.NonNull; import android.annotation.Nullable; @@ -99,7 +101,11 @@ public class RankingCoordinator implements Coordinator { NotificationPriorityBucketKt.BUCKET_ALERTING) { @Override public boolean isInSection(PipelineEntry entry) { - return mHighPriorityProvider.isHighPriority(entry); + return mHighPriorityProvider.isHighPriority(entry) + && entry.getRepresentativeEntry() != null + && entry.getRepresentativeEntry().getChannel() != null + && !SYSTEM_RESERVED_IDS.contains( + entry.getRepresentativeEntry().getChannel().getId()); } @Nullable 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 0478e718033d..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) { @@ -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/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/headsup/HeadsUpManagerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt index 6525b6f1186b..376735025abd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.headsup -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.statusbar.notification.collection.NotificationEntry import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow 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..76830646587d 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; @@ -979,7 +980,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } else if (isAboveShelf() != wasAboveShelf) { mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); } - updateColors(); + updateBackgroundTint(); } /** @@ -1678,20 +1679,34 @@ 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 (notificationRowTransparency()) { + boolean isColorized = false; if (NotificationBundleUi.isEnabled() && mEntryAdapter != null) { - mBackgroundNormal.setBgIsColorized(mEntryAdapter.isColorized()); + 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); } } @@ -3113,7 +3128,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.setOnKeyguard(onKeyguard); } } - updateColors(); + updateBackgroundTint(); } } @@ -3243,12 +3258,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) { @@ -4633,6 +4655,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @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/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/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index cd6e4979ce88..e9993ae31514 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); @@ -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/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/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 1e249520e8b3..abfb86244390 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -36,13 +36,14 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository; +import com.android.systemui.statusbar.notification.headsup.AvalancheController; +import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.BypassController; import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.notification.headsup.AvalancheController; import java.io.PrintWriter; @@ -424,6 +425,7 @@ public class AmbientState implements Dumpable { /** the bottom-most y position where we can draw pinned HUNs */ public float getHeadsUpBottom() { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f; + NotificationsHunSharedAnimationValues.assertInLegacyMode(); return mHeadsUpBottom; } 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..a5f711050c46 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; @@ -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. 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..bb3abc1fba38 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 @@ -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/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index d23a4c6307fc..28218227506c 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 @@ -967,9 +967,12 @@ public class StackScrollAlgorithm { childState.setZTranslation(baseZ); } if (isTopEntry && row.isAboveShelf()) { + float headsUpBottom = NotificationsHunSharedAnimationValues.isEnabled() + ? mHeadsUpAnimator.getHeadsUpAppearHeightBottom() + : ambientState.getHeadsUpBottom(); clampHunToMaxTranslation( /* headsUpTop = */ headsUpTranslation, - /* headsUpBottom = */ ambientState.getHeadsUpBottom(), + /* headsUpBottom = */ headsUpBottom, /* viewState = */ childState ); updateCornerRoundnessForPinnedHun(row, ambientState.getStackTop()); 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/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/SystemUIDialogManagerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt index fbc6b9524a6d..372e91f88ae5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.phone import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt index f82e681de76f..e2fd4bdc45a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.pipeline.airplane.data.repository import android.net.ConnectivityManager import android.os.Handler import android.provider.Settings.Global -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.table.TableLogBuffer diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/UnifiedBattery.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/UnifiedBattery.kt index 732ea6ac6790..5127e8a14796 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/UnifiedBattery.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/UnifiedBattery.kt @@ -101,7 +101,13 @@ fun BatteryCanvas( for (glyph in glyphs) { // Move the glyph to the right spot val verticalOffset = (BatteryFrame.innerHeight - glyph.height) / 2 - inset(horizontalOffset, verticalOffset) { glyph.draw(this, colors) } + inset( + // Never try and inset more than half of the available size - see b/400246091. + minOf(horizontalOffset, size.width / 2), + minOf(verticalOffset, size.height / 2), + ) { + glyph.draw(this, colors) + } horizontalOffset += glyph.width + INTER_GLYPH_PADDING_PX } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt index 1f5b849c56cc..f4076ae34a4f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt @@ -24,7 +24,7 @@ import com.android.systemui.Flags import com.android.systemui.KairosActivatable import com.android.systemui.KairosBuilder import com.android.systemui.activated -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt index 982f6ec36150..2a6ff3b98ae2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt @@ -28,7 +28,7 @@ import android.telephony.satellite.SatelliteModemStateCallback import android.telephony.satellite.SatelliteProvisionStateCallback import androidx.annotation.VisibleForTesting import com.android.app.tracing.coroutines.launchTraced as launch -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main 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/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index f9bba9d624f1..eaceb5e22535 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -26,7 +26,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Flags.multiuserWifiPickerTrackerSupport -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt index 0a2bbe580b99..14cadd90db4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt @@ -14,7 +14,7 @@ package com.android.systemui.statusbar.policy import android.content.res.Configuration -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow 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/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt index 3cb7090ea6d4..a352982f58f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt @@ -32,6 +32,7 @@ import com.android.systemui.qs.tiles.DndTile import com.android.systemui.qs.tiles.FlashlightTile import com.android.systemui.qs.tiles.LocationTile import com.android.systemui.qs.tiles.MicrophoneToggleTile +import com.android.systemui.qs.tiles.ModesDndTile import com.android.systemui.qs.tiles.ModesTile import com.android.systemui.qs.tiles.UiModeNightTile import com.android.systemui.qs.tiles.WorkModeTile @@ -49,9 +50,13 @@ import com.android.systemui.qs.tiles.impl.location.domain.LocationTileMapper import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileDataInteractor +import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileUserActionInteractor import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileDataInteractor import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel +import com.android.systemui.qs.tiles.impl.modes.ui.ModesDndTileMapper import com.android.systemui.qs.tiles.impl.modes.ui.ModesTileMapper import com.android.systemui.qs.tiles.impl.sensorprivacy.SensorPrivacyToggleTileDataInteractor import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.SensorPrivacyToggleTileUserActionInteractor @@ -132,6 +137,7 @@ interface PolicyModule { const val CAMERA_TOGGLE_TILE_SPEC = "cameratoggle" const val MIC_TOGGLE_TILE_SPEC = "mictoggle" const val DND_TILE_SPEC = "dnd" + const val MODES_DND_TILE_SPEC = "modes_dnd" /** Inject DndTile or ModesTile into tileMap in QSModule based on feature flag */ @Provides @@ -146,6 +152,12 @@ interface PolicyModule { return if (ModesUi.isEnabled) modesTile.get() else dndTile.get() } + /** Inject ModesDndTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(MODES_DND_TILE_SPEC) + fun bindDndModeTile(tile: ModesDndTile): QSTileImpl<*> = tile + /** Inject flashlight config */ @Provides @IntoMap @@ -449,6 +461,37 @@ interface PolicyModule { mapper, ) else StubQSTileViewModel + + @Provides + @IntoMap + @StringKey(MODES_DND_TILE_SPEC) + fun provideDndModeTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(MODES_DND_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_dnd_icon_off, + labelRes = R.string.quick_settings_dnd_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.CONNECTIVITY, + ) + + @Provides + @IntoMap + @StringKey(MODES_DND_TILE_SPEC) + fun provideDndModeTileViewModel( + factory: QSTileViewModelFactory.Static<ModesDndTileModel>, + mapper: ModesDndTileMapper, + stateInteractor: ModesDndTileDataInteractor, + userActionInteractor: ModesDndTileUserActionInteractor, + ): QSTileViewModel = + factory.create( + TileSpec.create(MODES_DND_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) } /** Inject FlashlightTile into tileMap in QSModule */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt index 07bbca74e12e..2b9d39a54c44 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt @@ -15,7 +15,7 @@ */ package com.android.systemui.statusbar.policy.data.repository -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.statusbar.policy.DeviceProvisionedController import dagger.Binds import dagger.Module diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt index e8347df5653f..ed814c6b3785 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt @@ -58,7 +58,7 @@ import kotlinx.coroutines.flow.stateIn * An interactor that performs business logic related to the status and configuration of Zen Mode * (or Do Not Disturb/DND Mode). */ - @SysUISingleton +@SysUISingleton class ZenModeInteractor @Inject constructor( @@ -141,6 +141,18 @@ constructor( return field } + /** + * Returns the current state of the special "manual DND" mode. + * + * This should only be used when there is a strong reason to handle DND specifically (such as + * legacy UI pieces that haven't been updated to use modes more generally, or if the user + * explicitly wants a shortcut to DND). Please prefer using [modes] or [activeModes] in all + * other scenarios. + */ + fun getDndMode(): ZenMode { + return zenModeRepository.getModes().single { it.isManualDnd } + } + /** Flow returning the currently active mode(s), if any. */ val activeModes: Flow<ActiveZenModes> = modes diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt index 2dc17f40a380..c86e00de4246 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.ui.viewmodel -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor diff --git a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt index b1b6014bfbde..9b88d439f2db 100644 --- a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt @@ -23,7 +23,7 @@ import android.content.pm.PackageManager import android.telecom.TelecomManager import android.telephony.Annotation import android.telephony.TelephonyCallback -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 9f60fe212567..48d7747d2dc2 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,16 +971,17 @@ 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 diff --git a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt index fbbd2b9c5de8..e47d74ec9412 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt @@ -16,7 +16,7 @@ package com.android.systemui.unfold.data.repository import androidx.annotation.FloatRange -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index c960b5525d96..05b2e0d1423e 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -33,7 +33,7 @@ import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.statusbar.IStatusBarService import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt index bcbd679b35eb..412161cf98bc 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt @@ -23,7 +23,7 @@ import android.os.UserManager import android.provider.Settings.Global.USER_SWITCHER_ENABLED import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt index 31a8d864de95..9937eeb29151 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt @@ -19,7 +19,7 @@ import android.content.ContentResolver import android.database.ContentObserver import android.os.Handler import android.provider.Settings -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt index 80ccd646f6be..d4eabb9264e6 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt @@ -16,7 +16,7 @@ package com.android.systemui.util.kotlin -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.statusbar.policy.BatteryController import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/ManagedProfileControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/ManagedProfileControllerExt.kt index 7a2f9b24700f..837bbea9cc3c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/ManagedProfileControllerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/ManagedProfileControllerExt.kt @@ -16,7 +16,7 @@ package com.android.systemui.util.kotlin -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.statusbar.phone.ManagedProfileController import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt index ee00e8b04ef1..02012ede697b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt @@ -16,7 +16,7 @@ package com.android.systemui.util.kotlin -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.qs.ReduceBrightColorsController import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt index 22cc8dd7745d..a914c86da0e7 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt @@ -16,7 +16,7 @@ package com.android.systemui.util.kotlin -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.statusbar.policy.RotationLockController import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt index 594c5526dca9..7e9ebd218787 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt @@ -24,7 +24,7 @@ import android.service.quickaccesswallet.QuickAccessWalletClient import android.service.quickaccesswallet.WalletCard import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags 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/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..00ee893e0e4d 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; @@ -942,9 +949,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { 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); + setRowPromotedOngoing(row); row.setSensitive(/* sensitive= */true, /* hideSensitive= */false); row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */true); @@ -957,9 +962,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { 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); + setRowPromotedOngoing(row); row.setOnKeyguard(false); // THEN @@ -971,9 +974,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { 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); + setRowPromotedOngoing(row); row.setOnKeyguard(true); // THEN @@ -986,9 +987,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); - NotificationEntry entry = mock(NotificationEntry.class); - when(entry.isPromotedOngoing()).thenReturn(true); - row.setEntry(entry); + setRowPromotedOngoing(row); row.setOnKeyguard(true); row.setIgnoreLockscreenConstraints(true); @@ -996,15 +995,31 @@ 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.setEntry(entry); + } + } + @Test @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.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); + setRowPromotedOngoing(row); row.setOnKeyguard(true); row.setSaveSpaceOnLockscreen(true); @@ -1018,9 +1033,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); - NotificationEntry entry = mock(NotificationEntry.class); - when(entry.isPromotedOngoing()).thenReturn(true); - row.setEntry(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..5d7b3edc457b 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 @@ -88,14 +89,11 @@ class NotificationContentViewTest : SysuiTestCase() { spy( when (NotificationBundleUi.isEnabled) { true -> { - ExpandableNotificationRow( - mContext, - /* attrs= */ null, - UserHandle.CURRENT - ).apply { - entry = mockEntry - entryAdapter = mockEntryAdapter - } + ExpandableNotificationRow(mContext, /* attrs= */ null, UserHandle.CURRENT) + .apply { + entry = mockEntry + entryAdapter = mockEntryAdapter + } } false -> { ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply { @@ -402,6 +400,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 +428,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 +458,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 +487,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 +516,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 +630,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/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/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 a71224a68125..84158cf911ad 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java @@ -21,8 +21,10 @@ import static android.view.MotionEvent.BUTTON_SECONDARY; import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT; import static android.view.accessibility.AccessibilityManager.AUTOCLICK_DELAY_DEFAULT; import static android.view.accessibility.AccessibilityManager.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT; +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; @@ -44,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; @@ -159,7 +162,8 @@ public class AutoclickController extends BaseEventStreamTransformation { initiateAutoclickIndicator(handler); } - mClickScheduler = new ClickScheduler(handler, AUTOCLICK_DELAY_DEFAULT); + mClickScheduler = new ClickScheduler( + handler, AUTOCLICK_DELAY_DEFAULT); mAutoclickSettingsObserver = new AutoclickSettingsObserver(mUserId, handler); mAutoclickSettingsObserver.start( mContext.getContentResolver(), @@ -304,6 +308,10 @@ public class AutoclickController extends BaseEventStreamTransformation { Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT); + private final Uri mAutoclickRevertToLeftClickSettingUri = + Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK); + private ContentResolver mContentResolver; private ClickScheduler mClickScheduler; private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler; @@ -368,6 +376,13 @@ public class AutoclickController extends BaseEventStreamTransformation { /* observer= */ this, mUserId); onChange(/* selfChange= */ true, mAutoclickIgnoreMinorCursorMovementSettingUri); + + mContentResolver.registerContentObserver( + mAutoclickRevertToLeftClickSettingUri, + /* notifyForDescendants= */ false, + /* observer= */ this, + mUserId); + onChange(/* selfChange= */ true, mAutoclickRevertToLeftClickSettingUri); } } @@ -424,6 +439,20 @@ public class AutoclickController extends BaseEventStreamTransformation { == AccessibilityUtils.State.ON; mClickScheduler.setIgnoreMinorCursorMovement(ignoreMinorCursorMovement); } + + if (mAutoclickRevertToLeftClickSettingUri.equals(uri)) { + boolean revertToLeftClick = + Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure + .ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK, + AUTOCLICK_REVERT_TO_LEFT_CLICK_DEFAULT + ? AccessibilityUtils.State.ON + : AccessibilityUtils.State.OFF, + mUserId) + == AccessibilityUtils.State.ON; + mClickScheduler.setRevertToLeftClick(revertToLeftClick); + } } } } @@ -505,6 +534,9 @@ public class AutoclickController extends BaseEventStreamTransformation { /** Whether the minor cursor movement should be ignored. */ private boolean mIgnoreMinorCursorMovement = AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT; + /** Whether the autoclick type reverts to left click once performing an action. */ + private boolean mRevertToLeftClick = AUTOCLICK_REVERT_TO_LEFT_CLICK_DEFAULT; + /** Whether there is pending click. */ private boolean mActive; /** If active, time at which pending click is scheduled. */ @@ -555,6 +587,7 @@ public class AutoclickController extends BaseEventStreamTransformation { sendClick(); resetInternalState(); + resetSelectedClickTypeIfNecessary(); } /** @@ -633,6 +666,11 @@ public class AutoclickController extends BaseEventStreamTransformation { return mDelay; } + @VisibleForTesting + boolean getRevertToLeftClickForTesting() { + return mRevertToLeftClick; + } + /** * Updates the time at which click sequence should occur. * @@ -692,6 +730,12 @@ public class AutoclickController extends BaseEventStreamTransformation { } } + private void resetSelectedClickTypeIfNecessary() { + if (mRevertToLeftClick && mActiveClickType != AUTOCLICK_TYPE_LEFT_CLICK) { + mAutoclickTypePanel.resetSelectedClickType(); + } + } + /** * @param event Observed motion event. * @return Whether the event coords are far enough from the anchor for the event not to be @@ -716,6 +760,10 @@ public class AutoclickController extends BaseEventStreamTransformation { mIgnoreMinorCursorMovement = ignoreMinorCursorMovement; } + public void setRevertToLeftClick(boolean revertToLeftClick) { + mRevertToLeftClick = revertToLeftClick; + } + private void updateMovementSlop(double slop) { mMovementSlop = slop; } @@ -753,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 @@ -761,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/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java index 57fa77d73729..5a484d42eb96 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java @@ -283,7 +283,11 @@ public class AutoclickTypePanel { // The pause button calls `togglePause()` directly so it does not need extra logic. mPauseButton.setOnClickListener(v -> togglePause()); - // Initializes panel as collapsed state and only displays the left click button. + resetSelectedClickType(); + } + + /** Reset panel as collapsed state and only displays the left click button. */ + public void resetSelectedClickType() { hideAllClickTypeButtons(); mLeftClickButton.setVisibility(View.VISIBLE); setSelectedClickType(AUTOCLICK_TYPE_LEFT_CLICK); 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/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..72de8d5f314f 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -1059,7 +1059,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 +7011,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/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java index 0e1fbf3a6d1a..f50f45a182d0 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java @@ -118,7 +118,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { @Override void shutdown() { mSqliteWriteHandler.removeAllPendingMessages(); - mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear()); + mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.evictAllAppOpEvents()); } @Override @@ -172,10 +172,14 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, int opFlagsFilter, Set<String> attributionExemptPkgs) { + IntArray opCodes = getAppOpCodes(filter, opNamesFilter); // flush the cache into database before read. - mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear()); + if (opCodes != null) { + mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.evictAppOpEvents(opCodes)); + } else { + mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.evictAllAppOpEvents()); + } boolean assembleChains = attributionExemptPkgs != null; - IntArray opCodes = getAppOpCodes(filter, opNamesFilter); beginTimeMillis = Math.max(beginTimeMillis, Instant.now().minus(sDiscreteHistoryCutoff, ChronoUnit.MILLIS).toEpochMilli()); List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter, @@ -214,7 +218,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps) { // flush the cache into database before dump. - mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear()); + mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.evictAllAppOpEvents()); IntArray opCodes = new IntArray(); if (dumpOp != AppOpsManager.OP_NONE) { opCodes.add(dumpOp); @@ -366,7 +370,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { try { List<DiscreteOp> evictedEvents; synchronized (mDiscreteOpCache) { - evictedEvents = mDiscreteOpCache.evict(); + evictedEvents = mDiscreteOpCache.evictOldAppOpEvents(); } mDiscreteOpsDbHelper.insertDiscreteOps(evictedEvents); } finally { @@ -389,7 +393,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { try { List<DiscreteOp> evictedEvents; synchronized (mDiscreteOpCache) { - evictedEvents = mDiscreteOpCache.evict(); + evictedEvents = mDiscreteOpCache.evictOldAppOpEvents(); // if nothing to evict, just write the whole cache to database. if (evictedEvents.isEmpty() && mDiscreteOpCache.size() >= mDiscreteOpCache.capacity()) { @@ -451,9 +455,10 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { } /** - * Evict entries older than {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization}. + * Evict entries older than {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization} i.e. + * app op events older than one minute (default quantization) will be evicted. */ - private List<DiscreteOp> evict() { + private List<DiscreteOp> evictOldAppOpEvents() { synchronized (this) { List<DiscreteOp> evictedEvents = new ArrayList<>(); Set<DiscreteOp> snapshot = new ArraySet<>(mCache); @@ -470,11 +475,9 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { } /** - * Remove all the entries from cache. - * - * @return return all removed entries. + * Evict all app op entries from cache, and return the list of removed ops. */ - public List<DiscreteOp> getAllEventsAndClear() { + public List<DiscreteOp> evictAllAppOpEvents() { synchronized (this) { List<DiscreteOp> cachedOps = new ArrayList<>(mCache.size()); if (mCache.isEmpty()) { @@ -486,6 +489,25 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { } } + /** + * Evict specified app ops from cache, and return the list of evicted ops. + */ + public List<DiscreteOp> evictAppOpEvents(IntArray ops) { + synchronized (this) { + List<DiscreteOp> evictedOps = new ArrayList<>(); + if (mCache.isEmpty()) { + return evictedOps; + } + for (DiscreteOp discreteOp: mCache) { + if (ops.contains(discreteOp.getOpCode())) { + evictedOps.add(discreteOp); + } + } + evictedOps.forEach(mCache::remove); + return evictedOps; + } + } + int size() { return mCache.size(); } @@ -646,7 +668,10 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { + ", uidState=" + getUidStateName(mUidState) + ", chainId=" + mChainId + ", accessTime=" + mAccessTime - + ", duration=" + mDuration + '}'; + + ", mDiscretizedAccessTime=" + mDiscretizedAccessTime + + ", duration=" + mDuration + + ", mDiscretizedDuration=" + mDiscretizedDuration + + '}'; } public int getUid() { 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..d917bffa06e9 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; } @@ -5100,7 +5100,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 +15109,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 +15179,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 +15202,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/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/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/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index cf598e89c988..62264dd73795 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -96,6 +96,7 @@ import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.os.ShellCommand; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; @@ -1564,6 +1565,12 @@ class PackageManagerShellCommand extends ShellCommand { private int doRunInstall(final InstallParams params) throws RemoteException { final PrintWriter pw = getOutPrintWriter(); + // Do not allow app installation if boot has not completed already + if (!SystemProperties.getBoolean("sys.boot_completed", false)) { + pw.println("Error: device is still booting."); + return 1; + } + int requestUserId = params.userId; if (requestUserId != UserHandle.USER_ALL && requestUserId != UserHandle.USER_CURRENT) { UserManagerInternal umi = @@ -2174,6 +2181,13 @@ class PackageManagerShellCommand extends ShellCommand { private int runUninstall() throws RemoteException { final PrintWriter pw = getOutPrintWriter(); + + // Do not allow app uninstallation if boot has not completed already + if (!SystemProperties.getBoolean("sys.boot_completed", false)) { + pw.println("Error: device is still booting."); + return 1; + } + int flags = 0; int userId = UserHandle.USER_ALL; long versionCode = PackageManager.VERSION_CODE_HIGHEST; 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/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/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/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/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..ff43d72c5a07 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(); @@ -8950,6 +8970,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/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/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 2a513ae3a8e8..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; @@ -953,11 +953,13 @@ public final class AlarmManagerServiceTest { @Test @EnableFlags(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND) - public void testWakelockOrdering() throws Exception { + public void testWakelockOrderingFirstAlarm() throws Exception { final long triggerTime = mNowElapsedTest + 5000; final PendingIntent alarmPi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi); + // Pretend that it is the first alarm in this batch, or no other alarms are still processing + mService.mBroadcastRefCount = 0; mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); @@ -975,20 +977,51 @@ public final class AlarmManagerServiceTest { @Test @EnableFlags(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND) - public void testWakelockReleasedWhenSendFails() throws Exception { + public void testWakelockOrderingNonFirst() throws Exception { final long triggerTime = mNowElapsedTest + 5000; final PendingIntent alarmPi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi); + // Pretend that some previous alarms are still processing. + mService.mBroadcastRefCount = 3; + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + + final ArgumentCaptor<PendingIntent.OnFinished> onFinishedCaptor = + ArgumentCaptor.forClass(PendingIntent.OnFinished.class); + verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), onFinishedCaptor.capture(), + any(Handler.class), isNull(), any()); + onFinishedCaptor.getValue().onSendFinished(alarmPi, null, 0, null, null); + + verify(mWakeLock, never()).acquire(); + verify(mWakeLock, never()).release(); + } + + @Test + @EnableFlags(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND) + public void testWakelockReleasedWhenSendFails() throws Exception { + final PendingIntent alarmPi = getNewMockPendingIntent(); doThrow(new PendingIntent.CanceledException("test")).when(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), any(), any(Handler.class), isNull(), any()); + setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5000, alarmPi); + + // Pretend that it is the first alarm in this batch, or no other alarms are still processing + mService.mBroadcastRefCount = 0; mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); final InOrder inOrder = Mockito.inOrder(mWakeLock); inOrder.verify(mWakeLock).acquire(); inOrder.verify(mWakeLock).release(); + + setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5000, alarmPi); + + // Pretend that some previous alarms are still processing. + mService.mBroadcastRefCount = 4; + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + inOrder.verifyNoMoreInteractions(); } @Test @@ -3691,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/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 9cfa51a85988..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( @@ -2374,14 +2453,6 @@ public class AccessibilityManagerServiceTest { return lockState; } - private void assertStartActivityWithExpectedComponentName(Context mockContext, - String componentName) { - verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(), - any(Bundle.class), any(UserHandle.class)); - assertThat(mIntentArgumentCaptor.getValue().getStringExtra( - Intent.EXTRA_COMPONENT_NAME)).isEqualTo(componentName); - } - private void assertStartActivityWithExpectedShortcutType(Context mockContext, @UserShortcutType int shortcutType) { verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(), @@ -2430,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) { @@ -2484,10 +2576,6 @@ public class AccessibilityManagerServiceTest { return mMockContext; } - public void addMockUserContext(int userId, Context context) { - mMockUserContexts.put(userId, context); - } - @Override @NonNull public Context createContextAsUser(UserHandle user, int flags) { @@ -2518,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 ea25e7992dd9..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 @@ -18,12 +18,14 @@ package com.android.server.accessibility.autoclick; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK; import static com.android.server.testutils.MockitoUtilsKt.eq; 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; @@ -73,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; } } @@ -547,6 +550,29 @@ public class AutoclickControllerTest { @Test @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void triggerRightClickWithRevertToLeftClickEnabled_resetClickType() { + // Move mouse to initialize autoclick panel. + injectFakeMouseActionHoverMoveEvent(); + + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; + mController.clickPanelController.handleAutoclickTypeChange(AUTOCLICK_TYPE_RIGHT_CLICK); + + // Set ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK to true. + Settings.Secure.putIntForUser(mTestableContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK, + AccessibilityUtils.State.ON, + mTestableContext.getUserId()); + mController.onChangeForTesting(/* selfChange= */ true, + Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK)); + when(mockAutoclickTypePanel.isPaused()).thenReturn(false); + mController.mClickScheduler.run(); + assertThat(mController.mClickScheduler.getRevertToLeftClickForTesting()).isTrue(); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) public void pauseButton_flagOn_clickNotTriggeredWhenPaused() { injectFakeMouseActionHoverMoveEvent(); @@ -766,6 +792,8 @@ public class AutoclickControllerTest { // Set click type to right click. mController.clickPanelController.handleAutoclickTypeChange( AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK); + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; // Send hover move event. MotionEvent hoverMove = MotionEvent.obtain( @@ -787,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); @@ -844,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/appop/DiscreteAppOpSqlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java index 01fee7f66497..918159f9262b 100644 --- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java @@ -97,7 +97,8 @@ public class DiscreteAppOpSqlPersistenceTest { mDiscreteRegistry.recordDiscreteAccess(opEvent2); List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps(); - assertThat(discreteOps.size()).isEqualTo(1); + assertWithMessage("Expected list size is 1, but the list is: " + discreteOps) + .that(discreteOps.size()).isEqualTo(1); assertThat(discreteOps).contains(opEvent); } 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/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/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/Input/assets/testPointerScale.png b/tests/Input/assets/testPointerScale.png Binary files differindex 54d37c24afc6..781df47a5e24 100644 --- a/tests/Input/assets/testPointerScale.png +++ b/tests/Input/assets/testPointerScale.png 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/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index 649241aaaa8c..6b099450064f 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -238,6 +238,36 @@ public class TestableLooper { while (processQueuedMessages() != 0) ; } + public long peekWhen() { + if (isAtLeastBaklava()) { + return peekWhenBaklava(); + } else { + return peekWhenLegacy(); + } + } + + private long peekWhenBaklava() { + Long when = mQueueWrapper.peekWhen(); + if (when != null) { + return when; + } else { + return 0; + } + } + + private long peekWhenLegacy() { + try { + Message msg = (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(mLooper.getQueue()); + if (msg != null) { + return msg.getWhen(); + } else { + return 0; + } + } catch (IllegalAccessException e) { + throw new RuntimeException("Access failed in TestableLooper: set - Message.when", e); + } + } + public void moveTimeForward(long milliSeconds) { if (isAtLeastBaklava()) { moveTimeForwardBaklava(milliSeconds); 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; |