diff options
385 files changed, 10277 insertions, 3722 deletions
diff --git a/TEST_MAPPING b/TEST_MAPPING index e469f167d32f..ce0da7e21071 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -25,6 +25,12 @@ "name": "FrameworksUiServicesTests" }, { + "name": "FrameworksUiServicesNotificationTests" + }, + { + "name": "FrameworksUiServicesZenTests" + }, + { "name": "FrameworksInputMethodSystemServerTests_server_inputmethod" }, { diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index 876274ecc32e..aae5bb31273b 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -126,3 +126,15 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "tune_quota_window_default_parameters" + namespace: "backstage_power" + description: "Tune default active/exempted bucket quota parameters" + bug: "401767691" + metadata { + purpose: PURPOSE_BUGFIX + } +} + + diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 54d337eded7d..a9c4a1501dd8 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -360,13 +360,13 @@ public final class QuotaController extends StateController { /** How much time each app will have to run jobs within their standby bucket window. */ private final long[] mAllowedTimePerPeriodMs = new long[]{ - QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + QcConstants.DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS, QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS, 0, // NEVER QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, - QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS + QcConstants.DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS }; /** @@ -3178,9 +3178,11 @@ public final class QuotaController extends StateController { static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS = QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms"; - private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + // Legacy default time each app will have to run jobs within EXEMPTED bucket + private static final long DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = 10 * 60 * 1000L; // 10 minutes - private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + // Legacy default time each app will have to run jobs within ACTIVE bucket + private static final long DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS = 10 * 60 * 1000L; // 10 minutes @@ -3192,14 +3194,26 @@ public final class QuotaController extends StateController { 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = 10 * 60 * 1000L; // 10 minutes + + // Current default time each app will have to run jobs within EXEMPTED bucket + private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + 20 * 60 * 1000L; // 20 minutes + // Current default time each app will have to run jobs within ACTIVE bucket + private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + 20 * 60 * 1000L; // 20 minutes + private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = + 20 * 60 * 1000L; // 20 minutes + private static final long DEFAULT_IN_QUOTA_BUFFER_MS = 30 * 1000L; // 30 seconds // Legacy default window size for EXEMPTED bucket + // EXEMPT apps can run jobs at any time private static final long DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS = - DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // Legacy default window size for ACTIVE bucket + // ACTIVE apps can run jobs at any time private static final long DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS = - DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // ACTIVE apps can run jobs at any time + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // Legacy default window size for WORKING bucket private static final long DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS = 2 * 60 * 60 * 1000L; // 2 hours @@ -3216,6 +3230,13 @@ public final class QuotaController extends StateController { private static final long DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS = 12 * 60 * 60 * 1000L; // 12 hours + // Latest default window size for EXEMPTED bucket. + private static final long DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS = + 40 * 60 * 1000L; // 40 minutes. + // Latest default window size for ACTIVE bucket. + private static final long DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS = + 60 * 60 * 1000L; // 60 minutes. + private static final long DEFAULT_WINDOW_SIZE_RARE_MS = 24 * 60 * 60 * 1000L; // 24 hours private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS = @@ -3276,12 +3297,13 @@ public final class QuotaController extends StateController { * bucket window. */ public long ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = - DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; /** * How much time each app in the active bucket will have to run jobs within their standby * bucket window. */ - public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; /** * How much time each app in the working set bucket will have to run jobs within their * standby bucket window. @@ -3575,11 +3597,30 @@ public final class QuotaController extends StateController { public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS; void adjustDefaultBucketWindowSizes() { - WINDOW_SIZE_EXEMPTED_MS = DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS; - WINDOW_SIZE_ACTIVE_MS = DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS; + ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS : + DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS; + + WINDOW_SIZE_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS : + DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS; + WINDOW_SIZE_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS : + DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS; WINDOW_SIZE_WORKING_MS = DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS; WINDOW_SIZE_FREQUENT_MS = DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS; + mAllowedTimePerPeriodMs[EXEMPTED_INDEX] = Math.min(MAX_PERIOD_MS, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS)); + mAllowedTimePerPeriodMs[ACTIVE_INDEX] = Math.min(MAX_PERIOD_MS, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS)); + mBucketPeriodsMs[EXEMPTED_INDEX] = Math.max( mAllowedTimePerPeriodMs[EXEMPTED_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_EXEMPTED_MS)); @@ -3592,6 +3633,11 @@ public final class QuotaController extends StateController { mBucketPeriodsMs[FREQUENT_INDEX] = Math.max( mAllowedTimePerPeriodMs[FREQUENT_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS)); + + mAllowedTimePeriodAdditionaInstallerMs = + Math.min(mBucketPeriodsMs[EXEMPTED_INDEX] + - mAllowedTimePerPeriodMs[EXEMPTED_INDEX], + ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS); } void adjustDefaultEjLimits() { @@ -3882,10 +3928,14 @@ public final class QuotaController extends StateController { KEY_WINDOW_SIZE_RESTRICTED_MS); ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, - DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS); + Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS); ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, - DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS); + Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS); ALLOWED_TIME_PER_PERIOD_WORKING_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS); @@ -3900,19 +3950,27 @@ public final class QuotaController extends StateController { DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS); ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS, - DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS); + Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS + : DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS); IN_QUOTA_BUFFER_MS = properties.getLong(KEY_IN_QUOTA_BUFFER_MS, DEFAULT_IN_QUOTA_BUFFER_MS); MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS); WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS, - Flags.adjustQuotaDefaultConstants() - ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS : - DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS); + (Flags.adjustQuotaDefaultConstants() + && Flags.tuneQuotaWindowDefaultParameters()) + ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS : + (Flags.adjustQuotaDefaultConstants() + ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS : + DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS)); WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS, - Flags.adjustQuotaDefaultConstants() - ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS : - DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS); + (Flags.adjustQuotaDefaultConstants() + && Flags.tuneQuotaWindowDefaultParameters()) + ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS : + (Flags.adjustQuotaDefaultConstants() + ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS : + DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS)); WINDOW_SIZE_WORKING_MS = properties.getLong(KEY_WINDOW_SIZE_WORKING_MS, Flags.adjustQuotaDefaultConstants() diff --git a/core/api/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..cd7e434d30d9 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(); } 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/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/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/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/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/Parcel.java b/core/java/android/os/Parcel.java index 49d3f06eb80f..6cb49b3ea166 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -52,6 +52,8 @@ import com.android.internal.util.ArrayUtils; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; +import java.nio.BufferOverflowException; +import java.nio.ReadOnlyBufferException; import libcore.util.SneakyThrow; import java.io.ByteArrayInputStream; @@ -62,6 +64,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.Serializable; +import java.nio.ByteBuffer; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Array; @@ -457,8 +460,15 @@ public final class Parcel { private static native void nativeDestroy(long nativePtr); private static native byte[] nativeMarshall(long nativePtr); + private static native int nativeMarshallArray( + long nativePtr, byte[] data, int offset, int length); + private static native int nativeMarshallBuffer( + long nativePtr, ByteBuffer buffer, int offset, int length); private static native void nativeUnmarshall( long nativePtr, byte[] data, int offset, int length); + private static native void nativeUnmarshallBuffer( + long nativePtr, ByteBuffer buffer, int offset, int length); + private static native int nativeCompareData(long thisNativePtr, long otherNativePtr); private static native boolean nativeCompareDataInRange( long ptrA, int offsetA, long ptrB, int offsetB, int length); @@ -814,12 +824,80 @@ public final class Parcel { } /** + * Writes the raw bytes of the parcel to a buffer. + * + * <p class="note">The data you retrieve here <strong>must not</strong> + * be placed in any kind of persistent storage (on local disk, across + * a network, etc). For that, you should use standard serialization + * or another kind of general serialization mechanism. The Parcel + * marshalled representation is highly optimized for local IPC, and as + * such does not attempt to maintain compatibility with data created + * in different versions of the platform. + * + * @param buffer The ByteBuffer to write the data to. + * @throws ReadOnlyBufferException if the buffer is read-only. + * @throws BufferOverflowException if the buffer is too small. + * + * @hide + */ + public final void marshall(@NonNull ByteBuffer buffer) { + if (buffer == null) { + throw new NullPointerException(); + } + if (buffer.isReadOnly()) { + throw new ReadOnlyBufferException(); + } + + final int position = buffer.position(); + final int remaining = buffer.remaining(); + + int marshalledSize = 0; + if (buffer.isDirect()) { + marshalledSize = nativeMarshallBuffer(mNativePtr, buffer, position, remaining); + } else if (buffer.hasArray()) { + marshalledSize = nativeMarshallArray( + mNativePtr, buffer.array(), buffer.arrayOffset() + position, remaining); + } else { + throw new IllegalArgumentException(); + } + + buffer.position(position + marshalledSize); + } + + /** * Fills the raw bytes of this Parcel with the supplied data. */ public final void unmarshall(@NonNull byte[] data, int offset, int length) { nativeUnmarshall(mNativePtr, data, offset, length); } + /** + * Fills the raw bytes of this Parcel with data from the supplied buffer. + * + * @param buffer will read buffer.remaining() bytes from the buffer. + * + * @hide + */ + public final void unmarshall(@NonNull ByteBuffer buffer) { + if (buffer == null) { + throw new NullPointerException(); + } + + final int position = buffer.position(); + final int remaining = buffer.remaining(); + + if (buffer.isDirect()) { + nativeUnmarshallBuffer(mNativePtr, buffer, position, remaining); + } else if (buffer.hasArray()) { + nativeUnmarshall( + mNativePtr, buffer.array(), buffer.arrayOffset() + position, remaining); + } else { + throw new IllegalArgumentException(); + } + + buffer.position(position + remaining); + } + public final void appendFrom(Parcel parcel, int offset, int length) { nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length); } diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 5d80119410e1..86acb2b21cfa 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -227,14 +227,6 @@ flag { } flag { - name: "ipc_data_cache_test_apis" - namespace: "system_performance" - description: "Expose IpcDataCache test apis to mainline modules." - bug: "396173886" - is_exported: true -} - -flag { name: "mainline_vcn_platform_api" namespace: "vcn" description: "Expose platform APIs to mainline VCN" diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 561a2c96e6a7..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 5b708b382a96..85f38c984f5c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -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. * @@ -15514,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: @@ -20815,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/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 da91e8d2d256..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())) { @@ -9413,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/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..50b8bd22350c 100644 --- a/core/java/android/window/DesktopExperienceFlags.java +++ b/core/java/android/window/DesktopExperienceFlags.java @@ -56,9 +56,12 @@ 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_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/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/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/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index e706af999117..53db2b1178cf 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." @@ -945,3 +952,13 @@ 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 + } +} 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/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp index dec724b6a7ff..b4c58b9b246a 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,58 @@ 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) { + jniThrowExceptionFmt(env, "java/nio/BufferOverflowException", + "Destination buffer remaining capacity %d is less than the Parcel data size %d.", + remaining, dataSize); + 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 +674,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 +991,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/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/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/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_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml index 63f32e3b3cd2..a8d4a1b13bac 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" 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 732021c65742..c94e211f3fac 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_call.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml @@ -78,7 +78,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_marginBottom="@dimen/notification_2025_margin" android:layout_marginTop="@dimen/notification_2025_margin" @@ -174,7 +173,6 @@ </FrameLayout> <LinearLayout - android:id="@+id/notification_action_list_margin_target" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="-20dp" 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 1ee7ddc8d060..6cdc84b9dea7 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml @@ -78,17 +78,10 @@ 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: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" @@ -196,20 +188,20 @@ </FrameLayout> - <LinearLayout - android:id="@+id/notification_action_list_margin_target" + <LinearLayout 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" + <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" /> + <include layout="@layout/notification_2025_action_list" /> + </LinearLayout> + </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..75867b867069 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" 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 af660254d172..0edf9df130f0 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml @@ -89,17 +89,10 @@ 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: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" @@ -161,7 +154,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" @@ -207,20 +199,20 @@ </FrameLayout> - <LinearLayout - android:id="@+id/notification_action_list_margin_target" + <LinearLayout 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" + <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" /> + <include layout="@layout/notification_2025_action_list" /> + </LinearLayout> + </LinearLayout> -</LinearLayout> </com.android.internal.widget.MessagingLayout> 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..1fe77f6f4461 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" > 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..6f8eb148c040 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" 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..3c58567804cd 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" > 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..7d561caa08da 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" 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..b220efc669b9 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" 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..9d54e7eddcfa 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" 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..c354e8568aa1 100644 --- a/core/res/res/layout/notification_2025_template_expanded_media.xml +++ b/core/res/res/layout/notification_2025_template_expanded_media.xml @@ -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..82996dec2925 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" 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..9a4653e73cae 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" > 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/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/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/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/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/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/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/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 890074552284..1977ff52c7c5 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java @@ -44,7 +44,7 @@ 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; @@ -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); } 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/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml index e11babe5cb0e..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 diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 9ebbf71138b0..2a8c88ed3a40 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -525,17 +525,21 @@ <!-- The radius of the Maximize menu shadow. --> <dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen> - <!-- The width of the handle menu in desktop mode. --> - <dimen name="desktop_mode_handle_menu_width">216dp</dimen> + <!-- The width of the handle menu in desktop mode plus the 2dp added for padding to account for + pill elevation. --> + <dimen name="desktop_mode_handle_menu_width">218dp</dimen> - <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each, - additional actions pill 208dp, plus 2dp spacing between them plus 4dp top padding. - 52*3 + 52*4 + (4-1)*2 + 4 = 374 --> - <dimen name="desktop_mode_handle_menu_height">374dp</dimen> + <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each plus + additional actions pill 208dp plus 2dp spacing between them plus 4dp top padding + plus 2dp bottom padding: 52*3 + 52*4 + (4-1)*2 + 4 + 2 = 376 --> + <dimen name="desktop_mode_handle_menu_height">376dp</dimen> <!-- The elevation set on the handle menu pills. --> <dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen> + <!-- The padding added to account for the handle menu's pills' elevation. --> + <dimen name="desktop_mode_handle_menu_pill_elevation_padding">2dp</dimen> + <!-- The height of the handle menu's "App Info" pill in desktop mode. --> <dimen name="desktop_mode_handle_menu_app_info_pill_height">52dp</dimen> diff --git a/libs/WindowManager/Shell/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 b87c2054bea6..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 @@ -23,6 +23,7 @@ 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. @@ -34,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. @@ -46,15 +49,21 @@ 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) /** @see DesktopModeCompatUtils.shouldExcludeCaptionFromAppBounds */ @@ -77,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) { 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/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/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 124043215e3e..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 @@ -1492,6 +1492,7 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, InputManager inputManager, + DisplayController displayController, @ShellMainThread Handler mainHandler ) { if (!DesktopModeStatus.canEnterDesktopMode(context)) { @@ -1506,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/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index 0c2ee4648a43..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 @@ -332,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) { 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 664e19d30707..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 @@ -43,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 @@ -464,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) @@ -2981,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) @@ -3747,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) @@ -3810,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 efd2253bc9b0..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 @@ -37,6 +37,8 @@ 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 @@ -304,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/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/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/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/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/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index febb8850183d..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); @@ -1780,6 +1786,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mMainChoreographer, mSyncQueue, mAppHeaderViewHolderFactory, + mAppHandleViewHolderFactory, mRootTaskDisplayAreaOrganizer, mGenericLinksParser, mAssistContentRequester, @@ -1871,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 5432f6f65afb..003baae29114 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; @@ -558,29 +569,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 +854,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 +905,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 +941,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 +960,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); @@ -1811,9 +1845,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); + } + } } /** @@ -1878,6 +1921,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Choreographer choreographer, SyncTransactionQueue syncQueue, AppHeaderViewHolder.Factory appHeaderViewHolderFactory, + AppHandleViewHolder.Factory appHandleViewHolderFactory, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, @@ -1905,6 +1949,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/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 bde46a1bc375..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 @@ -699,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/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt index d9df899f8b40..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 @@ -49,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, @@ -66,7 +66,7 @@ internal class AppHandleViewHolder( val position: Point, val width: Int, val height: Int, - val isCaptionVisible: Boolean + val showInputLayer: Boolean ) : Data() private lateinit var taskInfo: RunningTaskInfo @@ -101,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( @@ -109,14 +109,13 @@ internal class AppHandleViewHolder( position: Point, width: Int, height: Int, - isCaptionVisible: Boolean + showInputLayer: Boolean ) { 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 } @@ -276,4 +275,25 @@ internal class AppHandleViewHolder( } 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/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/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/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index 3813f752cae8..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() } 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 3c631dc81cbb..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 @@ -5103,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) @@ -7440,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, 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 9333a03c1592..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 @@ -334,6 +334,50 @@ class DesktopTasksTransitionObserverTest { } @Test + @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 = mockTransition, + info = createOpenChangeTransition(nonTopTransparentTask), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId) + } + + @Test + @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 = mockTransition, + info = createToFrontTransition(nonTopTransparentTask), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId) + } + + @Test fun transitCloseWallpaper_wallpaperActivityVisibilitySaved() { val wallpaperTask = createWallpaperTaskInfo() 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 dc96694b814d..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 @@ -66,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 @@ -457,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 @@ -508,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()) } 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/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/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 9a2e2fad50be..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) 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/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/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/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/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/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java index 7bd4b3f771ab..ddd9d2acdab3 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java @@ -112,26 +112,6 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen if (mSwitch.getVisibility() == VISIBLE) { mSwitch.setOnCheckedChangeListener(this); } - - setChecked(mSwitch.isChecked()); - - if (attrs != null) { - final TypedArray a = context.obtainStyledAttributes(attrs, - androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/, - 0 /*defStyleRes*/); - final CharSequence title = a.getText( - androidx.preference.R.styleable.Preference_android_title); - setTitle(title); - //TODO(b/369470034): update to next version - if (isExpressive && Build.VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) { - CharSequence summary = a.getText( - androidx.preference.R.styleable.Preference_android_summary); - setSummary(summary); - } - a.recycle(); - } - - setBackground(mSwitch.isChecked()); } @Override 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/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 2fdf2ac0567b..ae9ad958b287 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -703,18 +703,18 @@ public class BluetoothUtils { // However, app layer need to gate the feature based on whether the device has audio // sharing capability regardless of the BT state. // So here we check the BluetoothProperties when BT off. - String mode = BluetoothProperties.le_audio_dynamic_switcher_mode().orElse("none"); - Set<String> disabledModes = ImmutableSet.of("disabled", "unicast"); + // + // TODO: Also check SystemProperties "persist.bluetooth.leaudio_dynamic_switcher.mode" + // and return true if it is in broadcast mode. + // Now SystemUI don't have access to read the value. int sourceSupportedCode = adapter.isLeAudioBroadcastSourceSupported(); int assistantSupportedCode = adapter.isLeAudioBroadcastAssistantSupported(); return (sourceSupportedCode == BluetoothStatusCodes.FEATURE_SUPPORTED || (sourceSupportedCode == BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED - && BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false) - && !disabledModes.contains(mode))) + && BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false))) && (assistantSupportedCode == BluetoothStatusCodes.FEATURE_SUPPORTED || (assistantSupportedCode == BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED - && BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false) - && !disabledModes.contains(mode))); + && BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false))); } catch (IllegalStateException e) { Log.d(TAG, "Fail to check isAudioSharingSupported, e = ", e); 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 f22bdaf55ce4..01d8694256f3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -724,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 = @@ -732,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/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/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/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/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 e7527dcde95c..584b21adbe77 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -3157,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/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/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/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/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/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/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/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/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/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/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/strings.xml b/packages/SystemUI/res/values/strings.xml index cd94a265aa80..fefc222f283f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2056,7 +2056,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> 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/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/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt index 9b181be93b61..f3316958f01d 100644 --- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt +++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt @@ -16,8 +16,13 @@ package com.android.systemui.display +import android.hardware.display.DisplayManager +import android.os.Handler +import com.android.app.displaylib.DisplayLibComponent +import com.android.app.displaylib.createDisplayLibComponent import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DeviceStateRepository import com.android.systemui.display.data.repository.DeviceStateRepositoryImpl import com.android.systemui.display.data.repository.DisplayRepository @@ -28,6 +33,8 @@ import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepos import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepositoryImpl import com.android.systemui.display.data.repository.FocusedDisplayRepository import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl +import com.android.systemui.display.data.repository.PerDisplayRepoDumpHelper +import com.android.systemui.display.data.repository.PerDisplayRepository import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractorModule @@ -40,9 +47,11 @@ import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope /** Module binding display related classes. */ -@Module(includes = [DisplayWindowPropertiesInteractorModule::class]) +@Module(includes = [DisplayWindowPropertiesInteractorModule::class, DisplayLibModule::class]) interface DisplayModule { @Binds fun bindConnectedDisplayInteractor( @@ -73,6 +82,9 @@ interface DisplayModule { impl: DisplayWindowPropertiesRepositoryImpl ): DisplayWindowPropertiesRepository + @Binds + fun dumpRegistrationLambda(helper: PerDisplayRepoDumpHelper): PerDisplayRepository.InitCallback + companion object { @Provides @SysUISingleton @@ -103,3 +115,31 @@ interface DisplayModule { } } } + +/** Module to bind the DisplayRepository from displaylib to the systemui dagger graph. */ +@Module +object DisplayLibModule { + @Provides + @SysUISingleton + fun displayLibComponent( + displayManager: DisplayManager, + @Background backgroundHandler: Handler, + @Background bgApplicationScope: CoroutineScope, + @Background backgroundCoroutineDispatcher: CoroutineDispatcher, + ): DisplayLibComponent { + return createDisplayLibComponent( + displayManager, + backgroundHandler, + bgApplicationScope, + backgroundCoroutineDispatcher, + ) + } + + @Provides + @SysUISingleton + fun providesDisplayRepositoryFromLib( + displayLibComponent: DisplayLibComponent + ): com.android.app.displaylib.DisplayRepository { + return displayLibComponent.displayRepository + } +} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt index 101e8cc23f17..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,106 +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.displaylib.DisplayRepository as DisplayRepositoryFromLib -import com.android.app.tracing.FlowTracing.traceEach -import com.android.app.tracing.traceSection 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 : DisplayRepositoryFromLib { - /** 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> /** A [StateFlow] that maintains a set of display IDs that should have system decorations. */ val displayIdsWithSystemDecorations: StateFlow<Set<Int>> - - /** - * 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 @@ -124,310 +48,11 @@ interface DisplayRepository : DisplayRepositoryFromLib { 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 = @@ -481,20 +106,5 @@ constructor( private companion object { const val TAG = "DisplayRepository" - val DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG } } - -/** Used to provide default implementations for all methods. */ -private interface DisplayConnectionListener : DisplayListener { - - override fun onDisplayConnected(id: Int) {} - - override fun onDisplayDisconnected(id: Int) {} - - override fun onDisplayAdded(id: Int) {} - - override fun onDisplayRemoved(id: Int) {} - - override fun onDisplayChanged(id: Int) {} -} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt new file mode 100644 index 000000000000..212d55612935 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.display.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.dump.DumpableFromToString +import javax.inject.Inject + +/** Helper class to register PerDisplayRepository in the dump manager in SystemUI. */ +@SysUISingleton +class PerDisplayRepoDumpHelper @Inject constructor(private val dumpManager: DumpManager) : + PerDisplayRepository.InitCallback { + /** + * Registers PerDisplayRepository in the dump manager. + * + * The repository will be identified by the given debug name. + */ + override fun onInit(debugName: String, instance: Any) { + dumpManager.registerNormalDumpable( + "PerDisplayRepository-$debugName", + DumpableFromToString(instance), + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt index d1d013542fbf..7e00c60dc43a 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt @@ -20,13 +20,10 @@ import android.util.Log import android.view.Display import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.traceSection -import com.android.systemui.Dumpable import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dump.DumpManager import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import java.io.PrintWriter import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collectLatest @@ -88,6 +85,20 @@ interface PerDisplayRepository<T> { /** Debug name for this repository, mainly for tracing and logging. */ val debugName: String + + /** + * Callback to run when a given repository is initialized. + * + * This allows the caller to perform custom logic when the repository is ready to be used, e.g. + * register to dumpManager. + * + * Note that the instance is *leaked* outside of this class, so it should only be done when + * repository is meant to live as long as the caller. In systemUI this is ok because the + * repository lives as long as the process itself. + */ + interface InitCallback { + fun onInit(debugName: String, instance: Any) + } } /** @@ -110,8 +121,8 @@ constructor( @Assisted private val instanceProvider: PerDisplayInstanceProvider<T>, @Background private val backgroundApplicationScope: CoroutineScope, private val displayRepository: DisplayRepository, - private val dumpManager: DumpManager, -) : PerDisplayRepository<T>, Dumpable { + private val initCallback: PerDisplayRepository.InitCallback, +) : PerDisplayRepository<T> { private val perDisplayInstances = ConcurrentHashMap<Int, T?>() @@ -120,7 +131,7 @@ constructor( } private suspend fun start() { - dumpManager.registerNormalDumpable("PerDisplayRepository-${debugName}", this) + initCallback.onInit(debugName, this) displayRepository.displayIds.collectLatest { displayIds -> val toRemove = perDisplayInstances.keys - displayIds toRemove.forEach { displayId -> @@ -169,8 +180,9 @@ constructor( private const val TAG = "PerDisplayInstanceRepo" } - override fun dump(pw: PrintWriter, args: Array<out String>) { - pw.println(perDisplayInstances) + override fun toString(): String { + return "PerDisplayInstanceRepositoryImpl(" + + "debugName='$debugName', instances=$perDisplayInstances)" } } @@ -193,6 +205,7 @@ class DefaultDisplayOnlyInstanceRepositoryImpl<T>( private val lazyDefaultDisplayInstance by lazy { instanceProvider.createInstance(Display.DEFAULT_DISPLAY) } + override fun get(displayId: Int): T? = lazyDefaultDisplayInstance } diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt index 15a3cbdb8072..84f103e8f730 100644 --- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.display.domain.interactor import android.companion.virtual.VirtualDeviceManager import android.view.Display +import com.android.app.displaylib.DisplayRepository as DisplayRepositoryFromLib import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DeviceStateRepository @@ -138,7 +139,8 @@ constructor( .distinctUntilChanged() .flowOn(backgroundCoroutineDispatcher) - private fun DisplayRepository.PendingDisplay.toInteractorPendingDisplay(): PendingDisplay = + private fun DisplayRepositoryFromLib.PendingDisplay.toInteractorPendingDisplay(): + PendingDisplay = object : PendingDisplay { override suspend fun enable() = this@toInteractorPendingDisplay.enable() diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt new file mode 100644 index 000000000000..438931a1962d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dump + +import com.android.systemui.Dumpable +import java.io.PrintWriter + +/** Dumpable implementation that just calls toString() on the instance. */ +class DumpableFromToString<T>(private val instance: T) : Dumpable { + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("$instance") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index cf5c3402792e..f2a10cc43fd9 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -147,14 +147,14 @@ import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; +import dagger.Lazy; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import javax.inject.Inject; -import dagger.Lazy; - /** * Helper to show the global actions dialog. Each item is an {@link Action} that may show depending * on whether the keyguard is showing, and whether the device is provisioned. @@ -270,6 +270,16 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final UserLogoutInteractor mLogoutInteractor; private final GlobalActionsInteractor mInteractor; private final Lazy<DisplayWindowPropertiesRepository> mDisplayWindowPropertiesRepositoryLazy; + private final Handler mHandler; + + private final UserTracker.Callback mOnUserSwitched = new UserTracker.Callback() { + @Override + public void onBeforeUserSwitching(int newUser) { + // Dismiss the dialog as soon as we start switching. This will schedule a message + // in a handler so it will be pretty quick. + dismissDialog(); + } + }; @VisibleForTesting public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { @@ -425,6 +435,29 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mInteractor = interactor; mDisplayWindowPropertiesRepositoryLazy = displayWindowPropertiesRepository; + mHandler = new Handler(mMainHandler.getLooper()) { + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_DISMISS: + if (mDialog != null) { + if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) { + // Hide instantly. + mDialog.hide(); + mDialog.dismiss(); + } else { + mDialog.dismiss(); + } + mDialog = null; + } + break; + case MESSAGE_REFRESH: + refreshSilentMode(); + mAdapter.notifyDataSetChanged(); + break; + } + } + }; + // receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); @@ -537,6 +570,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene expandable != null ? expandable.dialogTransitionController( new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)) : null; + mUserTracker.addCallback(mOnUserSwitched, mBackgroundExecutor); if (controller != null) { mDialogTransitionAnimator.show(mDialog, controller); } else { @@ -1404,6 +1438,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mWindowManagerFuncs.onGlobalActionsHidden(); mLifecycle.setCurrentState(Lifecycle.State.CREATED); mInteractor.onDismissed(); + mUserTracker.removeCallback(mOnUserSwitched); } /** @@ -2228,29 +2263,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mDialogPressDelay = 0; // ms } - private Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_DISMISS: - if (mDialog != null) { - if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) { - // Hide instantly. - mDialog.hide(); - mDialog.dismiss(); - } else { - mDialog.dismiss(); - } - mDialog = null; - } - break; - case MESSAGE_REFRESH: - refreshSilentMode(); - mAdapter.notifyDataSetChanged(); - break; - } - } - }; - private void onAirplaneModeChanged() { // Let the service state callbacks handle the state. if (mHasTelephony || mAirplaneModeOn == null) return; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 757464976261..79685088fed7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -673,7 +673,8 @@ public class KeyguardService extends Service { if (SceneContainerFlag.isEnabled()) { mDeviceEntryInteractorLazy.get().lockNow("doKeyguardTimeout"); } else if (KeyguardWmStateRefactor.isEnabled()) { - mKeyguardServiceShowLockscreenInteractor.onKeyguardServiceDoKeyguardTimeout(); + mKeyguardServiceShowLockscreenInteractor + .onKeyguardServiceDoKeyguardTimeout(options); } mKeyguardViewMediator.doKeyguardTimeout(options); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt index 58692746d1e0..51b953ef290c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt @@ -22,11 +22,14 @@ import android.util.Log import android.view.IRemoteAnimationFinishedCallback import android.view.RemoteAnimationTarget import android.view.WindowManager +import com.android.internal.widget.LockPatternUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardShowWhileAwakeInteractor import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.window.flags.Flags import com.android.wm.shell.keyguard.KeyguardTransitions import java.util.concurrent.Executor @@ -46,6 +49,9 @@ constructor( private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier, private val keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor, private val keyguardTransitions: KeyguardTransitions, + private val selectedUserInteractor: SelectedUserInteractor, + private val lockPatternUtils: LockPatternUtils, + private val keyguardShowWhileAwakeInteractor: KeyguardShowWhileAwakeInteractor, ) { /** @@ -92,12 +98,23 @@ constructor( * second timeout). */ private var isKeyguardGoingAway = false - private set(value) { + private set(goingAway) { // TODO(b/278086361): Extricate the keyguard state controller. - keyguardStateController.notifyKeyguardGoingAway(value) - field = value + keyguardStateController.notifyKeyguardGoingAway(goingAway) + + if (goingAway) { + keyguardGoingAwayRequestedForUserId = selectedUserInteractor.getSelectedUserId() + } + + field = goingAway } + /** + * The current user ID when we asked WM to start the keyguard going away animation. This is used + * for validation when user switching occurs during unlock. + */ + private var keyguardGoingAwayRequestedForUserId: Int = -1 + /** Callback provided by WM to call once we're done with the going away animation. */ private var goingAwayRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null @@ -171,6 +188,14 @@ constructor( nonApps: Array<RemoteAnimationTarget>, finishedCallback: IRemoteAnimationFinishedCallback, ) { + goingAwayRemoteAnimationFinishedCallback = finishedCallback + + if (maybeStartTransitionIfUserSwitchedDuringGoingAway()) { + Log.d(TAG, "User switched during keyguard going away - ending remote animation.") + endKeyguardGoingAwayAnimation() + return + } + // If we weren't expecting the keyguard to be going away, WM triggered this transition. if (!isKeyguardGoingAway) { // Since WM triggered this, we're likely not transitioning to GONE yet. See if we can @@ -198,7 +223,6 @@ constructor( } if (apps.isNotEmpty()) { - goingAwayRemoteAnimationFinishedCallback = finishedCallback keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0]) } else { // Nothing to do here if we have no apps, end the animation, which will cancel it and WM @@ -211,6 +235,7 @@ constructor( // If WM cancelled the animation, we need to end immediately even if we're still using the // animation. endKeyguardGoingAwayAnimation() + maybeStartTransitionIfUserSwitchedDuringGoingAway() } /** @@ -301,6 +326,29 @@ constructor( } } + /** + * If necessary, start a transition to show/hide keyguard in response to a user switch during + * keyguard going away. + * + * Returns [true] if a transition was started, or false if a transition was not necessary. + */ + private fun maybeStartTransitionIfUserSwitchedDuringGoingAway(): Boolean { + val currentUser = selectedUserInteractor.getSelectedUserId() + if (currentUser != keyguardGoingAwayRequestedForUserId) { + if (lockPatternUtils.isSecure(currentUser)) { + keyguardShowWhileAwakeInteractor.onSwitchedToSecureUserWhileKeyguardGoingAway() + } else { + keyguardDismissTransitionInteractor.startDismissKeyguardTransition( + reason = "User switch during keyguard going away, and new user is insecure" + ) + } + + return true + } else { + return false + } + } + companion object { private val TAG = "WindowManagerLsVis" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt new file mode 100644 index 000000000000..16c2d14b78ae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import android.os.IRemoteCallback +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * Holds an IRemoteCallback along with the current user ID at the time the callback was provided. + */ +data class ShowLockscreenCallback(val userId: Int, val remoteCallback: IRemoteCallback) + +/** Maintains state related to KeyguardService requests to show the lockscreen. */ +@SysUISingleton +class KeyguardServiceShowLockscreenRepository @Inject constructor() { + val showLockscreenCallbacks = ArrayList<ShowLockscreenCallback>() + + /** + * Adds a callback that we'll notify when we show the lockscreen (or affirmatively decide not to + * show it). + */ + fun addShowLockscreenCallback(forUser: Int, callback: IRemoteCallback) { + synchronized(showLockscreenCallbacks) { + showLockscreenCallbacks.add(ShowLockscreenCallback(forUser, callback)) + } + } + + companion object { + private const val TAG = "ShowLockscreenRepository" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt index ab0efed2cb76..02e04aa279d8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt @@ -226,6 +226,10 @@ constructor( } fun handleFidgetTap(x: Float, y: Float) { + if (!com.android.systemui.Flags.clockFidgetAnimation()) { + return + } + if (selectedClockSize.value == ClockSizeSetting.DYNAMIC) { clockEventController.handleFidgetTap(x, y) } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt index b55bb383c308..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/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/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/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/remedia/ui/compose/Media.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt index 7543e0f2af2e..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 @@ -46,7 +46,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -55,7 +54,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ButtonDefaults @@ -75,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 @@ -101,6 +100,7 @@ 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 @@ -110,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 @@ -138,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. @@ -406,7 +408,7 @@ private fun ContentScope.CardForegroundContent( ) ) { // Always add the first/top row, regardless of presentation style. - BoxWithConstraints(modifier = Modifier.fillMaxWidth()) { + Box(modifier = Modifier.fillMaxWidth()) { // Icon. Icon( icon = viewModel.icon, @@ -418,9 +420,26 @@ 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( @@ -433,9 +452,23 @@ private fun ContentScope.CardForegroundContent( // // 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. - .widthIn(max = this@BoxWithConstraints.maxWidth * 0.4f), + // 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) + } + }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index a4386dee7264..05a60a6db31e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -18,7 +18,6 @@ package com.android.systemui.qs.composefragment import android.annotation.SuppressLint import android.content.Context -import android.content.res.Configuration import android.graphics.PointF import android.graphics.Rect import android.os.Bundle @@ -49,7 +48,6 @@ import androidx.compose.foundation.layout.requiredHeightIn import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -72,8 +70,6 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.layout.positionOnScreen import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.CustomAccessibilityAction @@ -255,7 +251,7 @@ constructor( @Composable private fun Content() { - PlatformTheme(isDarkTheme = true /* Delete AlwaysDarkMode when removing this */) { + PlatformTheme { ProvideShortcutHelperIndication(interactionsConfig = interactionsConfig()) { // TODO(b/389985793): Make sure that there is no coroutine work or recompositions // happening when alwaysCompose is true but isQsVisibleAndAnyShadeExpanded is false. @@ -747,25 +743,22 @@ constructor( ) val BrightnessSlider = @Composable { - AlwaysDarkMode { - Box( - Modifier.systemGestureExclusionInShade( - enabled = { - layoutState.transitionState is TransitionState.Idle - } - ) - ) { - BrightnessSliderContainer( - viewModel = - containerViewModel.brightnessSliderViewModel, - containerColors = - ContainerColors( - Color.Transparent, - ContainerColors.defaultContainerColor, - ), - modifier = Modifier.fillMaxWidth(), - ) - } + Box( + Modifier.systemGestureExclusionInShade( + enabled = { + layoutState.transitionState is TransitionState.Idle + } + ) + ) { + BrightnessSliderContainer( + viewModel = containerViewModel.brightnessSliderViewModel, + containerColors = + ContainerColors( + Color.Transparent, + ContainerColors.defaultContainerColor, + ), + modifier = Modifier.fillMaxWidth(), + ) } } val TileGrid = @@ -1243,28 +1236,3 @@ private fun interactionsConfig() = private inline val alwaysCompose get() = Flags.alwaysComposeQsUiFragment() - -/** - * Forces the configuration and themes to be dark theme. This is needed in order to have - * [colorResource] retrieve the dark mode colors. - * - * This should be removed when we remove the force dark mode in [PlatformTheme] at the root of the - * compose hierarchy. - */ -@Composable -private fun AlwaysDarkMode(content: @Composable () -> Unit) { - val currentConfig = LocalConfiguration.current - val darkConfig = - Configuration(currentConfig).apply { - uiMode = - (uiMode and (Configuration.UI_MODE_NIGHT_MASK.inv())) or - Configuration.UI_MODE_NIGHT_YES - } - val newContext = LocalContext.current.createConfigurationContext(darkConfig) - CompositionLocalProvider( - LocalConfiguration provides darkConfig, - LocalContext provides newContext, - ) { - content() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index 22971a9eb703..a7ebb2289814 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -88,7 +88,11 @@ constructor( LaunchedEffect(listening, pagerState) { snapshotFlow { listening() } .collect { - if (!listening()) { + // Whenever we go from not listening to listening, we should be in the first + // page. If we did this when going from listening to not listening, opening + // edit mode in second page will cause it to go to first page during the + // transition. + if (listening()) { pagerState.scrollToPage(0) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt index 69b967a68c3c..ccbd8fdbe00c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt @@ -63,6 +63,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -89,6 +90,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.graphics.Color @@ -113,7 +115,9 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastMap +import com.android.compose.gesture.effect.rememberOffsetOverscrollEffectFactory import com.android.compose.modifiers.height +import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.common.ui.compose.load import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.SizedTileImpl @@ -131,6 +135,7 @@ import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaul import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AUTO_SCROLL_SPEED import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AvailableTilesGridMinHeight import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.CurrentTilesGridPadding +import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.GridBackgroundCornerRadius import com.android.systemui.qs.panels.ui.compose.selection.InteractiveTileContainer import com.android.systemui.qs.panels.ui.compose.selection.MutableSelectionState import com.android.systemui.qs.panels.ui.compose.selection.ResizingState @@ -163,14 +168,27 @@ object TileType @OptIn(ExperimentalMaterial3Api::class) @Composable private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { - + val primaryContainerColor = MaterialTheme.colorScheme.primaryContainer TopAppBar( - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), - title = { Text(text = stringResource(id = R.string.qs_edit)) }, + colors = + TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent, + titleContentColor = MaterialTheme.colorScheme.onSurface, + ), + title = { + Text( + text = stringResource(id = R.string.qs_edit), + modifier = Modifier.padding(start = 24.dp), + ) + }, navigationIcon = { - IconButton(onClick = onStopEditing) { + IconButton( + onClick = onStopEditing, + modifier = Modifier.drawBehind { drawCircle(primaryContainerColor) }, + ) { Icon( Icons.AutoMirrored.Filled.ArrowBack, + tint = MaterialTheme.colorScheme.onSurface, contentDescription = stringResource(id = com.android.internal.R.string.action_bar_up_description), ) @@ -178,11 +196,19 @@ private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { }, actions = { if (onReset != null) { - TextButton(onClick = onReset) { + TextButton( + onClick = onReset, + colors = + ButtonDefaults.textButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ), + ) { Text(stringResource(id = com.android.internal.R.string.reset)) } } }, + modifier = Modifier.padding(vertical = 8.dp), ) } @@ -215,7 +241,9 @@ fun DefaultEditTileGrid( containerColor = Color.Transparent, topBar = { EditModeTopBar(onStopEditing = onStopEditing, onReset = reset) }, ) { innerPadding -> - CompositionLocalProvider(LocalOverscrollFactory provides null) { + CompositionLocalProvider( + LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory() + ) { val scrollState = rememberScrollState() AutoScrollGrid(listState, scrollState, innerPadding) @@ -244,7 +272,7 @@ fun DefaultEditTileGrid( targetState = listState.dragInProgress || selectionState.selected, label = "QSEditHeader", contentAlignment = Alignment.Center, - modifier = Modifier.fillMaxWidth().heightIn(min = 80.dp), + modifier = Modifier.fillMaxWidth().heightIn(min = 48.dp), ) { showRemoveTarget -> EditGridHeader { if (showRemoveTarget) { @@ -289,10 +317,6 @@ fun DefaultEditTileGrid( spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)), modifier = modifier.fillMaxSize(), ) { - EditGridHeader { - Text(text = stringResource(id = R.string.drag_to_add_tiles)) - } - val availableTiles = remember { mutableStateListOf<AvailableTileGridCell>().apply { addAll(toAvailableTiles(listState.tiles, otherTiles)) @@ -371,9 +395,7 @@ private fun EditGridHeader( modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit, ) { - CompositionLocalProvider( - LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f) - ) { + CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) { Box(contentAlignment = Alignment.Center, modifier = modifier.fillMaxWidth()) { content() } } } @@ -420,6 +442,7 @@ private fun CurrentTilesGrid( listState.tiles.fastMap { Pair(it, BounceableTileViewModel()) } } + val primaryColor = MaterialTheme.colorScheme.primary TileLazyGrid( state = gridState, columns = GridCells.Fixed(columns), @@ -428,9 +451,9 @@ private fun CurrentTilesGrid( Modifier.fillMaxWidth() .height { totalHeight.roundToPx() } .border( - width = 1.dp, - color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f), - shape = RoundedCornerShape((TileHeight / 2) + CurrentTilesGridPadding), + width = 2.dp, + color = primaryColor, + shape = RoundedCornerShape(GridBackgroundCornerRadius), ) .dragAndDropTileList(gridState, { gridContentOffset }, listState) { spec -> onSetTiles(currentListState.tileSpecs()) @@ -439,6 +462,13 @@ private fun CurrentTilesGrid( .onGloballyPositioned { coordinates -> gridContentOffset = coordinates.positionInRoot() } + .drawBehind { + drawRoundRect( + primaryColor, + cornerRadius = CornerRadius(GridBackgroundCornerRadius.toPx()), + alpha = .15f, + ) + } .testTag(CURRENT_TILES_GRID_TEST_TAG), ) { EditTiles(cells, listState, selectionState, coroutineScope, largeTilesSpan, onRemoveTile) { @@ -469,7 +499,6 @@ private fun AvailableTileGrid( remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) { groupAndSort(tiles) } - val labelColors = EditModeTileDefaults.editTileColors() // Available tiles Column( @@ -480,32 +509,45 @@ private fun AvailableTileGrid( ) { groupedTiles.forEach { (category, tiles) -> key(category) { - Text( - text = category.label.load() ?: "", - fontSize = 20.sp, - color = labelColors.label, + val surfaceColor = MaterialTheme.colorScheme.surface + Column( + verticalArrangement = spacedBy(16.dp), modifier = - Modifier.fillMaxWidth().padding(start = 16.dp, bottom = 8.dp, top = 8.dp), - ) - tiles.chunked(columns).forEach { row -> - Row( - horizontalArrangement = spacedBy(TileArrangementPadding), - modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max), - ) { - row.forEach { tileGridCell -> - key(tileGridCell.key) { - AvailableTileGridCell( - cell = tileGridCell, - dragAndDropState = dragAndDropState, - selectionState = selectionState, - onAddTile = onAddTile, - modifier = Modifier.weight(1f).fillMaxHeight(), + Modifier.drawBehind { + drawRoundRect( + surfaceColor, + cornerRadius = CornerRadius(GridBackgroundCornerRadius.toPx()), + alpha = .32f, ) } - } + .padding(16.dp), + ) { + Text( + text = category.label.load() ?: "", + fontSize = 20.sp, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.fillMaxWidth().padding(start = 8.dp, bottom = 16.dp), + ) + tiles.chunked(columns).forEach { row -> + Row( + horizontalArrangement = spacedBy(TileArrangementPadding), + modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max), + ) { + row.forEach { tileGridCell -> + key(tileGridCell.key) { + AvailableTileGridCell( + cell = tileGridCell, + dragAndDropState = dragAndDropState, + selectionState = selectionState, + onAddTile = onAddTile, + modifier = Modifier.weight(1f).fillMaxHeight(), + ) + } + } - // Spacers for incomplete rows - repeat(columns - row.size) { Spacer(modifier = Modifier.weight(1f)) } + // Spacers for incomplete rows + repeat(columns - row.size) { Spacer(modifier = Modifier.weight(1f)) } + } } } } @@ -761,7 +803,7 @@ private fun AvailableTileGridCell( color = colors.label, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, - modifier = Modifier.align(Alignment.Center), + modifier = Modifier.align(Alignment.TopCenter), ) } } @@ -861,15 +903,16 @@ private object EditModeTileDefaults { const val AUTO_SCROLL_SPEED = 2 // 2ms per pixel val CurrentTilesGridPadding = 10.dp val AvailableTilesGridMinHeight = 200.dp + val GridBackgroundCornerRadius = 42.dp @Composable fun editTileColors(): TileColors = TileColors( - background = MaterialTheme.colorScheme.surfaceVariant, - iconBackground = MaterialTheme.colorScheme.surfaceVariant, - label = MaterialTheme.colorScheme.onSurfaceVariant, - secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant, - icon = MaterialTheme.colorScheme.onSurfaceVariant, + background = LocalAndroidColorScheme.current.surfaceEffect2, + iconBackground = Color.Transparent, + label = MaterialTheme.colorScheme.onSurface, + secondaryLabel = MaterialTheme.colorScheme.onSurface, + icon = MaterialTheme.colorScheme.onSurface, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/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/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/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/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/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/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 238ba8d9f490..780e8f47a7fe 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/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt index a0a86710b4ba..f43767d3effb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt @@ -22,7 +22,6 @@ import com.android.internal.widget.MessagingGroup import com.android.internal.widget.MessagingMessage import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.systemui.Flags import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener @@ -147,9 +146,7 @@ internal constructor( traceSection("updateNotifOnUiModeChanged") { mPipeline?.allNotifs?.forEach { entry -> entry.row?.onUiModeChanged() - if (Flags.notificationUndoGutsOnConfigChanged()) { - mGutsManager.closeAndUndoGuts() - } + mGutsManager.closeAndUndoGuts() } } } @@ -158,16 +155,7 @@ internal constructor( colorUpdateLogger.logEvent("VCC.updateNotificationsOnDensityOrFontScaleChanged()") mPipeline?.allNotifs?.forEach { entry -> entry.onDensityOrFontScaleChanged() - if (Flags.notificationUndoGutsOnConfigChanged()) { - mGutsManager.closeAndUndoGuts() - } else { - // This property actually gets reset when the guts are re-inflated, so we're never - // actually calling onDensityOrFontScaleChanged below. - val exposedGuts = entry.areGutsExposed() - if (exposedGuts) { - mGutsManager.onDensityOrFontScaleChanged(entry) - } - } + mGutsManager.closeAndUndoGuts() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index bdbdc53c4b1c..27765635edcb 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/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/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/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 b3357d01ab7a..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); @@ -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/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/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/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt index ba666512af5a..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, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt index c717b180575c..540babad5dd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt @@ -21,6 +21,8 @@ import android.graphics.Rect import android.view.Display import android.view.View import androidx.compose.runtime.getValue +import com.android.app.tracing.FlowTracing.traceEach +import com.android.app.tracing.TrackGroupUtils.trackGroup import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -130,7 +132,7 @@ interface HomeStatusBarViewModel : Activatable { val primaryOngoingActivityChip: StateFlow<OngoingActivityChipModel> /** All supported activity chips, whether they are currently active or not. */ - val ongoingActivityChips: StateFlow<ChipsVisibilityModel> + val ongoingActivityChips: ChipsVisibilityModel /** * The multiple ongoing activity chips that should be shown on the left-hand side of the status @@ -386,11 +388,9 @@ constructor( } override val isHomeStatusBarAllowed = - isHomeStatusBarAllowedCompat.stateIn( - bgScope, - SharingStarted.WhileSubscribed(), - initialValue = false, - ) + isHomeStatusBarAllowedCompat + .traceEach(trackGroup(TRACK_GROUP, "isHomeStatusBarAllowed"), logcat = true) + .stateIn(bgScope, SharingStarted.WhileSubscribed(), initialValue = false) private val shouldHomeStatusBarBeVisible = combine( @@ -461,24 +461,29 @@ constructor( isHomeStatusBarAllowed && !isSecureCameraActive && !hideStartSideContentForHeadsUp } - override val ongoingActivityChips = + private val chipsVisibilityModel: Flow<ChipsVisibilityModel> = combine(ongoingActivityChipsViewModel.chips, canShowOngoingActivityChips) { chips, canShow -> ChipsVisibilityModel(chips, areChipsAllowed = canShow) } - .stateIn( - bgScope, - SharingStarted.WhileSubscribed(), - initialValue = - ChipsVisibilityModel( - chips = MultipleOngoingActivityChipsModel(), - areChipsAllowed = false, - ), - ) + .traceEach(trackGroup(TRACK_GROUP, "chips"), logcat = true) { + "Chips[allowed=${it.areChipsAllowed} numChips=${it.chips.active.size}]" + } + + override val ongoingActivityChips: ChipsVisibilityModel by + hydrator.hydratedStateOf( + traceName = "ongoingActivityChips", + initialValue = + ChipsVisibilityModel( + chips = MultipleOngoingActivityChipsModel(), + areChipsAllowed = false, + ), + source = chipsVisibilityModel, + ) private val hasOngoingActivityChips = if (StatusBarChipsModernization.isEnabled) { - ongoingActivityChips.map { it.chips.active.any { chip -> !chip.isHidden } } + chipsVisibilityModel.map { it.chips.active.any { chip -> !chip.isHidden } } } else if (StatusBarNotifChips.isEnabled) { ongoingActivityChipsLegacy.map { it.primary is OngoingActivityChipModel.Active } } else { @@ -607,6 +612,8 @@ constructor( private const val COL_PREFIX_NOTIF_CONTAINER = "notifContainer" private const val COL_PREFIX_SYSTEM_INFO = "systemInfo" + private const val TRACK_GROUP = "StatusBar" + fun tableLogBufferName(displayId: Int) = "HomeStatusBarViewModel[$displayId]" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java index b13e01be40f7..fa022b4768fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java @@ -27,6 +27,7 @@ import android.util.IndentingPrintWriter; import androidx.annotation.NonNull; +import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager; import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; import com.android.systemui.Dumpable; import com.android.systemui.dagger.qualifiers.Main; @@ -56,8 +57,8 @@ public final class DeviceStateRotationLockSettingController private int mDeviceState = -1; @Nullable private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; - private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener - mDeviceStateRotationLockSettingsListener; + private DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener + mDeviceStateAutoRotateSettingListener; @Inject public DeviceStateRotationLockSettingController( @@ -83,17 +84,17 @@ public final class DeviceStateRotationLockSettingController // is no user action. mDeviceStateCallback = this::updateDeviceState; mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback); - mDeviceStateRotationLockSettingsListener = () -> + mDeviceStateAutoRotateSettingListener = () -> readPersistedSetting("deviceStateRotationLockChange", mDeviceState); mDeviceStateRotationLockSettingsManager.registerListener( - mDeviceStateRotationLockSettingsListener); + mDeviceStateAutoRotateSettingListener); } else { if (mDeviceStateCallback != null) { mDeviceStateManager.unregisterCallback(mDeviceStateCallback); } - if (mDeviceStateRotationLockSettingsListener != null) { + if (mDeviceStateAutoRotateSettingListener != null) { mDeviceStateRotationLockSettingsManager.unregisterListener( - mDeviceStateRotationLockSettingsListener); + mDeviceStateAutoRotateSettingListener); } } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 1bc3096d0ac1..9475bdbd043b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -2746,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/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/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/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/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/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/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/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index b75b7ddf8181..9ae8ff869800 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -3871,6 +3871,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { */ public void notifyCarrierRoamingNtnSignalStrengthChanged(int subId, @NonNull NtnSignalStrength ntnSignalStrength) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + log("notifyCarrierRoamingNtnSignalStrengthChanged: invalid subscription id"); + return; + } if (!checkNotifyPermission("notifyCarrierRoamingNtnSignalStrengthChanged")) { log("notifyCarrierRoamingNtnSignalStrengthChanged: caller does not have required " + "permissions."); 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/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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 01e3d670d12a..d917bffa06e9 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -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/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/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/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/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/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java index ce3ad889a308..d9354323ae7c 100644 --- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java +++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java @@ -73,9 +73,12 @@ public final class DesktopModeBoundsCalculator { 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 = diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 76a39d9c884a..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, 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/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..242aea941429 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; } @@ -1024,18 +1019,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/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/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 6c3d51622ff5..c19fa8c03e0a 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -473,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) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 4a93904e466c..a270af56cbcd 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -169,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; @@ -231,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; @@ -399,7 +397,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * rotation. */ final boolean mForceSeamlesslyRotate; - SeamlessRotator mPendingSeamlessRotate; private RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks; @@ -593,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; @@ -655,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. */ @@ -778,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()) { @@ -899,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; } @@ -2176,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; } /** @@ -3998,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); @@ -4144,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); @@ -4883,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"); @@ -5282,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/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index e32ce525cb40..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); } 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/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/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index d79d88400cf9..f0e61ec9c692 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -109,7 +109,7 @@ import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.Manifest; import android.app.ActivityManager; @@ -3724,8 +3724,8 @@ public final class AlarmManagerServiceTest { setDeviceConfigInt(KEY_TEMPORARY_QUOTA_BUMP, 0); mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER); - verifyZeroInteractions(mPackageManagerInternal); - verifyZeroInteractions(mService.mHandler); + verifyNoMoreInteractions(mPackageManagerInternal); + verifyNoMoreInteractions(mService.mHandler); } private void testTemporaryQuota_bumpedAfterDeferral(int standbyBucket) throws Exception { diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java index 7dab1c854625..859d2d2f2e38 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java @@ -30,7 +30,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.app.AlarmManager; import android.app.PendingIntent; @@ -244,7 +244,7 @@ public class AlarmStoreTest { addAlarmsToStore(simpleAlarm, alarmClock); mAlarmStore.remove(simpleAlarm::equals); - verifyZeroInteractions(onRemoved); + verifyNoMoreInteractions(onRemoved); mAlarmStore.remove(alarmClock::equals); verify(onRemoved).run(); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java index 35ab2d233563..acc06d0c7cba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -74,7 +74,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import android.Manifest; import android.app.ActivityManager; @@ -584,7 +583,7 @@ public class ActivityManagerServiceTest { if (app.uid == uidRec.getUid() && expectedBlockState == NETWORK_STATE_BLOCK) { verify(app.getThread()).setNetworkBlockSeq(uidRec.curProcStateSeq); } else { - verifyZeroInteractions(app.getThread()); + verifyNoMoreInteractions(app.getThread()); } Mockito.reset(app.getThread()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/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/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/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/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 75df9a8707c0..d254e9689048 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -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 @@ -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/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index 42b84bdc51e6..c7c8c5846bb1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -64,7 +64,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityService; @@ -838,7 +837,7 @@ public class AbstractAccessibilityServiceConnectionTest { // ...without secure layers included assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isFalse(); // No error sent to callback - verifyZeroInteractions(mMockCallback); + verifyNoMoreInteractions(mMockCallback); } @Test @@ -856,7 +855,7 @@ public class AbstractAccessibilityServiceConnectionTest { // ...with secure layers included assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isTrue(); // No error sent to callback - verifyZeroInteractions(mMockCallback); + verifyNoMoreInteractions(mMockCallback); } @Test @@ -889,7 +888,7 @@ public class AbstractAccessibilityServiceConnectionTest { // ...with secure layers included assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isTrue(); // No error sent to callback - verifyZeroInteractions(mMockCallback); + verifyNoMoreInteractions(mMockCallback); } private void takeScreenshotOfWindow(int windowFlags) throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 8253595a50d1..2ccd33648c3e 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -50,6 +50,7 @@ import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; @@ -66,6 +67,7 @@ import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.app.RemoteAction; @@ -77,10 +79,12 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.XmlResourceParser; import android.graphics.drawable.Icon; +import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.hardware.input.KeyGestureEvent; import android.net.Uri; @@ -114,6 +118,7 @@ import android.view.accessibility.IUserInitializationCompleteCallback; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.TestUtils; import com.android.internal.R; @@ -136,6 +141,8 @@ import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; +import com.google.common.truth.Correspondence; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -188,6 +195,8 @@ public class AccessibilityManagerServiceTest { DESCRIPTION, TEST_PENDING_INTENT); + private static final int FAKE_SYSTEMUI_UID = 1000; + private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1; private static final String TARGET_MAGNIFICATION = MAGNIFICATION_CONTROLLER_NAME; private static final ComponentName TARGET_ALWAYS_ON_A11Y_SERVICE = @@ -207,11 +216,12 @@ public class AccessibilityManagerServiceTest { @Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport; @Mock private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController; @Mock private PackageManager mMockPackageManager; + @Mock + private PackageManagerInternal mMockPackageManagerInternal; @Mock private WindowManagerInternal mMockWindowManagerService; @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy; @Mock private SystemActionPerformer mMockSystemActionPerformer; @Mock private AccessibilityWindowManager mMockA11yWindowManager; - @Mock private AccessibilityDisplayListener mMockA11yDisplayListener; @Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal; @Mock private UserManagerInternal mMockUserManagerInternal; @Mock private IBinder mMockBinder; @@ -234,6 +244,7 @@ public class AccessibilityManagerServiceTest { private TestableLooper mTestableLooper; private Handler mHandler; private FakePermissionEnforcer mFakePermissionEnforcer; + private TestDisplayManagerWrapper mTestDisplayManagerWrapper; @Before public void setUp() throws Exception { @@ -246,6 +257,7 @@ public class AccessibilityManagerServiceTest { LocalServices.removeServiceForTest(UserManagerInternal.class); LocalServices.removeServiceForTest(StatusBarManagerInternal.class); LocalServices.removeServiceForTest(PermissionEnforcer.class); + LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService( WindowManagerInternal.class, mMockWindowManagerService); LocalServices.addService( @@ -256,6 +268,12 @@ public class AccessibilityManagerServiceTest { mInputFilter = mock(FakeInputFilter.class); mTestableContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager); + when(mMockPackageManagerInternal.getSystemUiServiceComponent()).thenReturn( + new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService")); + when(mMockPackageManagerInternal.getPackageUid(eq("com.android.systemui"), anyLong(), + anyInt())).thenReturn(FAKE_SYSTEMUI_UID); + LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); + when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn( mMockMagnificationConnectionManager); when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn( @@ -273,15 +291,9 @@ public class AccessibilityManagerServiceTest { eq(UserHandle.USER_CURRENT))) .thenReturn(mTestableContext.getUserId()); - final ArrayList<Display> displays = new ArrayList<>(); - final Display defaultDisplay = new Display(DisplayManagerGlobal.getInstance(), - Display.DEFAULT_DISPLAY, new DisplayInfo(), - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); - final Display testDisplay = new Display(DisplayManagerGlobal.getInstance(), TEST_DISPLAY, - new DisplayInfo(), DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); - displays.add(defaultDisplay); - displays.add(testDisplay); - when(mMockA11yDisplayListener.getValidDisplayList()).thenReturn(displays); + mTestDisplayManagerWrapper = new TestDisplayManagerWrapper(mTestableContext); + mTestDisplayManagerWrapper.mDisplays = createFakeDisplayList(Display.TYPE_INTERNAL, + Display.TYPE_EXTERNAL); mA11yms = new AccessibilityManagerService( mTestableContext, @@ -290,7 +302,7 @@ public class AccessibilityManagerServiceTest { mMockSecurityPolicy, mMockSystemActionPerformer, mMockA11yWindowManager, - mMockA11yDisplayListener, + mTestDisplayManagerWrapper, mMockMagnificationController, mInputFilter, mProxyManager, @@ -2309,6 +2321,73 @@ public class AccessibilityManagerServiceTest { mA11yms.getCurrentUserIdLocked())).isEmpty(); } + @Test + public void displayListReturnsDisplays() { + mTestDisplayManagerWrapper.mDisplays = createFakeDisplayList( + Display.TYPE_INTERNAL, + Display.TYPE_EXTERNAL, + Display.TYPE_WIFI, + Display.TYPE_OVERLAY, + Display.TYPE_VIRTUAL + ); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // In #setUp() we already have TYPE_INTERNAL and TYPE_EXTERNAL. Call the rest. + for (int i = 2; i < mTestDisplayManagerWrapper.mDisplays.size(); i++) { + mTestDisplayManagerWrapper.mRegisteredListener.onDisplayAdded( + mTestDisplayManagerWrapper.mDisplays.get(i).getDisplayId()); + } + }); + + List<Display> displays = mA11yms.getValidDisplayList(); + assertThat(displays).hasSize(5); + assertThat(displays) + .comparingElementsUsing( + Correspondence.transforming(Display::getType, "has a type of")) + .containsExactly(Display.TYPE_INTERNAL, + Display.TYPE_EXTERNAL, + Display.TYPE_WIFI, + Display.TYPE_OVERLAY, + Display.TYPE_VIRTUAL); + } + + @Test + public void displayListReturnsDisplays_excludesVirtualPrivate() { + // Add a private virtual display whose uid is different from systemui. + final List<Display> displays = createFakeDisplayList(Display.TYPE_INTERNAL, + Display.TYPE_EXTERNAL); + displays.add(createFakeVirtualPrivateDisplay(/* displayId= */ 2, FAKE_SYSTEMUI_UID + 100)); + mTestDisplayManagerWrapper.mDisplays = displays; + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mTestDisplayManagerWrapper.mRegisteredListener.onDisplayAdded(2); + }); + + List<Display> validDisplays = mA11yms.getValidDisplayList(); + assertThat(validDisplays).hasSize(2); + assertThat(validDisplays) + .comparingElementsUsing( + Correspondence.transforming(Display::getType, "has a type of")) + .doesNotContain(Display.TYPE_VIRTUAL); + } + + @Test + public void displayListReturnsDisplays_includesVirtualSystemUIPrivate() { + // Add a private virtual display whose uid is systemui. + final List<Display> displays = createFakeDisplayList(Display.TYPE_INTERNAL, + Display.TYPE_EXTERNAL); + displays.add(createFakeVirtualPrivateDisplay(/* displayId= */ 2, FAKE_SYSTEMUI_UID)); + mTestDisplayManagerWrapper.mDisplays = displays; + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mTestDisplayManagerWrapper.mRegisteredListener.onDisplayAdded(2); + }); + + List<Display> validDisplays = mA11yms.getValidDisplayList(); + assertThat(validDisplays).hasSize(3); + assertThat(validDisplays) + .comparingElementsUsing( + Correspondence.transforming(Display::getType, "has a type of")) + .contains(Display.TYPE_VIRTUAL); + } + private Set<String> readStringsFromSetting(String setting) { final Set<String> result = new ArraySet<>(); mA11yms.readColonDelimitedSettingToSet( @@ -2422,6 +2501,27 @@ public class AccessibilityManagerServiceTest { }); } + private static List<Display> createFakeDisplayList(int... types) { + final ArrayList<Display> displays = new ArrayList<>(); + for (int i = 0; i < types.length; i++) { + final DisplayInfo info = new DisplayInfo(); + info.type = types[i]; + final Display display = new Display(DisplayManagerGlobal.getInstance(), + i, info, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + displays.add(display); + } + return displays; + } + + private static Display createFakeVirtualPrivateDisplay(int displayId, int uid) { + final DisplayInfo info = new DisplayInfo(); + info.type = Display.TYPE_VIRTUAL; + info.flags |= Display.FLAG_PRIVATE; + info.ownerUid = uid; + return new Display(DisplayManagerGlobal.getInstance(), + displayId, info, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + } + public static class FakeInputFilter extends AccessibilityInputFilter { FakeInputFilter(Context context, AccessibilityManagerService service) { @@ -2506,4 +2606,35 @@ public class AccessibilityManagerServiceTest { Set<String> setting = readStringsFromSetting(ShortcutUtils.convertToKey(shortcutType)); assertThat(setting).containsExactlyElementsIn(value); } + + private static class TestDisplayManagerWrapper extends + AccessibilityDisplayListener.DisplayManagerWrapper { + List<Display> mDisplays; + DisplayManager.DisplayListener mRegisteredListener; + + TestDisplayManagerWrapper(Context context) { + super(context); + } + + @Override + public Display[] getDisplays() { + return mDisplays.toArray(new Display[0]); + } + + @Override + public Display getDisplay(int displayId) { + for (final Display display : mDisplays) { + if (display.getDisplayId() == displayId) { + return display; + } + } + return null; + } + + @Override + public void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, + @Nullable Handler handler) { + mRegisteredListener = listener; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/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/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 c3256cad0fd8..186f7425b189 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java @@ -27,7 +27,7 @@ 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; @@ -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/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java index 9b8e619c964d..367f2d143acc 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java @@ -37,7 +37,6 @@ 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/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/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/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/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/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/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/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 46be9a57ce57..11143653a75d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -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/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/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/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/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java index 48731cb460d4..00b617e91bfd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java @@ -383,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); @@ -411,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); @@ -1170,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(); 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/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/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/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/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/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); } /** |