diff options
29 files changed, 1112 insertions, 416 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java index 6840877369f5..dc5e3414a819 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java @@ -206,14 +206,14 @@ public class UserWakeupStore { * @return true if an entry is found and the list of wakeups changes. */ private boolean deleteWakeupFromUserStarts(int userId) { - int index; synchronized (mUserWakeupLock) { - index = mUserStarts.indexOfKey(userId); + final int index = mUserStarts.indexOfKey(userId); if (index >= 0) { mUserStarts.removeAt(index); + return true; } + return false; } - return index >= 0; } /** diff --git a/api/Android.bp b/api/Android.bp index 3fa9c600741e..26899eaf872d 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -157,6 +157,7 @@ genrule { genrule { name: "frameworks-base-api-system-current-compat", srcs: [ + ":android.api.public.latest", ":android.api.system.latest", ":android-incompatibilities.api.system.latest", ":frameworks-base-api-current.txt", @@ -165,33 +166,35 @@ genrule { out: ["updated-baseline.txt"], tools: ["metalava"], cmd: metalava_cmd + + "--check-compatibility:api:released $(location :android.api.public.latest) " + "--check-compatibility:api:released $(location :android.api.system.latest) " + - "--check-compatibility:base $(location :frameworks-base-api-current.txt) " + "--baseline:compatibility:released $(location :android-incompatibilities.api.system.latest) " + "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " + + "$(location :frameworks-base-api-current.txt) " + "$(location :frameworks-base-api-system-current.txt)", } genrule { name: "frameworks-base-api-module-lib-current-compat", srcs: [ + ":android.api.public.latest", + ":android.api.system.latest", ":android.api.module-lib.latest", ":android-incompatibilities.api.module-lib.latest", ":frameworks-base-api-current.txt", + ":frameworks-base-api-system-current.txt", ":frameworks-base-api-module-lib-current.txt", ], out: ["updated-baseline.txt"], tools: ["metalava"], cmd: metalava_cmd + + "--check-compatibility:api:released $(location :android.api.public.latest) " + + "--check-compatibility:api:released $(location :android.api.system.latest) " + "--check-compatibility:api:released $(location :android.api.module-lib.latest) " + - // Note: having "public" be the base of module-lib is not perfect -- it should - // ideally be a merged public+system (which metalava is not currently able to generate). - // This "base" will help when migrating from MODULE_LIBS -> public, but not when - // migrating from MODULE_LIBS -> system (where it needs to instead be listed as - // an incompatibility). - "--check-compatibility:base $(location :frameworks-base-api-current.txt) " + "--baseline:compatibility:released $(location :android-incompatibilities.api.module-lib.latest) " + "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " + + "$(location :frameworks-base-api-current.txt) " + + "$(location :frameworks-base-api-system-current.txt) " + "$(location :frameworks-base-api-module-lib-current.txt)", } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index a3afcce5fe36..863a99adacca 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -262,15 +262,12 @@ public class ZenModeConfig implements Parcelable { private static final String CONDITION_ATT_SOURCE = "source"; private static final String CONDITION_ATT_FLAGS = "flags"; - private static final String ZEN_POLICY_TAG = "zen_policy"; - private static final String MANUAL_TAG = "manual"; private static final String AUTOMATIC_TAG = "automatic"; private static final String AUTOMATIC_DELETED_TAG = "deleted"; private static final String RULE_ATT_ID = "ruleId"; private static final String RULE_ATT_ENABLED = "enabled"; - private static final String RULE_ATT_SNOOZING = "snoozing"; private static final String RULE_ATT_NAME = "name"; private static final String RULE_ATT_PKG = "pkg"; private static final String RULE_ATT_COMPONENT = "component"; @@ -286,6 +283,7 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_ICON = "rule_icon"; private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc"; private static final String RULE_ATT_DELETION_INSTANT = "deletionInstant"; + private static final String RULE_ATT_DISABLED_ORIGIN = "disabledOrigin"; private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale"; private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY = @@ -1170,6 +1168,10 @@ public class ZenModeConfig implements Parcelable { if (deletionInstant != null) { rt.deletionInstant = Instant.ofEpochMilli(deletionInstant); } + if (Flags.modesUi()) { + rt.disabledOrigin = safeInt(parser, RULE_ATT_DISABLED_ORIGIN, + UPDATE_ORIGIN_UNKNOWN); + } } return rt; } @@ -1224,6 +1226,9 @@ public class ZenModeConfig implements Parcelable { out.attributeLong(null, RULE_ATT_DELETION_INSTANT, rule.deletionInstant.toEpochMilli()); } + if (Flags.modesUi()) { + out.attributeInt(null, RULE_ATT_DISABLED_ORIGIN, rule.disabledOrigin); + } } } @@ -2514,6 +2519,8 @@ public class ZenModeConfig implements Parcelable { @ZenPolicy.ModifiableField public int zenPolicyUserModifiedFields; @ZenDeviceEffects.ModifiableField public int zenDeviceEffectsUserModifiedFields; @Nullable public Instant deletionInstant; // Only set on deleted rules. + @FlaggedApi(Flags.FLAG_MODES_UI) + @ConfigChangeOrigin public int disabledOrigin = UPDATE_ORIGIN_UNKNOWN; public ZenRule() { } @@ -2552,6 +2559,9 @@ public class ZenModeConfig implements Parcelable { if (source.readInt() == 1) { deletionInstant = Instant.ofEpochMilli(source.readLong()); } + if (Flags.modesUi()) { + disabledOrigin = source.readInt(); + } } } @@ -2626,6 +2636,9 @@ public class ZenModeConfig implements Parcelable { } else { dest.writeInt(0); } + if (Flags.modesUi()) { + dest.writeInt(disabledOrigin); + } } } @@ -2671,6 +2684,9 @@ public class ZenModeConfig implements Parcelable { if (deletionInstant != null) { sb.append(",deletionInstant=").append(deletionInstant); } + if (Flags.modesUi()) { + sb.append(",disabledOrigin=").append(disabledOrigin); + } } return sb.append(']').toString(); @@ -2724,7 +2740,7 @@ public class ZenModeConfig implements Parcelable { && other.modified == modified; if (Flags.modesApi()) { - return finalEquals + finalEquals = finalEquals && Objects.equals(other.zenDeviceEffects, zenDeviceEffects) && other.allowManualInvocation == allowManualInvocation && Objects.equals(other.iconResName, iconResName) @@ -2735,6 +2751,11 @@ public class ZenModeConfig implements Parcelable { && other.zenDeviceEffectsUserModifiedFields == zenDeviceEffectsUserModifiedFields && Objects.equals(other.deletionInstant, deletionInstant); + + if (Flags.modesUi()) { + finalEquals = finalEquals + && other.disabledOrigin == disabledOrigin; + } } return finalEquals; @@ -2743,11 +2764,21 @@ public class ZenModeConfig implements Parcelable { @Override public int hashCode() { if (Flags.modesApi()) { - return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, - component, configurationActivity, pkg, id, enabler, zenPolicy, - zenDeviceEffects, modified, allowManualInvocation, iconResName, - triggerDescription, type, userModifiedFields, zenPolicyUserModifiedFields, - zenDeviceEffectsUserModifiedFields, deletionInstant); + if (Flags.modesUi()) { + return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, + component, configurationActivity, pkg, id, enabler, zenPolicy, + zenDeviceEffects, modified, allowManualInvocation, iconResName, + triggerDescription, type, userModifiedFields, + zenPolicyUserModifiedFields, + zenDeviceEffectsUserModifiedFields, deletionInstant, disabledOrigin); + } else { + return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, + component, configurationActivity, pkg, id, enabler, zenPolicy, + zenDeviceEffects, modified, allowManualInvocation, iconResName, + triggerDescription, type, userModifiedFields, + zenPolicyUserModifiedFields, + zenDeviceEffectsUserModifiedFields, deletionInstant); + } } return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, modified); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 9bf244e1af1c..81891ce91e04 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -47,7 +47,7 @@ class DesktopModeTaskRepository { val visibleTasks: ArraySet<Int> = ArraySet(), val minimizedTasks: ArraySet<Int> = ArraySet(), // Tasks that are closing, but are still visible - // TODO(b/332682201): Remove when the repository state is updated via TransitionObserver + // TODO(b/332682201): Remove when the repository state is updated via TransitionObserver val closingTasks: ArraySet<Int> = ArraySet(), // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0). val freeformTasksInZOrder: ArrayList<Int> = ArrayList(), @@ -234,17 +234,16 @@ class DesktopModeTaskRepository { } /** - * Check if a task with the given [taskId] is the only active, non-closing, not-minimized task + * Check if a task with the given [taskId] is the only visible, non-closing, not-minimized task * on its display */ - fun isOnlyActiveNonClosingTask(taskId: Int): Boolean { - return displayData.valueIterator().asSequence().any { data -> - data.activeTasks + fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean = + displayData.valueIterator().asSequence().any { data -> + data.visibleTasks .subtract(data.closingTasks) .subtract(data.minimizedTasks) .singleOrNull() == taskId } - } /** Get a set of the active tasks for given [displayId] */ fun getActiveTasks(displayId: Int): ArraySet<Int> { 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 d28cda33a569..14ae3a7b9b27 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 @@ -40,7 +40,6 @@ import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_NONE import android.view.WindowManager.TRANSIT_OPEN -import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.RemoteTransition import android.window.TransitionInfo @@ -76,6 +75,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.shared.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE import com.android.wm.shell.shared.DesktopModeStatus.useDesktopOverrideDensity +import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.splitscreen.SplitScreenController @@ -446,7 +446,7 @@ class DesktopTasksController( * @param taskId task id of the window that's being closed */ fun onDesktopWindowClose(wct: WindowContainerTransaction, displayId: Int, taskId: Int) { - if (desktopModeTaskRepository.isOnlyActiveNonClosingTask(taskId)) { + if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskId)) { removeWallpaperActivity(wct) } if (!desktopModeTaskRepository.addClosingTask(displayId, taskId)) { @@ -879,8 +879,8 @@ class DesktopTasksController( reason = "recents animation is running" false } - // Handle back navigation for the last window if wallpaper available - shouldHandleBackNavigation(request) -> true + // Handle task closing for the last window if wallpaper is available + shouldHandleTaskClosing(request) -> true // Only handle open or to front transitions request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> { reason = "transition type not handled (${request.type})" @@ -918,7 +918,8 @@ class DesktopTasksController( val result = triggerTask?.let { task -> when { - request.type == TRANSIT_TO_BACK -> handleBackNavigation(task) + // Check if the closing task needs to be handled + TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task) // Check if the task has a top transparent activity shouldLaunchAsModal(task) -> handleIncompatibleTaskLaunch(task) // Check if the task has a top systemUI activity @@ -960,9 +961,10 @@ class DesktopTasksController( private fun shouldLaunchAsModal(task: TaskInfo) = Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task) - private fun shouldHandleBackNavigation(request: TransitionRequestInfo): Boolean { + private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean { return Flags.enableDesktopWindowingWallpaperActivity() && - request.type == TRANSIT_TO_BACK + TransitionUtil.isClosingType(request.type) && + request.triggerTask != null } private fun handleFreeformTaskLaunch( @@ -1029,10 +1031,10 @@ class DesktopTasksController( return WindowContainerTransaction().also { wct -> addMoveToFullscreenChanges(wct, task) } } - /** Handle back navigation by removing wallpaper activity if it's the last active task */ - private fun handleBackNavigation(task: RunningTaskInfo): WindowContainerTransaction? { + /** Handle task closing by removing wallpaper activity if it's the last active task */ + private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? { val wct = if ( - desktopModeTaskRepository.isOnlyActiveNonClosingTask(task.taskId) && + desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId) && desktopModeTaskRepository.wallpaperActivityToken != null ) { // Remove wallpaper activity when the last active task is removed diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index b88afb9e7197..b48aee5ccd5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -122,10 +122,6 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, mDesktopModeTaskRepository.ifPresent(repository -> { repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId); repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId); - if (repository.removeClosingTask(taskInfo.taskId)) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, - "Removing closing freeform task: #%d", taskInfo.taskId); - } if (repository.removeActiveTask(taskInfo.taskId)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Removing active freeform task: #%d", taskInfo.taskId); diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index 13f95ccea640..92be4f9f0374 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -46,6 +46,7 @@ android_test { "androidx.dynamicanimation_dynamicanimation", "dagger2", "frameworks-base-testutils", + "kotlin-test", "kotlinx-coroutines-android", "kotlinx-coroutines-core", "mockito-kotlin2", diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index 193d61437b2b..6612aee0cd12 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -119,86 +119,91 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test - fun isOnlyActiveNonClosingTask_noActiveNonClosingTasks() { - // Not an active task - assertThat(repo.isOnlyActiveNonClosingTask(1)).isFalse() + fun isOnlyVisibleNonClosingTask_noTasks() { + // No visible tasks + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() assertThat(repo.isClosingTask(1)).isFalse() } @Test - fun isOnlyActiveNonClosingTask_singleActiveNonClosingTask() { - repo.addActiveTask(DEFAULT_DISPLAY, 1) - // The only active task - assertThat(repo.isActiveTask(1)).isTrue() + fun isOnlyVisibleNonClosingTask_singleVisibleNonClosingTask() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + + // The only visible task + assertThat(repo.isVisibleTask(1)).isTrue() assertThat(repo.isClosingTask(1)).isFalse() - assertThat(repo.isOnlyActiveNonClosingTask(1)).isTrue() - // Not an active task - assertThat(repo.isActiveTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isTrue() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() assertThat(repo.isClosingTask(99)).isFalse() - assertThat(repo.isOnlyActiveNonClosingTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() } @Test - fun isOnlyActiveNonClosingTask_singleActiveClosingTask() { - repo.addActiveTask(DEFAULT_DISPLAY, 1) + fun isOnlyVisibleNonClosingTask_singleVisibleClosingTask() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) repo.addClosingTask(DEFAULT_DISPLAY, 1) - // The active task that's closing - assertThat(repo.isActiveTask(1)).isTrue() + + // A visible task that's closing + assertThat(repo.isVisibleTask(1)).isTrue() assertThat(repo.isClosingTask(1)).isTrue() - assertThat(repo.isOnlyActiveNonClosingTask(1)).isFalse() - // Not an active task - assertThat(repo.isActiveTask(99)).isFalse() - assertThat(repo.isOnlyActiveNonClosingTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() } @Test - fun isOnlyActiveNonClosingTask_singleActiveMinimizedTask() { - repo.addActiveTask(DEFAULT_DISPLAY, 1) + fun isOnlyVisibleNonClosingTask_singleVisibleMinimizedTask() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) repo.minimizeTask(DEFAULT_DISPLAY, 1) - // The active task that's closing - assertThat(repo.isActiveTask(1)).isTrue() + + // The visible task that's closing + assertThat(repo.isVisibleTask(1)).isTrue() assertThat(repo.isMinimizedTask(1)).isTrue() - assertThat(repo.isOnlyActiveNonClosingTask(1)).isFalse() - // Not an active task - assertThat(repo.isActiveTask(99)).isFalse() - assertThat(repo.isOnlyActiveNonClosingTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() } @Test - fun isOnlyActiveNonClosingTask_multipleActiveNonClosingTasks() { - repo.addActiveTask(DEFAULT_DISPLAY, 1) - repo.addActiveTask(DEFAULT_DISPLAY, 2) + fun isOnlyVisibleNonClosingTask_multipleVisibleNonClosingTasks() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) + // Not the only task - assertThat(repo.isActiveTask(1)).isTrue() + assertThat(repo.isVisibleTask(1)).isTrue() assertThat(repo.isClosingTask(1)).isFalse() - assertThat(repo.isOnlyActiveNonClosingTask(1)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() // Not the only task - assertThat(repo.isActiveTask(2)).isTrue() + assertThat(repo.isVisibleTask(2)).isTrue() assertThat(repo.isClosingTask(2)).isFalse() - assertThat(repo.isOnlyActiveNonClosingTask(2)).isFalse() - // Not an active task - assertThat(repo.isActiveTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(2)).isFalse() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() assertThat(repo.isClosingTask(99)).isFalse() - assertThat(repo.isOnlyActiveNonClosingTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() } @Test - fun isOnlyActiveNonClosingTask_multipleDisplays() { - repo.addActiveTask(DEFAULT_DISPLAY, 1) - repo.addActiveTask(DEFAULT_DISPLAY, 2) - repo.addActiveTask(SECOND_DISPLAY, 3) + fun isOnlyVisibleNonClosingTask_multipleDisplays() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) + repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 3, visible = true) + // Not the only task on DEFAULT_DISPLAY - assertThat(repo.isActiveTask(1)).isTrue() - assertThat(repo.isOnlyActiveNonClosingTask(1)).isFalse() + assertThat(repo.isVisibleTask(1)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() // Not the only task on DEFAULT_DISPLAY - assertThat(repo.isActiveTask(2)).isTrue() - assertThat(repo.isOnlyActiveNonClosingTask(2)).isFalse() - // The only active task on SECOND_DISPLAY - assertThat(repo.isActiveTask(3)).isTrue() - assertThat(repo.isOnlyActiveNonClosingTask(3)).isTrue() - // Not an active task - assertThat(repo.isActiveTask(99)).isFalse() - assertThat(repo.isOnlyActiveNonClosingTask(99)).isFalse() + assertThat(repo.isVisibleTask(2)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(2)).isFalse() + // The only visible task on SECOND_DISPLAY + assertThat(repo.isVisibleTask(3)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(3)).isTrue() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() } @Test 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 392161fd8e66..a1a18a9b7c9d 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 @@ -45,6 +45,7 @@ import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE +import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT @@ -102,6 +103,8 @@ import com.google.common.truth.Truth.assertWithMessage import java.util.Optional import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue +import kotlin.test.assertNotNull +import kotlin.test.assertNull import org.junit.After import org.junit.Assume.assumeTrue import org.junit.Before @@ -1254,72 +1257,205 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() { + val task = setUpFreeformTask() + + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_singleActiveTask_noToken() { + fun handleRequest_backTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() { val task = setUpFreeformTask() + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - // Doesn't handle request - assertThat(result).isNull() + + assertNull(result, "Should not handle request") } @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_singleActiveTask_hasToken_desktopWallpaperDisabled() { - desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() - + fun handleRequest_backTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() { val task = setUpFreeformTask() + + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - // Doesn't handle request - assertThat(result).isNull() + + assertNull(result, "Should not handle request") } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_singleActiveTask_hasToken_desktopWallpaperEnabled() { + fun handleRequest_backTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() { + val task = setUpFreeformTask() val wallpaperToken = MockToken().token() - desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken - val task = setUpFreeformTask() + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - assertThat(result).isNotNull() - // Creates remove wallpaper transaction - result!!.assertRemoveAt(index = 0, wallpaperToken) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_multipleActiveTasks() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_multipleActiveTasksFlagDisabled_doesNotHandle() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_multipleActiveTasksFlagEnabled_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() + + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) - // Doesn't handle request - assertThat(result).isNull() + + assertNull(result, "Should not handle request") } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_multipleActiveTasks_singleNonClosing() { - desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + fun handleRequest_backTransition_multipleActiveTasksSingleNonClosing_handlesRequest() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() - val task1 = setUpFreeformTask() - setUpFreeformTask() + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) - // Doesn't handle request - assertThat(result).isNull() + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_multipleActiveTasks_singleNonMinimized() { + fun handleRequest_backTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() { + val task = setUpFreeformTask() + + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() { + val task = setUpFreeformTask() + + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() { + val task = setUpFreeformTask() + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleActiveTasksFlagDisabled_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) - // Doesn't handle request - assertThat(result).isNull() + + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleActiveTasksFlagEnabled_doesNotHandle() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleActiveTasksSingleNonClosing_handlesRequest() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) } @Test @@ -1693,6 +1829,7 @@ class DesktopTasksControllerTest : ShellTestCase() { task.topActivityInfo = activityInfo whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) desktopModeTaskRepository.addActiveTask(displayId, task.taskId) + desktopModeTaskRepository.updateVisibleFreeformTasks(displayId, task.taskId, visible = true) desktopModeTaskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId) runningTasks.add(task) return task diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index 4587ea6dbdc8..c5ba02d0773a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -528,6 +528,30 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { } @Test + fun userChange_isFingerprintEnrolledAndEnabledUpdated() = + testScope.runTest { + createBiometricSettingsRepository() + whenever(authController.isFingerprintEnrolled(ANOTHER_USER_ID)).thenReturn(false) + whenever(authController.isFingerprintEnrolled(PRIMARY_USER_ID)).thenReturn(true) + + verify(biometricManager) + .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture()) + val isFingerprintEnrolledAndEnabled = + collectLastValue(underTest.isFingerprintEnrolledAndEnabled) + biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID) + runCurrent() + userRepository.setSelectedUserInfo(ANOTHER_USER) + runCurrent() + assertThat(isFingerprintEnrolledAndEnabled()).isFalse() + + biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID) + runCurrent() + userRepository.setSelectedUserInfo(PRIMARY_USER) + runCurrent() + assertThat(isFingerprintEnrolledAndEnabled()).isTrue() + } + + @Test fun userChange_biometricEnabledChange_handlesRaceCondition() = testScope.runTest { createBiometricSettingsRepository() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index 882f2315f6e8..dd3e6190ceaf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -188,35 +188,33 @@ constructor( ) private val isFingerprintEnrolled: Flow<Boolean> = - selectedUserId - .flatMapLatest { currentUserId -> - conflatedCallbackFlow { - val callback = - object : AuthController.Callback { - override fun onEnrollmentsChanged( - sensorBiometricType: BiometricType, - userId: Int, - hasEnrollments: Boolean - ) { - if (sensorBiometricType.isFingerprint && userId == currentUserId) { - trySendWithFailureLogging( - hasEnrollments, - TAG, - "update fpEnrollment" - ) - } + selectedUserId.flatMapLatest { currentUserId -> + conflatedCallbackFlow { + val callback = + object : AuthController.Callback { + override fun onEnrollmentsChanged( + sensorBiometricType: BiometricType, + userId: Int, + hasEnrollments: Boolean + ) { + if (sensorBiometricType.isFingerprint && userId == currentUserId) { + trySendWithFailureLogging( + hasEnrollments, + TAG, + "update fpEnrollment" + ) } } - authController.addCallback(callback) - awaitClose { authController.removeCallback(callback) } - } + } + authController.addCallback(callback) + trySendWithFailureLogging( + authController.isFingerprintEnrolled(currentUserId), + TAG, + "Initial value of fingerprint enrollment" + ) + awaitClose { authController.removeCallback(callback) } } - .stateIn( - scope, - started = SharingStarted.Eagerly, - initialValue = - authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id) - ) + } private val isFaceEnrolled: Flow<Boolean> = selectedUserId.flatMapLatest { selectedUserId: Int -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 04a413acdf0b..240953d6bfcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -291,8 +291,9 @@ public class NotificationMediaManager implements Dumpable { } private void updateMediaMetaData(MediaListener callback) { - callback.onPrimaryMetadataOrStateChanged(mMediaMetadata, - getMediaControllerPlaybackState(mMediaController)); + int playbackState = getMediaControllerPlaybackState(mMediaController); + mHandler.post( + () -> callback.onPrimaryMetadataOrStateChanged(mMediaMetadata, playbackState)); } public void removeCallback(MediaListener callback) { @@ -437,9 +438,11 @@ public class NotificationMediaManager implements Dumpable { private void updateMediaMetaData(List<MediaListener> callbacks) { @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController); - for (int i = 0; i < callbacks.size(); i++) { - callbacks.get(i).onPrimaryMetadataOrStateChanged(mMediaMetadata, state); - } + mHandler.post(() -> { + for (int i = 0; i < callbacks.size(); i++) { + callbacks.get(i).onPrimaryMetadataOrStateChanged(mMediaMetadata, state); + } + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 456c321a0c14..2cbb6aea53a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -64,6 +64,7 @@ public class AmbientState implements Dumpable { * Used to read bouncer states. */ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private float mStackTop; private float mStackCutoff; private int mScrollY; private float mOverScrollTopAmount; @@ -186,6 +187,7 @@ public class AmbientState implements Dumpable { * @param stackY Distance of top of notifications panel from top of screen. */ public void setStackY(float stackY) { + SceneContainerFlag.assertInLegacyMode(); mStackY = stackY; } @@ -193,6 +195,7 @@ public class AmbientState implements Dumpable { * @return Distance of top of notifications panel from top of screen. */ public float getStackY() { + SceneContainerFlag.assertInLegacyMode(); return mStackY; } @@ -348,6 +351,18 @@ public class AmbientState implements Dumpable { return mZDistanceBetweenElements; } + /** Y coordinate in view pixels of the top of the notification stack */ + public float getStackTop() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f; + return mStackTop; + } + + /** @see #getStackTop() */ + public void setStackTop(float mStackTop) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + this.mStackTop = mStackTop; + } + /** * Y coordinate in view pixels above which the bottom of the notification stack / shelf / footer * must be. @@ -769,6 +784,8 @@ public class AmbientState implements Dumpable { @Override public void dump(PrintWriter pw, String[] args) { + pw.println("mStackTop=" + mStackTop); + pw.println("mStackCutoff" + mStackCutoff); pw.println("mTopPadding=" + mTopPadding); pw.println("mStackTopMargin=" + mStackTopMargin); pw.println("mStackTranslation=" + mStackTranslation); 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 38bcc0b98e59..1eee466f0d56 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 @@ -834,7 +834,7 @@ public class NotificationStackScrollLayout drawDebugInfo(canvas, y, Color.RED, /* label= */ "y = " + y); if (SceneContainerFlag.isEnabled()) { - y = (int) mScrollViewFields.getStackTop(); + y = (int) mAmbientState.getStackTop(); drawDebugInfo(canvas, y, Color.RED, /* label= */ "getStackTop() = " + y); y = (int) mAmbientState.getStackCutoff(); @@ -1216,7 +1216,7 @@ public class NotificationStackScrollLayout @Override public void setStackTop(float stackTop) { - mScrollViewFields.setStackTop(stackTop); + mAmbientState.setStackTop(stackTop); // TODO(b/332574413): replace the following with using stackTop updateTopPadding(stackTop, isAddOrRemoveAnimationPending()); } @@ -1426,11 +1426,7 @@ public class NotificationStackScrollLayout if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) { fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction); } - // TODO(b/322228881): Clean up scene container vs legacy behavior in NSSL - if (SceneContainerFlag.isEnabled()) { - // stackY should be driven by scene container, not NSSL - mAmbientState.setStackY(getTopPadding()); - } else { + if (!SceneContainerFlag.isEnabled()) { final float stackY = MathUtils.lerp(0, endTopPosition, fraction); mAmbientState.setStackY(stackY); } @@ -3726,7 +3722,7 @@ public class NotificationStackScrollLayout protected boolean isInsideQsHeader(MotionEvent ev) { if (SceneContainerFlag.isEnabled()) { - return ev.getY() < mScrollViewFields.getStackTop(); + return ev.getY() < mAmbientState.getStackTop(); } mQsHeader.getBoundsOnScreen(mQsHeaderBound); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt index 2e86ad97a5f4..97ec39192cc8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -32,8 +32,6 @@ import java.util.function.Consumer class ScrollViewFields { /** Used to produce the clipping path */ var scrimClippingShape: ShadeScrimShape? = null - /** Y coordinate in view pixels of the top of the notification stack */ - var stackTop: Float = 0f /** Y coordinate in view pixels of the top of the HUN */ var headsUpTop: Float = 0f /** Whether the notifications are scrolled all the way to the top (i.e. when freshly opened) */ @@ -76,7 +74,6 @@ class ScrollViewFields { fun dump(pw: IndentingPrintWriter) { pw.printSection("StackViewStates") { pw.println("scrimClippingShape", scrimClippingShape) - pw.println("stackTop", stackTop) pw.println("headsUpTop", headsUpTop) pw.println("isScrolledToTop", isScrolledToTop) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index f9efc07c67fb..ca74c0e6f9e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -29,6 +29,7 @@ import com.android.internal.policy.SystemBarUtils; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; @@ -332,8 +333,10 @@ public class StackScrollAlgorithm { private void updateClipping(StackScrollAlgorithmState algorithmState, AmbientState ambientState) { - float drawStart = ambientState.isOnKeyguard() ? 0 + float stackTop = SceneContainerFlag.isEnabled() ? ambientState.getStackTop() : ambientState.getStackY() - ambientState.getScrollY(); + float drawStart = ambientState.isOnKeyguard() ? 0 + : stackTop; float clipStart = 0; int childCount = algorithmState.visibleChildren.size(); boolean firstHeadsUp = true; @@ -641,7 +644,10 @@ public class StackScrollAlgorithm { // Incoming views have yTranslation=0 by default. viewState.setYTranslation(algorithmState.mCurrentYPosition); - float viewEnd = viewState.getYTranslation() + viewState.height + ambientState.getStackY(); + float stackTop = SceneContainerFlag.isEnabled() + ? ambientState.getStackTop() + : ambientState.getStackY(); + float viewEnd = stackTop + viewState.getYTranslation() + viewState.height; maybeUpdateHeadsUpIsVisible(viewState, ambientState.isShadeExpanded(), view.mustStayOnScreen(), /* topVisible= */ viewState.getYTranslation() >= mNotificationScrimPadding, @@ -681,7 +687,9 @@ public class StackScrollAlgorithm { } } else { if (view instanceof EmptyShadeView) { - float fullHeight = ambientState.getLayoutMaxHeight() + mMarginBottom + float fullHeight = SceneContainerFlag.isEnabled() + ? ambientState.getStackCutoff() - ambientState.getStackTop() + : ambientState.getLayoutMaxHeight() + mMarginBottom - ambientState.getStackY(); viewState.setYTranslation((fullHeight - getMaxAllowedChildHeight(view)) / 2f); } else if (view != ambientState.getTrackedHeadsUpRow()) { @@ -726,7 +734,7 @@ public class StackScrollAlgorithm { + mPaddingBetweenElements; setLocation(view.getViewState(), algorithmState.mCurrentYPosition, i); - viewState.setYTranslation(viewState.getYTranslation() + ambientState.getStackY()); + viewState.setYTranslation(viewState.getYTranslation() + stackTop); } @VisibleForTesting @@ -1002,8 +1010,11 @@ public class StackScrollAlgorithm { // Animate pinned HUN bottom corners to and from original roundness. final float originalCornerRadius = row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius); + final float stackTop = SceneContainerFlag.isEnabled() + ? ambientState.getStackTop() + : ambientState.getStackY(); final float bottomValue = computeCornerRoundnessForPinnedHun(mHostView.getHeight(), - ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius); + stackTop, getMaxAllowedChildHeight(row), originalCornerRadius); row.requestBottomRoundness(bottomValue, STACK_SCROLL_ALGO); row.addOnDetachResetRoundness(STACK_SCROLL_ALGO); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 12f3ef3cf553..967f95eb0ef6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -214,6 +214,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + @DisableSceneContainer // TODO(b/332574413) cover stack bounds integration with tests public void testUpdateStackHeight_qsExpansionGreaterThanZero() { final float expansionFraction = 0.2f; final float overExpansion = 50f; @@ -891,6 +892,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + @DisableSceneContainer // NSSL has no more scroll logic when SceneContainer is on public void testNormalShade_hasNoTopOverscroll() { mTestableResources .addOverride(R.bool.config_use_split_notification_shade, /* value= */ false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index d28e0c17c5c9..8401a19f075d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -11,6 +11,8 @@ import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerPr import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation.getContentAlpha import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.res.R @@ -66,7 +68,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { EmptyShadeView(context, /* attrs= */ null).apply { layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100) } - private val footerView = FooterView(context, /*attrs=*/ null) + private val footerView = FooterView(context, /* attrs= */ null) @OptIn(ExperimentalCoroutinesApi::class) private val ambientState = AmbientState( @@ -126,6 +128,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer // TODO(b/332574413) cover hun bounds integration with tests fun resetViewStates_defaultHunWhenShadeIsOpening_yTranslationIsInset() { whenever(notificationRow.isPinned).thenReturn(true) whenever(notificationRow.isHeadsUp).thenReturn(true) @@ -168,6 +171,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer // TODO(b/332574413) cover hun bounds integration with tests @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_defaultHun_showingQS_newHeadsUpAnim_hunTranslatedToMax() { // Given: the shade is open and scrolled to the bottom to show the QuickSettings @@ -184,6 +188,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer // TODO(b/332574413) cover hun bounds integration with tests @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_hunAnimatingAway_showingQS_newHeadsUpAnim_hunTranslatedToBottomOfScreen() { // Given: the shade is open and scrolled to the bottom to show the QuickSettings @@ -272,6 +277,27 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun resetViewStates_emptyShadeView_isCenteredVertically_withSceneContainer() { + stackScrollAlgorithm.initView(context) + hostView.removeAllViews() + hostView.addView(emptyShadeView) + ambientState.layoutMaxHeight = maxPanelHeight.toInt() + + val stackTop = 200f + val stackBottom = 2000f + val stackHeight = stackBottom - stackTop + ambientState.stackTop = stackTop + ambientState.stackCutoff = stackBottom + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + val centeredY = stackTop + stackHeight / 2f - emptyShadeView.height / 2f + assertThat(emptyShadeView.viewState.yTranslation).isEqualTo(centeredY) + } + + @Test + @DisableSceneContainer fun resetViewStates_emptyShadeView_isCenteredVertically() { stackScrollAlgorithm.initView(context) hostView.removeAllViews() @@ -1157,6 +1183,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { assertFalse(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState)) } + // endregion private fun createHunViewMock( diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 2e7295eca1a2..c078409f468c 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -34,6 +34,7 @@ import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT; import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER; import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP; import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN; import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; @@ -1143,6 +1144,17 @@ public class ZenModeHelper { modified = true; } + if (Flags.modesUi()) { + if (!azr.isEnabled() && (isNew || rule.enabled)) { + // Creating a rule as disabled, or disabling a previously enabled rule. + // Record whodunit. + rule.disabledOrigin = origin; + } else if (azr.isEnabled()) { + // Enabling or previously enabled. Clear disabler. + rule.disabledOrigin = UPDATE_ORIGIN_UNKNOWN; + } + } + if (!Objects.equals(rule.conditionId, azr.getConditionId())) { rule.conditionId = azr.getConditionId(); modified = true; diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationCapability.java b/services/core/java/com/android/server/wm/AppCompatOrientationCapability.java index fbe90a2cecda..10f3e833f78a 100644 --- a/services/core/java/com/android/server/wm/AppCompatOrientationCapability.java +++ b/services/core/java/com/android/server/wm/AppCompatOrientationCapability.java @@ -38,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.utils.OptPropFactory; import java.util.function.BooleanSupplier; +import java.util.function.LongSupplier; class AppCompatOrientationCapability { @@ -58,7 +59,8 @@ class AppCompatOrientationCapability { @NonNull LetterboxConfiguration letterboxConfiguration, @NonNull ActivityRecord activityRecord) { mActivityRecord = activityRecord; - mOrientationCapabilityState = new OrientationCapabilityState(mActivityRecord); + mOrientationCapabilityState = new OrientationCapabilityState(mActivityRecord, + System::currentTimeMillis); final BooleanSupplier isPolicyForIgnoringRequestedOrientationEnabled = asLazy( letterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled); mIgnoreRequestedOrientationOptProp = optPropBuilder.create( @@ -214,8 +216,12 @@ class AppCompatOrientationCapability { private long mTimeMsLastSetOrientationRequest = 0; // Counter for ActivityRecord#setRequestedOrientation private int mSetOrientationRequestCounter = 0; + @VisibleForTesting + LongSupplier mCurrentTimeMillisSupplier; - OrientationCapabilityState(@NonNull ActivityRecord activityRecord) { + OrientationCapabilityState(@NonNull ActivityRecord activityRecord, + @NonNull LongSupplier currentTimeMillisSupplier) { + mCurrentTimeMillisSupplier = currentTimeMillisSupplier; mIsOverrideToNosensorOrientationEnabled = activityRecord.info.isChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR); mIsOverrideToPortraitOrientationEnabled = @@ -238,7 +244,7 @@ class AppCompatOrientationCapability { * Updates the orientation request counter using a specific timeout. */ void updateOrientationRequestLoopState() { - final long currTimeMs = System.currentTimeMillis(); + final long currTimeMs = mCurrentTimeMillisSupplier.getAsLong(); final long elapsedTime = currTimeMs - mTimeMsLastSetOrientationRequest; if (elapsedTime < SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS) { mSetOrientationRequestCounter++; diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 7e61023e28fc..a029f38de128 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2544,9 +2544,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (wc.asWindowState() != null) continue; final ChangeInfo changeInfo = changes.get(wc); - - // Reject no-ops - if (!changeInfo.hasChanged()) { + // Reject no-ops, unless wallpaper + if (!changeInfo.hasChanged() && wc.asWallpaperToken() == null) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Rejecting as no-op: %s", wc); continue; diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 3e43f5a2da66..a3e03029c095 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -764,10 +764,10 @@ class WallpaperController { void collectTopWallpapers(Transition transition) { if (mFindResults.hasTopShowWhenLockedWallpaper()) { - transition.collect(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper); + transition.collect(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper.mToken); } if (mFindResults.hasTopHideWhenLockedWallpaper()) { - transition.collect(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper); + transition.collect(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper.mToken); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index 9352c1287ee1..f5ab95c1ab4c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -511,6 +511,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); + if (Flags.modesUi()) { + rule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_USER; + } Parcel parcel = Parcel.obtain(); rule.writeToParcel(parcel, 0); @@ -540,6 +543,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.triggerDescription, parceled.triggerDescription); assertEquals(rule.zenPolicy, parceled.zenPolicy); assertEquals(rule.deletionInstant, parceled.deletionInstant); + if (Flags.modesUi()) { + assertEquals(rule.disabledOrigin, parceled.disabledOrigin); + } assertEquals(rule, parceled); assertEquals(rule.hashCode(), parceled.hashCode()); @@ -620,6 +626,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); + if (Flags.modesUi()) { + rule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_APP; + } ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeRuleXml(rule, baos); @@ -653,6 +662,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.triggerDescription, fromXml.triggerDescription); assertEquals(rule.iconResName, fromXml.iconResName); assertEquals(rule.deletionInstant, fromXml.deletionInstant); + if (Flags.modesUi()) { + assertEquals(rule.disabledOrigin, fromXml.disabledOrigin); + } } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java index 26a13cb47563..57587f7dc38a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java @@ -16,6 +16,7 @@ package com.android.server.notification; +import static android.app.Flags.FLAG_MODES_API; import static android.app.Flags.FLAG_MODES_UI; import static com.google.common.truth.Truth.assertThat; @@ -32,6 +33,7 @@ import android.app.Flags; import android.app.NotificationManager; import android.content.ComponentName; import android.net.Uri; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; @@ -79,16 +81,17 @@ public class ZenModeDiffTest extends UiServiceTestCase { : Set.of("version", "manualRule", "automaticRules"); // Differences for flagged fields are only generated if the flag is enabled. - // "Metadata" fields (userModifiedFields & co, deletionInstant) are not compared. + // "Metadata" fields (userModifiedFields, deletionInstant, disabledOrigin) are not compared. private static final Set<String> ZEN_RULE_EXEMPT_FIELDS = android.app.Flags.modesApi() ? Set.of("userModifiedFields", "zenPolicyUserModifiedFields", - "zenDeviceEffectsUserModifiedFields", "deletionInstant") + "zenDeviceEffectsUserModifiedFields", "deletionInstant", + "disabledOrigin") : Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION, RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL, RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, "userModifiedFields", "zenPolicyUserModifiedFields", "zenDeviceEffectsUserModifiedFields", - "deletionInstant"); + "deletionInstant", "disabledOrigin"); // allowPriorityChannels is flagged by android.app.modes_api public static final Set<String> ZEN_MODE_CONFIG_FLAGGED_FIELDS = @@ -201,8 +204,8 @@ public class ZenModeDiffTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MODES_API) public void testConfigDiff_fieldDiffs_flagOn() throws Exception { - mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); // these two start the same ZenModeConfig c1 = new ZenModeConfig(); ZenModeConfig c2 = new ZenModeConfig(); 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 40b0d78f0640..7bb633e6e1e0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -6226,6 +6226,101 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(mZenModeHelper.getConfig().manualRule.zenDeviceEffects).isEqualTo(effects); } + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void addAutomaticZenRule_startsDisabled_recordsDisabledOrigin() { + AutomaticZenRule startsDisabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setOwner(new ComponentName(mPkg, "SomeProvider")) + .setEnabled(false) + .build(); + + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsDisabled, UPDATE_ORIGIN_APP, + "new", CUSTOM_PKG_UID); + + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_APP); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void updateAutomaticZenRule_disabling_recordsDisabledOrigin() { + AutomaticZenRule startsEnabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setOwner(new ComponentName(mPkg, "SomeProvider")) + .setEnabled(true) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled, UPDATE_ORIGIN_APP, + "new", CUSTOM_PKG_UID); + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_UNKNOWN); + + AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled) + .setEnabled(false) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, UPDATE_ORIGIN_USER, "off", + Process.SYSTEM_UID); + + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_USER); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void updateAutomaticZenRule_keepingDisabled_preservesPreviousDisabledOrigin() { + AutomaticZenRule startsEnabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setOwner(new ComponentName(mPkg, "SomeProvider")) + .setEnabled(true) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled, UPDATE_ORIGIN_APP, + "new", CUSTOM_PKG_UID); + AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled) + .setEnabled(false) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, UPDATE_ORIGIN_USER, "off", + Process.SYSTEM_UID); + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_USER); + + // Now update it again, for an unrelated reason with a different origin. + AutomaticZenRule nowRenamed = new AutomaticZenRule.Builder(nowDisabled) + .setName("Fancy pants rule") + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, nowRenamed, UPDATE_ORIGIN_APP, "update", + CUSTOM_PKG_UID); + + // Identity of the disabler is preserved. + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_USER); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void updateAutomaticZenRule_enabling_clearsDisabledOrigin() { + AutomaticZenRule startsEnabled = new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setOwner(new ComponentName(mPkg, "SomeProvider")) + .setEnabled(true) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled, UPDATE_ORIGIN_APP, + "new", CUSTOM_PKG_UID); + AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled) + .setEnabled(false) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, UPDATE_ORIGIN_USER, "off", + Process.SYSTEM_UID); + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_USER); + + // Now enable it again + AutomaticZenRule nowEnabled = new AutomaticZenRule.Builder(nowDisabled) + .setEnabled(true) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, nowEnabled, UPDATE_ORIGIN_APP, "on", + CUSTOM_PKG_UID); + + // Identity of the disabler was cleared. + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo( + UPDATE_ORIGIN_UNKNOWN); + } + private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationCapabilityTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationCapabilityTest.java new file mode 100644 index 000000000000..f1cf866df4a5 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationCapabilityTest.java @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.wm; + +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; +import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.AppCompatOrientationCapability.OrientationCapabilityState.MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; +import static com.android.server.wm.AppCompatOrientationCapability.OrientationCapabilityState.SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; + +import android.compat.testing.PlatformCompatChangeRule; +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.platform.test.annotations.Presubmit; + +import androidx.annotation.NonNull; + +import com.android.server.wm.utils.TestComponentStack; + +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; +import java.util.function.IntConsumer; +import java.util.function.LongSupplier; + +/** + * Test class for {@link AppCompatOrientationCapability}. + * <p> + * Build/Install/Run: + * atest WmTests:AppCompatOrientationCapabilityTest + */ +@Presubmit +@RunWith(WindowTestRunner.class) +public class AppCompatOrientationCapabilityTest extends WindowTestsBase { + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareRelaunchingAfterRequestedOrientationChanged(true); + + robot.checkShouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_cameraCompatTreatment_returnsTrue() { + runTestScenario((robot) -> { + robot.prepareIsCameraCompatTreatmentEnabled(true); + robot.prepareIsCameraCompatTreatmentEnabledAtBuildTime(true); + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + + robot.createActivityWithComponentInNewTask(); + robot.prepareRelaunchingAfterRequestedOrientationChanged(false); + robot.prepareIsTreatmentEnabledForTopActivity(true); + + robot.checkShouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + public void testShouldIgnoreRequestedOrientation_overrideDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + + robot.createActivityWithComponent(); + robot.prepareRelaunchingAfterRequestedOrientationChanged(true); + + robot.checkShouldNotIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + public void testShouldIgnoreRequestedOrientation_propertyIsTrue_returnsTrue() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.enableProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION); + + robot.createActivityWithComponent(); + robot.prepareRelaunchingAfterRequestedOrientationChanged(true); + + robot.checkShouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_propertyIsFalseAndOverride_returnsFalse() + throws Exception { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.disableProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION); + + robot.createActivityWithComponent(); + robot.prepareRelaunchingAfterRequestedOrientationChanged(true); + + robot.checkShouldNotIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + @Test + public void testShouldIgnoreOrientationRequestLoop_overrideDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(false); + + robot.checkRequestLoopExtended((i) -> { + robot.checkShouldNotIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ 0); + }); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_propertyIsFalseAndOverride_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.disableProperty( + PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(false); + + robot.checkRequestLoopExtended((i) -> { + robot.checkShouldNotIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ 0); + }); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_isLetterboxed_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(true); + + robot.checkRequestLoopExtended((i) -> { + robot.checkShouldNotIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ i); + }); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_noLoop_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(false); + + robot.checkShouldNotIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ 0); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_timeout_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(false); + + robot.prepareMockedTime(); + robot.checkRequestLoopExtended((i) -> { + robot.checkShouldNotIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ 0); + robot.delay(); + }); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_returnsTrue() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(false); + + robot.checkRequestLoop((i) -> { + robot.checkShouldNotIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ i); + }); + robot.checkShouldIgnoreOrientationLoop(); + robot.checkExpectedLoopCount(/* expectedCount */ MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH}) + public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prepareIsPolicyForIgnoringRequestedOrientationEnabled(true); + robot.createActivityWithComponent(); + robot.prepareIsLetterboxedForFixedOrientationAndAspectRatio(false); + + robot.checkShouldNotIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + }); + } + + /** + * Runs a test scenario providing a Robot. + */ + void runTestScenario(@NonNull Consumer<OrientationCapabilityRobotTest> consumer) { + spyOn(mWm.mLetterboxConfiguration); + final OrientationCapabilityRobotTest robot = + new OrientationCapabilityRobotTest(mWm, mAtm, mSupervisor); + consumer.accept(robot); + } + + private static class OrientationCapabilityRobotTest { + + @NonNull + private final ActivityTaskManagerService mAtm; + @NonNull + private final WindowManagerService mWm; + @NonNull + private final ActivityTaskSupervisor mSupervisor; + @NonNull + private final LetterboxConfiguration mLetterboxConfiguration; + @NonNull + private final TestComponentStack<ActivityRecord> mActivityStack; + @NonNull + private final TestComponentStack<Task> mTaskStack; + @NonNull + private final CurrentTimeMillisSupplierTest mTestCurrentTimeMillisSupplier; + + + OrientationCapabilityRobotTest(@NonNull WindowManagerService wm, + @NonNull ActivityTaskManagerService atm, + @NonNull ActivityTaskSupervisor supervisor) { + mAtm = atm; + mWm = wm; + mSupervisor = supervisor; + mActivityStack = new TestComponentStack<>(); + mTaskStack = new TestComponentStack<>(); + mLetterboxConfiguration = mWm.mLetterboxConfiguration; + mTestCurrentTimeMillisSupplier = new CurrentTimeMillisSupplierTest(); + } + + void prepareRelaunchingAfterRequestedOrientationChanged(boolean enabled) { + getTopOrientationCapability().setRelaunchingAfterRequestedOrientationChanged(enabled); + } + + void prepareIsPolicyForIgnoringRequestedOrientationEnabled(boolean enabled) { + doReturn(enabled).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); + } + + void prepareIsCameraCompatTreatmentEnabled(boolean enabled) { + doReturn(enabled).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(); + } + + void prepareIsCameraCompatTreatmentEnabledAtBuildTime(boolean enabled) { + doReturn(enabled).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabledAtBuildTime(); + } + + void prepareIsTreatmentEnabledForTopActivity(boolean enabled) { + final DisplayRotationCompatPolicy displayPolicy = mActivityStack.top() + .mDisplayContent.mDisplayRotationCompatPolicy; + spyOn(displayPolicy); + doReturn(enabled).when(displayPolicy) + .isTreatmentEnabledForActivity(eq(mActivityStack.top())); + } + + // Useful to reduce timeout during tests + void prepareMockedTime() { + getTopOrientationCapability().mOrientationCapabilityState.mCurrentTimeMillisSupplier = + mTestCurrentTimeMillisSupplier; + } + + void delay() { + mTestCurrentTimeMillisSupplier.delay(SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS); + } + + void enableProperty(@NonNull String propertyName) { + setPropertyValue(propertyName, /* enabled */ true); + } + + void disableProperty(@NonNull String propertyName) { + setPropertyValue(propertyName, /* enabled */ false); + } + + void prepareIsLetterboxedForFixedOrientationAndAspectRatio(boolean enabled) { + spyOn(mActivityStack.top()); + doReturn(enabled).when(mActivityStack.top()) + .isLetterboxedForFixedOrientationAndAspectRatio(); + } + + void createActivityWithComponent() { + createActivityWithComponentInNewTask(/* inNewTask */ mTaskStack.isEmpty()); + } + + void createActivityWithComponentInNewTask() { + createActivityWithComponentInNewTask(/* inNewTask */ true); + } + + private void createActivityWithComponentInNewTask(boolean inNewTask) { + if (inNewTask) { + createNewTask(); + } + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setOnTop(true) + .setTask(mTaskStack.top()) + // Set the component to be that of the test class in order + // to enable compat changes + .setComponent(ComponentName.createRelative(mAtm.mContext, + com.android.server.wm.LetterboxUiControllerTest.class.getName())) + .build(); + mActivityStack.push(activity); + } + + void checkShouldIgnoreRequestedOrientation( + @Configuration.Orientation int expectedOrientation) { + assertTrue(getTopOrientationCapability() + .shouldIgnoreRequestedOrientation(expectedOrientation)); + } + + void checkShouldNotIgnoreRequestedOrientation( + @Configuration.Orientation int expectedOrientation) { + assertFalse(getTopOrientationCapability() + .shouldIgnoreRequestedOrientation(expectedOrientation)); + } + + void checkExpectedLoopCount(int expectedCount) { + assertEquals(expectedCount, getTopOrientationCapability() + .getSetOrientationRequestCounter()); + } + + void checkShouldNotIgnoreOrientationLoop() { + assertFalse(getTopOrientationCapability().shouldIgnoreOrientationRequestLoop()); + } + + void checkShouldIgnoreOrientationLoop() { + assertTrue(getTopOrientationCapability().shouldIgnoreOrientationRequestLoop()); + } + + void checkRequestLoop(IntConsumer consumer) { + for (int i = 0; i < MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { + consumer.accept(i); + } + } + + void checkRequestLoopExtended(IntConsumer consumer) { + for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { + consumer.accept(i); + } + } + + private AppCompatOrientationCapability getTopOrientationCapability() { + return mActivityStack.top().mAppCompatController.getAppCompatCapability() + .getAppCompatOrientationCapability(); + } + + private void createNewTask() { + final DisplayContent displayContent = new TestDisplayContent + .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build(); + final Task newTask = new TaskBuilder(mSupervisor).setDisplay(displayContent).build(); + mTaskStack.push(newTask); + } + + private void setPropertyValue(@NonNull String propertyName, boolean enabled) { + PackageManager.Property property = new PackageManager.Property(propertyName, + /* value */ enabled, /* packageName */ "", + /* className */ ""); + PackageManager pm = mWm.mContext.getPackageManager(); + spyOn(pm); + try { + doReturn(property).when(pm).getProperty(eq(propertyName), anyString()); + } catch (PackageManager.NameNotFoundException e) { + fail(e.getLocalizedMessage()); + } + } + + private static class CurrentTimeMillisSupplierTest implements LongSupplier { + + private long mCurrenTimeMillis = System.currentTimeMillis(); + + @Override + public long getAsLong() { + return mCurrenTimeMillis; + } + + public void delay(long delay) { + mCurrenTimeMillis += delay; + } + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 07870527c8fd..b1200bcdd4b7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -25,8 +25,6 @@ import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FRE import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS; -import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; -import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA; @@ -50,21 +48,17 @@ import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTA import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS; -import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.server.wm.AppCompatOrientationCapability.OrientationCapabilityState.MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; -import static com.android.server.wm.AppCompatOrientationCapability.OrientationCapabilityState.SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS; import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM; import static org.junit.Assert.assertEquals; @@ -149,187 +143,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { mController = new LetterboxUiController(mWm, mActivity); } - // shouldIgnoreRequestedOrientation - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) - public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() { - prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - - assertTrue(mActivity.mAppCompatController.getAppCompatCapability() - .getAppCompatOrientationCapability() - .shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) - public void testShouldIgnoreRequestedOrientation_cameraCompatTreatment_returnsTrue() { - doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(); - doReturn(true).when(mLetterboxConfiguration) - .isCameraCompatTreatmentEnabledAtBuildTime(); - - // Recreate DisplayContent with DisplayRotationCompatPolicy - mActivity = setUpActivityWithComponent(); - mController = new LetterboxUiController(mWm, mActivity); - prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - mController.setRelaunchingAfterRequestedOrientationChanged(false); - - spyOn(mDisplayContent.mDisplayRotationCompatPolicy); - doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy) - .isTreatmentEnabledForActivity(eq(mActivity)); - - assertTrue(mActivity.mAppCompatController.getAppCompatCapability() - .getAppCompatOrientationCapability() - .shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - public void testShouldIgnoreRequestedOrientation_overrideDisabled_returnsFalse() { - prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCapability() - .getAppCompatOrientationCapability() - .shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - public void testShouldIgnoreRequestedOrientation_propertyIsTrue_returnsTrue() - throws Exception { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - mockThatProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, /* value */ true); - mController = new LetterboxUiController(mWm, mActivity); - prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - - assertTrue(mActivity.mAppCompatController.getAppCompatCapability() - .getAppCompatOrientationCapability() - .shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) - public void testShouldIgnoreRequestedOrientation_propertyIsFalseAndOverride_returnsFalse() - throws Exception { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - mockThatProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, /* value */ false); - - mController = new LetterboxUiController(mWm, mActivity); - prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCapability() - .getAppCompatOrientationCapability() - .shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); - } - - @Test - public void testShouldIgnoreOrientationRequestLoop_overrideDisabled_returnsFalse() { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); - // Request 3 times to simulate orientation request loop - for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, - /* expectedCount */ 0); - } - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) - public void testShouldIgnoreOrientationRequestLoop_propertyIsFalseAndOverride_returnsFalse() - throws Exception { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - mockThatProperty(PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED, - /* value */ false); - doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); - - mController = new LetterboxUiController(mWm, mActivity); - - // Request 3 times to simulate orientation request loop - for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, - /* expectedCount */ 0); - } - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) - public void testShouldIgnoreOrientationRequestLoop_isLetterboxed_returnsFalse() { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); - - // Request 3 times to simulate orientation request loop - for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, - /* expectedCount */ i); - } - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) - public void testShouldIgnoreOrientationRequestLoop_noLoop_returnsFalse() { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); - - // No orientation request loop - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, - /* expectedCount */ 0); - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) - public void testShouldIgnoreOrientationRequestLoop_timeout_returnsFalse() - throws InterruptedException { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); - - for (int i = MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i > 0; i--) { - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, - /* expectedCount */ 0); - Thread.sleep(SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS); - } - } - - @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) - public void testShouldIgnoreOrientationRequestLoop_returnsTrue() { - doReturn(true).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); - - for (int i = 0; i < MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, - /* expectedCount */ i); - } - assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ true, - /* expectedCount */ MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP); - } - - private void assertShouldIgnoreOrientationRequestLoop(boolean shouldIgnore, int expectedCount) { - if (shouldIgnore) { - assertTrue(mController.shouldIgnoreOrientationRequestLoop()); - } else { - assertFalse(mController.shouldIgnoreOrientationRequestLoop()); - } - assertEquals(expectedCount, mController.getSetOrientationRequestCounter()); - } - - @Test - @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH}) - public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() { - prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - doReturn(false).when(mLetterboxConfiguration) - .isPolicyForIgnoringRequestedOrientationEnabled(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCapability() - .getAppCompatOrientationCapability() - .shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); - } - // shouldRefreshActivityForCameraCompat @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java index 78cea954eceb..628c65e992fd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java @@ -46,12 +46,14 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import androidx.annotation.NonNull; + +import com.android.server.wm.utils.TestComponentStack; + import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; -import java.util.List; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -318,20 +320,22 @@ public class TransparentPolicyTest extends WindowTestsBase { */ private static class TransparentPolicyRobotTest { + @NonNull private final ActivityTaskManagerService mAtm; - + @NonNull private final Task mTask; - - private final ActivityStackTest mActivityStack; - + @NonNull + private final TestComponentStack<ActivityRecord> mActivityStack; + @NonNull private WindowConfiguration mTopActivityWindowConfiguration; - private TransparentPolicyRobotTest(ActivityTaskManagerService atm, Task task, - ActivityRecord opaqueActivity) { + private TransparentPolicyRobotTest(@NonNull ActivityTaskManagerService atm, + @NonNull Task task, + @NonNull ActivityRecord opaqueActivity) { mAtm = atm; mTask = task; - mActivityStack = new ActivityStackTest(); - mActivityStack.pushActivity(opaqueActivity); + mActivityStack = new TestComponentStack<>(); + mActivityStack.push(opaqueActivity); spyOn(opaqueActivity.mAppCompatController.getTransparentPolicy()); } @@ -361,7 +365,7 @@ public class TransparentPolicyTest extends WindowTestsBase { mTask.addChild(newActivity); } spyOn(newActivity.mAppCompatController.getTransparentPolicy()); - mActivityStack.pushActivity(newActivity); + mActivityStack.push(newActivity); } void attachTopActivityToTask() { @@ -596,45 +600,5 @@ public class TransparentPolicyTest extends WindowTestsBase { display.computeScreenConfiguration(c); display.onRequestedOverrideConfigurationChanged(c); } - - /** - * Contains all the ActivityRecord launched in the test. This is different from what's in - * the Task because activities are added here even if not added to tasks. - */ - private static class ActivityStackTest { - private final List<ActivityRecord> mActivities = new ArrayList<>(); - - void pushActivity(ActivityRecord activityRecord) { - mActivities.add(activityRecord); - } - - void applyToTop(Consumer<ActivityRecord> consumer) { - consumer.accept(top()); - } - - ActivityRecord getFromTop(int fromTop) { - return mActivities.get(mActivities.size() - fromTop - 1); - } - - ActivityRecord base() { - return mActivities.get(0); - } - - ActivityRecord top() { - return mActivities.get(mActivities.size() - 1); - } - - // Allows access to the activity at position beforeLast from the top. - // If fromTop = 0 the activity used is the top one. - void applyTo(int fromTop, Consumer<ActivityRecord> consumer) { - consumer.accept(getFromTop(fromTop)); - } - - void applyToAll(Consumer<ActivityRecord> consumer) { - for (int i = mActivities.size() - 1; i >= 0; i--) { - consumer.accept(mActivities.get(i)); - } - } - } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/TestComponentStack.java b/services/tests/wmtests/src/com/android/server/wm/utils/TestComponentStack.java new file mode 100644 index 000000000000..a06c0a216d9e --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/TestComponentStack.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.utils; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Contains all the component created in the test. + */ +public class TestComponentStack<T> { + + @NonNull + private final List<T> mItems = new ArrayList<>(); + + /** + * Adds an item to the stack. + * + * @param item The item to add. + */ + public void push(@NonNull T item) { + mItems.add(item); + } + + /** + * Consumes the top element of the stack. + * + * @param consumer Consumer for the optional top element. + * @throws IndexOutOfBoundsException In case that stack is empty. + */ + public void applyToTop(@NonNull Consumer<T> consumer) { + consumer.accept(top()); + } + + /** + * Returns the item at fromTop position from the top one if present or it throws an + * exception if not present. + * + * @param fromTop The position from the top of the item to return. + * @return The returned item. + * @throws IndexOutOfBoundsException In case that position doesn't exist. + */ + @NonNull + public T getFromTop(int fromTop) { + return mItems.get(mItems.size() - fromTop - 1); + } + + /** + * @return The item at the base of the stack if present. + * @throws IndexOutOfBoundsException In case that stack is empty. + */ + @NonNull + public T base() { + return mItems.get(0); + } + + /** + * @return The item at the top of the stack if present. + * @throws IndexOutOfBoundsException In case that stack is empty. + */ + @NonNull + public T top() { + return mItems.get(mItems.size() - 1); + } + + /** + * @return {@code true} if the stack is empty. + */ + public boolean isEmpty() { + return mItems.isEmpty(); + } + + /** + * Allows access to the item at position beforeLast from the top. + * + * @param fromTop The position from the top of the item to return. + * @param consumer Consumer for the optional returned element. + */ + public void applyTo(int fromTop, @NonNull Consumer<T> consumer) { + consumer.accept(getFromTop(fromTop)); + } + + /** + * Invoked the consumer iterating over all the elements in the stack. + * + * @param consumer Consumer for the elements. + */ + public void applyToAll(@NonNull Consumer<T> consumer) { + for (int i = mItems.size() - 1; i >= 0; i--) { + consumer.accept(mItems.get(i)); + } + } +} |