diff options
185 files changed, 2788 insertions, 1640 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 3da5a5cca861..7bc0fb220e1a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -56108,6 +56108,17 @@ package android.view { method @NonNull public android.view.WindowInsets getWindowInsets(); } + @FlaggedApi("android.xr.xr_manifest_entries") public final class XrWindowProperties { + field @FlaggedApi("android.xr.xr_manifest_entries") public static final String PROPERTY_XR_ACTIVITY_START_MODE = "android.window.PROPERTY_XR_ACTIVITY_START_MODE"; + field @FlaggedApi("android.xr.xr_manifest_entries") public static final String PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED = "android.window.PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED"; + field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED = "XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED"; + field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED = "XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED"; + field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_ACTIVITY_START_MODE_HOME_SPACE = "XR_ACTIVITY_START_MODE_HOME_SPACE"; + field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_ACTIVITY_START_MODE_UNDEFINED = "XR_ACTIVITY_START_MODE_UNDEFINED"; + field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_BOUNDARY_TYPE_LARGE = "XR_BOUNDARY_TYPE_LARGE"; + field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_BOUNDARY_TYPE_NO_RECOMMENDATION = "XR_BOUNDARY_TYPE_NO_RECOMMENDATION"; + } + } package android.view.accessibility { diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 0085e4f42397..4fb3982c3754 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -150,3 +150,11 @@ flag { description: "Settings override for virtual devices" bug: "371801645" } + +flag { + namespace: "virtual_devices" + name: "viewconfiguration_apis" + description: "APIs for settings ViewConfiguration attributes on virtual devices" + bug: "370720522" + is_exported: true +} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 7b47efd47008..894b068b1528 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -527,22 +527,13 @@ public class InputMethodService extends AbstractInputMethodService { public static final int IME_ACTIVE = 1 << 0; /** - * The IME is perceptibly visible to the user. + * The IME is visible. * * @hide */ public static final int IME_VISIBLE = 1 << 1; /** - * The IME is visible, but not yet perceptible to the user (e.g. fading in) - * by {@link android.view.WindowInsetsController}. - * - * @see InputMethodManager#reportPerceptible - * @hide - */ - public static final int IME_VISIBLE_IMPERCEPTIBLE = 1 << 2; - - /** * The IME window visibility state. * * @hide @@ -550,7 +541,6 @@ public class InputMethodService extends AbstractInputMethodService { @IntDef(flag = true, prefix = { "IME_" }, value = { IME_ACTIVE, IME_VISIBLE, - IME_VISIBLE_IMPERCEPTIBLE, }) public @interface ImeWindowVisibility {} diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 98e6cdde664a..7dc96f21b5ae 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1836,6 +1836,7 @@ public final class ViewRootImpl implements ViewParent, eventsToBeRegistered, mBasePackageName); + // LINT.IfChange(fi_cb) if (forceInvertColor()) { if (mForceInvertObserver == null) { mForceInvertObserver = new ContentObserver(mHandler) { @@ -1844,7 +1845,6 @@ public final class ViewRootImpl implements ViewParent, updateForceDarkMode(); } }; - final Uri[] urisToObserve = { Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED), @@ -1859,6 +1859,7 @@ public final class ViewRootImpl implements ViewParent, } } } + // LINT.ThenChange(/services/core/java/com/android/server/UiModeManagerService.java:fi_cb) } /** diff --git a/core/java/android/view/XrWindowProperties.java b/core/java/android/view/XrWindowProperties.java new file mode 100644 index 000000000000..23021a563393 --- /dev/null +++ b/core/java/android/view/XrWindowProperties.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.FlaggedApi; + +/** + * Class for XR-specific window properties to put in application manifests. + */ +@FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) +public final class XrWindowProperties { + /** @hide */ + private XrWindowProperties() {} + + /** + * Both Application and activity level + * {@link android.content.pm.PackageManager.Property PackageManager.Property} for an app to + * inform the system of the activity launch mode in XR. When it is declared at the application + * level, all activities are set to the defined value, unless it is overridden at the activity + * level. + * + * <p>The default value is {@link #XR_ACTIVITY_START_MODE_UNDEFINED}. + * + * <p>The available values are: + * <ul> + * <li>{@link #XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED} + * <li>{@link #XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED} + * <li>{@link #XR_ACTIVITY_START_MODE_HOME_SPACE} + * <li>{@link #XR_ACTIVITY_START_MODE_UNDEFINED} + * </ul> + * + * <p><b>Syntax:</b> + * <pre> + * <application> + * <property + * android:name="android.window.PROPERTY_ACTIVITY_XR_START_MODE" + * android:value="XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED| + * XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED| + * XR_ACTIVITY_START_MODE_HOME_SPACE| + * XR_ACTIVITY_START_MODE_UNDEFINED"/> + * </application> + * </pre> + */ + @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) + public static final String PROPERTY_XR_ACTIVITY_START_MODE = + "android.window.PROPERTY_XR_ACTIVITY_START_MODE"; + + /** + * Defines the value to launch an activity in unmanaged full space mode in XR, where the + * activity itself is rendering the space and controls its own scene graph. This should be used + * for all activities that use OpenXR to render. + * + * @see #PROPERTY_XR_ACTIVITY_START_MODE + */ + @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) + public static final String XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED = + "XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED"; + + /** + * The default value if not specified. If used, the actual launching mode will be determined by + * the system based on the launching activity's current mode and the launching flags. When + * {@link #PROPERTY_XR_ACTIVITY_START_MODE} is used at the application level, apps can use this + * value to reset at individual activity level. + * + * @see #PROPERTY_XR_ACTIVITY_START_MODE + */ + @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) + public static final String XR_ACTIVITY_START_MODE_UNDEFINED = + "XR_ACTIVITY_START_MODE_UNDEFINED"; + + /** + * Defines the value to launch an activity in + * <a href="https://developer.android.com/develop/xr/jetpack-xr-sdk/transition-home-space-to-full-space">managed + * full space mode</a> in XR, where the system is rendering the activity from a scene graph. + * + * @see #PROPERTY_XR_ACTIVITY_START_MODE + */ + @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) + public static final String XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED = + "XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED"; + + /** + * Defines the value to launch an activity in + * <a href="https://developer.android.com/develop/xr/jetpack-xr-sdk/transition-home-space-to-full-space">home + * space mode</a> in XR. + * + * @see #PROPERTY_XR_ACTIVITY_START_MODE + */ + @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) + public static final String XR_ACTIVITY_START_MODE_HOME_SPACE = + "XR_ACTIVITY_START_MODE_HOME_SPACE"; + + /** + * Both Application and activity level + * {@link android.content.pm.PackageManager.Property PackageManager.Property} for an app to + * inform the system of the type of safety boundary recommended for the activity. When it is + * declared at the application level, all activities are set to the defined value, unless it is + * overridden at the activity level. When not declared, the system will not enforce any + * recommendations for a type of safety boundary and will continue to use the type that is + * currently in use. + * + * <p>The default value is {@link #XR_BOUNDARY_TYPE_NO_RECOMMENDATION}. + * + * <p>The available values are: + * <ul> + * <li>{@link #XR_BOUNDARY_TYPE_LARGE} + * <li>{@link #XR_BOUNDARY_TYPE_NO_RECOMMENDATION} + * </ul> + * + * <p><b>Syntax:</b> + * <pre> + * <application> + * <property + * android:name="android.window.PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED" + * android:value="XR_BOUNDARY_TYPE_LARGE| + * XR_BOUNDARY_TYPE_NO_RECOMMENDATION"/> + * </application> + * </pre> + */ + @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) + public static final String PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED = + "android.window.PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED"; + + /** + * Defines the value to launch an activity with no recommendations for the type of safety + * boundary. The system will continue to use the type of safety boundary that is currently + * in use. + * + * @see #PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED + */ + @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) + public static final String XR_BOUNDARY_TYPE_NO_RECOMMENDATION = + "XR_BOUNDARY_TYPE_NO_RECOMMENDATION"; + + /** + * Defines the value to launch an activity with a large boundary recommended. This is useful for + * activities which expect users to be moving around. The system will ask the user to use a + * larger size for their safety boundary and check that their space is clear, if the larger + * size is not already in use. This larger size will be determined by the system. + * + * @see #PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED + */ + @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) + public static final String XR_BOUNDARY_TYPE_LARGE = "XR_BOUNDARY_TYPE_LARGE"; +} diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp index 746740b0248b..1e8da730aff8 100644 --- a/core/jni/platform/host/HostRuntime.cpp +++ b/core/jni/platform/host/HostRuntime.cpp @@ -280,12 +280,18 @@ static string getJavaProperty(JNIEnv* env, const char* property_name, return string(chars.c_str()); } -static void loadIcuData(string icuPath) { +static void loadIcuData(JNIEnv* env, string icuPath) { void* addr = mmapFile(icuPath.c_str()); + if (addr == nullptr) { + jniThrowRuntimeException(env, "Failed to map the ICU data file."); + } UErrorCode err = U_ZERO_ERROR; udata_setCommonData(addr, &err); if (err != U_ZERO_ERROR) { - ALOGE("Unable to load ICU data\n"); + jniThrowRuntimeException(env, + format("udata_setCommonData failed with error code {}", + u_errorName(err)) + .c_str()); } } @@ -296,12 +302,12 @@ static void loadIcuData() { JNIEnv* env = AndroidRuntime::getJNIEnv(); string icuPath = base::GetProperty("ro.icu.data.path", ""); if (!icuPath.empty()) { - loadIcuData(icuPath); + loadIcuData(env, icuPath); } else { // fallback to read from java.lang.System.getProperty string icuPathFromJava = getJavaProperty(env, "icu.data.path"); if (!icuPathFromJava.empty()) { - loadIcuData(icuPathFromJava); + loadIcuData(env, icuPathFromJava); } } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt index 1a80b0f29aa9..362a5c5c3750 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt @@ -111,47 +111,28 @@ class DragZoneFactory( /** Updates all dimensions after a configuration change. */ fun onConfigurationUpdated() { - dismissDragZoneSize = - if (deviceConfig.isSmallTablet) { - context.resolveDimension(R.dimen.drag_zone_dismiss_fold) - } else { - context.resolveDimension(R.dimen.drag_zone_dismiss_tablet) - } - bubbleDragZoneTabletSize = context.resolveDimension(R.dimen.drag_zone_bubble_tablet) - bubbleDragZoneFoldableSize = context.resolveDimension(R.dimen.drag_zone_bubble_fold) - fullScreenDragZoneWidth = context.resolveDimension(R.dimen.drag_zone_full_screen_width) - fullScreenDragZoneHeight = context.resolveDimension(R.dimen.drag_zone_full_screen_height) - desktopWindowDragZoneWidth = - context.resolveDimension(R.dimen.drag_zone_desktop_window_width) - desktopWindowDragZoneHeight = - context.resolveDimension(R.dimen.drag_zone_desktop_window_height) - desktopWindowFromExpandedViewDragZoneWidth = - context.resolveDimension(R.dimen.drag_zone_desktop_window_expanded_view_width) - desktopWindowFromExpandedViewDragZoneHeight = - context.resolveDimension(R.dimen.drag_zone_desktop_window_expanded_view_height) - splitFromBubbleDragZoneHeight = - context.resolveDimension(R.dimen.drag_zone_split_from_bubble_height) - splitFromBubbleDragZoneWidth = - context.resolveDimension(R.dimen.drag_zone_split_from_bubble_width) - hSplitFromExpandedViewDragZoneWidth = - context.resolveDimension(R.dimen.drag_zone_h_split_from_expanded_view_width) - vSplitFromExpandedViewDragZoneWidth = - context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_width) - vSplitFromExpandedViewDragZoneHeightTablet = - context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_tablet) - vSplitFromExpandedViewDragZoneHeightFoldTall = - context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_tall) - vSplitFromExpandedViewDragZoneHeightFoldShort = - context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_short) - fullScreenDropTargetPadding = - context.resolveDimension(R.dimen.drop_target_full_screen_padding) - desktopWindowDropTargetPaddingSmall = - context.resolveDimension(R.dimen.drop_target_desktop_window_padding_small) - desktopWindowDropTargetPaddingLarge = - context.resolveDimension(R.dimen.drop_target_desktop_window_padding_large) - - // TODO b/393172431: Use the shared xml resources once we can easily access them from + // TODO b/396539130: Use the shared xml resources once we can easily access them from // launcher + dismissDragZoneSize = + if (deviceConfig.isSmallTablet) 140.dpToPx() else 200.dpToPx() + bubbleDragZoneTabletSize = 200.dpToPx() + bubbleDragZoneFoldableSize = 140.dpToPx() + fullScreenDragZoneWidth = 512.dpToPx() + fullScreenDragZoneHeight = 44.dpToPx() + desktopWindowDragZoneWidth = 880.dpToPx() + desktopWindowDragZoneHeight = 300.dpToPx() + desktopWindowFromExpandedViewDragZoneWidth = 200.dpToPx() + desktopWindowFromExpandedViewDragZoneHeight = 350.dpToPx() + splitFromBubbleDragZoneHeight = 100.dpToPx() + splitFromBubbleDragZoneWidth = 60.dpToPx() + hSplitFromExpandedViewDragZoneWidth = 60.dpToPx() + vSplitFromExpandedViewDragZoneWidth = 200.dpToPx() + vSplitFromExpandedViewDragZoneHeightTablet = 285.dpToPx() + vSplitFromExpandedViewDragZoneHeightFoldTall = 150.dpToPx() + vSplitFromExpandedViewDragZoneHeightFoldShort = 100.dpToPx() + fullScreenDropTargetPadding = 20.dpToPx() + desktopWindowDropTargetPaddingSmall = 100.dpToPx() + desktopWindowDropTargetPaddingLarge = 130.dpToPx() expandedViewDropTargetWidth = 364.dpToPx() expandedViewDropTargetHeight = 578.dpToPx() expandedViewDropTargetPaddingBottom = 108.dpToPx() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 2ded48a79d6e..43a19ef034c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -877,8 +877,7 @@ public abstract class WMShellModule { @ShellMainThread Handler handler) { int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context); if (!DesktopModeStatus.canEnterDesktopMode(context) - || !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isTrue() - || maxTaskLimit <= 0) { + || !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isTrue()) { return Optional.empty(); } return Optional.of( @@ -886,7 +885,7 @@ public abstract class WMShellModule { transitions, desktopUserRepositories, shellTaskOrganizer, - maxTaskLimit, + maxTaskLimit <= 0 ? null : maxTaskLimit, interactionJankMonitor, context, handler)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt index 70a648f57125..fcdf4af531ee 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt @@ -109,7 +109,7 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser desktopUserRepositories.getProfile(taskInfo.userId) if (!desktopRepository.isActiveTask(taskInfo.taskId)) return logD("onTaskMovingToBack for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId) - // TODO: b/367268953 - Connect this with DesktopRepository. + desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, /* isVisible= */ false) } override fun onTaskClosing(taskInfo: RunningTaskInfo) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index b9b4d9e4bbd8..da369f094405 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -38,17 +38,17 @@ import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionObserver /** - * Limits the number of tasks shown in Desktop Mode. + * Keeps track of minimized tasks and limits the number of tasks shown in Desktop Mode. * - * This class should only be used if - * [android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT] is enabled and - * [maxTasksLimit] is strictly greater than 0. + * [maxTasksLimit] must be strictly greater than 0 if it's given. + * + * TODO(b/400634379): Separate two responsibilities of this class into two classes. */ class DesktopTasksLimiter( transitions: Transitions, private val desktopUserRepositories: DesktopUserRepositories, private val shellTaskOrganizer: ShellTaskOrganizer, - private val maxTasksLimit: Int, + private val maxTasksLimit: Int?, private val interactionJankMonitor: InteractionJankMonitor, private val context: Context, @ShellMainThread private val handler: Handler, @@ -59,13 +59,19 @@ class DesktopTasksLimiter( private var userId: Int init { - require(maxTasksLimit > 0) { - "DesktopTasksLimiter: maxTasksLimit should be greater than 0. Current value: $maxTasksLimit." + maxTasksLimit?.let { + require(it > 0) { + "DesktopTasksLimiter: maxTasksLimit should be greater than 0. Current value: $it." + } } transitions.registerObserver(minimizeTransitionObserver) userId = ActivityManager.getCurrentUser() desktopUserRepositories.current.addActiveTaskListener(leftoverMinimizedTasksRemover) - logV("Starting limiter with a maximum of %d tasks", maxTasksLimit) + if (maxTasksLimit != null) { + logV("Starting limiter with a maximum of %d tasks", maxTasksLimit) + } else { + logV("Starting limiter without the task limit") + } } data class TaskDetails( @@ -325,7 +331,7 @@ class DesktopTasksLimiter( launchingNewIntent: Boolean, ): Int? { val newTasksOpening = if (launchingNewIntent) 1 else 0 - if (visibleOrderedTasks.size + newTasksOpening <= maxTasksLimit) { + if (visibleOrderedTasks.size + newTasksOpening <= (maxTasksLimit ?: Int.MAX_VALUE)) { logV("No need to minimize; tasks below limit") // No need to minimize anything return null diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt index 6b0ee5b7ffd4..54360a8fd908 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt @@ -180,6 +180,28 @@ class DesktopTaskChangeListenerTest : ShellTestCase() { } @Test + fun onTaskMovingToBack_activeTaskInRepo_updatesTask() { + val task = createFreeformTask().apply { isVisible = true } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) + + desktopTaskChangeListener.onTaskMovingToBack(task) + + verify(desktopUserRepositories.current) + .updateTask(task.displayId, task.taskId, /* isVisible= */ false) + } + + @Test + fun onTaskMovingToBack_nonActiveTaskInRepo_noop() { + val task = createFreeformTask().apply { isVisible = true } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false) + + desktopTaskChangeListener.onTaskMovingToBack(task) + + verify(desktopUserRepositories.current, never()) + .updateTask(task.displayId, task.taskId, /* isVisible= */ false) + } + + @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun onTaskClosing_backNavEnabled_nonClosingTask_minimizesTaskInRepo() { val task = createFreeformTask().apply { isVisible = true } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 62e3c544e390..76103640c029 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -172,6 +172,20 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test + fun createDesktopTasksLimiter_withNoLimit_shouldSucceed() { + // Instantiation should succeed without an error. + DesktopTasksLimiter( + transitions, + userRepositories, + shellTaskOrganizer, + maxTasksLimit = null, + interactionJankMonitor, + mContext, + handler, + ) + } + + @Test fun addPendingMinimizeTransition_taskIsNotMinimized() { desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index 9588a5c73385..0871d38ceb46 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -15,7 +15,6 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper -import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_OPEN import android.window.TransitionInfo @@ -785,7 +784,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { startTransaction: SurfaceControl.Transaction = mock(), finishTransaction: SurfaceControl.Transaction = mock(), homeChange: TransitionInfo.Change? = createHomeChange(), - transitionRootLeash: SurfaceControl? = null, + transitionRootLeash: SurfaceControl = mock(), ): IBinder { whenever(dragAnimator.position).thenReturn(PointF()) // Simulate transition is started and is ready to animate. @@ -887,7 +886,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { type: Int, draggedTask: RunningTaskInfo, homeChange: TransitionInfo.Change? = createHomeChange(), - rootLeash: SurfaceControl? = null, + rootLeash: SurfaceControl = mock(), ) = TransitionInfo(type, /* flags= */ 0).apply { homeChange?.let { addChange(it) } @@ -904,9 +903,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { flags = flags or FLAG_IS_WALLPAPER } ) - if (rootLeash != null) { - addRootLeash(DEFAULT_DISPLAY, rootLeash, /* offsetLeft= */ 0, /* offsetTop= */ 0) - } + addRootLeash(draggedTask.displayId, rootLeash, /* offsetLeft= */ 0, /* offsetTop= */ 0) } private fun createHomeChange() = diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index a029f56cf1d7..418a76ffb534 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -249,3 +249,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "audio_stream_media_service_by_receive_state" + namespace: "cross_device_experiences" + description: "Start or update audio stream media service by receive state" + bug: "398700619" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SettingsLib/res/drawable/ic_news.xml b/packages/SettingsLib/res/drawable/ic_news.xml new file mode 100644 index 000000000000..90615ec1b8fd --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_news.xml @@ -0,0 +1,19 @@ +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="14.933333dp" android:viewportHeight="14" android:viewportWidth="15" android:width="16dp"> + <path android:fillColor="#ffffff" android:pathData="M3,10.8H11V9.6H3V10.8ZM3,8.4H5.4V4.8H3V8.4ZM12.2,13.6H1.8C1.467,13.6 1.183,13.483 0.95,13.25C0.717,13.017 0.6,12.733 0.6,12.4V3.6C0.6,3.267 0.717,2.983 0.95,2.75C1.183,2.517 1.467,2.4 1.8,2.4H6.683C6.528,2.711 6.406,3.033 6.317,3.367C6.239,3.7 6.2,4.044 6.2,4.4C6.2,4.933 6.283,5.439 6.45,5.917C6.628,6.383 6.878,6.811 7.2,7.2H6.6V8.4H8.767C9.056,8.522 9.35,8.622 9.65,8.7C9.961,8.767 10.278,8.8 10.6,8.8C11.111,8.8 11.606,8.717 12.083,8.55C12.572,8.372 13.011,8.122 13.4,7.8V12.4C13.4,12.733 13.283,13.017 13.05,13.25C12.817,13.483 12.533,13.6 12.2,13.6ZM10.6,8C10.6,7 10.25,6.15 9.55,5.45C8.85,4.75 8,4.4 7,4.4C8,4.4 8.85,4.05 9.55,3.35C10.25,2.65 10.6,1.8 10.6,0.8C10.6,1.8 10.95,2.65 11.65,3.35C12.35,4.05 13.2,4.4 14.2,4.4C13.2,4.4 12.35,4.75 11.65,5.45C10.95,6.15 10.6,7 10.6,8Z"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_promotions.xml b/packages/SettingsLib/res/drawable/ic_promotions.xml new file mode 100644 index 000000000000..a597ecebd967 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_promotions.xml @@ -0,0 +1,19 @@ +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="16dp" android:viewportHeight="15" android:viewportWidth="13" android:width="13.866667dp"> + <path android:fillColor="#ffffff" android:pathData="M1.4,13.4C1.067,13.4 0.783,13.283 0.55,13.05C0.317,12.817 0.2,12.533 0.2,12.2V4.6C0.2,4.267 0.317,3.983 0.55,3.75C0.783,3.517 1.067,3.4 1.4,3.4H2.6V3.133C2.6,2.467 2.822,1.878 3.267,1.367C3.722,0.856 4.3,0.6 5,0.6C5.667,0.6 6.233,0.833 6.7,1.3C7.167,1.767 7.4,2.333 7.4,3V3.4H8.6C8.933,3.4 9.217,3.517 9.45,3.75C9.683,3.983 9.8,4.267 9.8,4.6V6.367C9.611,6.311 9.417,6.272 9.217,6.25C9.017,6.217 8.811,6.2 8.6,6.2C7.378,6.2 6.339,6.628 5.483,7.483C4.628,8.339 4.2,9.378 4.2,10.6C4.2,11.111 4.283,11.611 4.45,12.1C4.628,12.578 4.878,13.011 5.2,13.4H1.4ZM8.6,14.2C8.6,13.2 8.25,12.35 7.55,11.65C6.85,10.95 6,10.6 5,10.6C6,10.6 6.85,10.25 7.55,9.55C8.25,8.85 8.6,8 8.6,7C8.6,8 8.95,8.85 9.65,9.55C10.35,10.25 11.2,10.6 12.2,10.6C11.2,10.6 10.35,10.95 9.65,11.65C8.95,12.35 8.6,13.2 8.6,14.2ZM3.8,3.4H6.2V3C6.2,2.667 6.083,2.383 5.85,2.15C5.617,1.917 5.333,1.8 5,1.8C4.667,1.8 4.383,1.917 4.15,2.15C3.917,2.383 3.8,2.667 3.8,3V3.4ZM3.2,5.8C3.367,5.8 3.506,5.744 3.617,5.633C3.739,5.511 3.8,5.367 3.8,5.2V4.6H2.6V5.2C2.6,5.367 2.656,5.511 2.767,5.633C2.889,5.744 3.033,5.8 3.2,5.8ZM6.8,5.8C6.967,5.8 7.106,5.744 7.217,5.633C7.339,5.511 7.4,5.367 7.4,5.2V4.6H6.2V5.2C6.2,5.367 6.256,5.511 6.367,5.633C6.489,5.744 6.633,5.8 6.8,5.8Z"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_recs.xml b/packages/SettingsLib/res/drawable/ic_recs.xml new file mode 100644 index 000000000000..034ff9e221b0 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_recs.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="14dp" + android:height="16dp" + android:viewportWidth="14" + android:viewportHeight="16"> + <path + android:pathData="M6,15.2L3.6,12.8H1.6C1.278,12.8 0.994,12.683 0.75,12.45C0.517,12.206 0.4,11.922 0.4,11.6V2.8C0.4,2.478 0.517,2.2 0.75,1.967C0.994,1.722 1.278,1.6 1.6,1.6H6.2C5.878,1.989 5.628,2.428 5.45,2.917C5.283,3.394 5.2,3.889 5.2,4.4C5.2,5.622 5.628,6.661 6.483,7.517C7.339,8.372 8.378,8.8 9.6,8.8C9.956,8.8 10.3,8.761 10.633,8.683C10.967,8.594 11.289,8.472 11.6,8.317V11.6C11.6,11.922 11.483,12.206 11.25,12.45C11.017,12.683 10.733,12.8 10.4,12.8H8.4L6,15.2ZM9.6,8C9.6,7 9.25,6.15 8.55,5.45C7.85,4.75 7,4.4 6,4.4C7,4.4 7.85,4.05 8.55,3.35C9.25,2.65 9.6,1.8 9.6,0.8C9.6,1.8 9.95,2.65 10.65,3.35C11.35,4.05 12.2,4.4 13.2,4.4C12.2,4.4 11.35,4.75 10.65,5.45C9.95,6.15 9.6,7 9.6,8Z" + android:fillColor="#ffffff"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_social.xml b/packages/SettingsLib/res/drawable/ic_social.xml new file mode 100644 index 000000000000..01974319b441 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_social.xml @@ -0,0 +1,19 @@ +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="16dp" android:viewportHeight="15" android:viewportWidth="15" android:width="16dp"> + <path android:fillColor="#ffffff" android:pathData="M0.6,14.4V12.733C0.6,12.411 0.678,12.117 0.833,11.85C0.989,11.583 1.211,11.378 1.5,11.233C2.056,10.956 2.628,10.75 3.217,10.617C3.817,10.472 4.428,10.4 5.05,10.4C5.661,10.4 6.256,10.472 6.833,10.617C7.422,10.75 7.989,10.956 8.533,11.233C8.811,11.378 9.028,11.583 9.183,11.85C9.339,12.117 9.417,12.411 9.417,12.733V14.4H0.6ZM10.6,14.4V12.617C10.6,12.183 10.494,11.778 10.283,11.4C10.072,11.022 9.772,10.733 9.383,10.533C9.772,10.6 10.144,10.7 10.5,10.833C10.856,10.956 11.206,11.106 11.55,11.283C11.828,11.439 12.072,11.628 12.283,11.85C12.494,12.061 12.6,12.317 12.6,12.617V14.4H10.6ZM5.2,9.6C4.589,9.6 4.067,9.389 3.633,8.967C3.211,8.533 3,8.011 3,7.4C3,6.789 3.211,6.272 3.633,5.85C4.067,5.417 4.589,5.2 5.2,5.2C5.811,5.2 6.328,5.417 6.75,5.85C7.183,6.272 7.4,6.789 7.4,7.4C7.4,8.011 7.183,8.533 6.75,8.967C6.328,9.389 5.811,9.6 5.2,9.6ZM10.6,7.4C10.6,8.011 10.383,8.533 9.95,8.967C9.528,9.389 9.011,9.6 8.4,9.6C8.311,9.6 8.217,9.594 8.117,9.583C8.017,9.572 7.922,9.55 7.833,9.517C8.089,9.206 8.278,8.872 8.4,8.517C8.533,8.161 8.6,7.789 8.6,7.4C8.6,7.011 8.533,6.639 8.4,6.283C8.278,5.928 8.089,5.594 7.833,5.283C7.922,5.25 8.017,5.228 8.117,5.217C8.217,5.206 8.311,5.2 8.4,5.2C9.011,5.2 9.528,5.417 9.95,5.85C10.383,6.272 10.6,6.789 10.6,7.4ZM11.4,6.4C11.4,5.622 11.128,4.961 10.583,4.417C10.039,3.872 9.378,3.6 8.6,3.6C9.378,3.6 10.039,3.328 10.583,2.783C11.128,2.239 11.4,1.578 11.4,0.8C11.4,1.578 11.672,2.239 12.217,2.783C12.761,3.328 13.422,3.6 14.2,3.6C13.422,3.6 12.761,3.872 12.217,4.417C11.672,4.961 11.4,5.622 11.4,6.4Z"/> +</vector> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java index de7c450d8d39..7c975b750639 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java @@ -72,6 +72,7 @@ import java.util.regex.Pattern; public final class DeviceConfigService extends Binder { private static final List<String> sAconfigTextProtoFilesOnDevice = List.of( "/system/etc/aconfig_flags.pb", + "/system_ext/etc/aconfig_flags.pb", "/product/etc/aconfig_flags.pb", "/vendor/etc/aconfig_flags.pb"); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 99c4e21c6053..17c13b78778c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -158,6 +158,7 @@ public class SettingsState { private static final List<String> sAconfigTextProtoFilesOnDevice = List.of( "/system/etc/aconfig_flags.pb", + "/system_ext/etc/aconfig_flags.pb", "/product/etc/aconfig_flags.pb", "/vendor/etc/aconfig_flags.pb"); diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/PagerDots.kt index 91f1477d5325..172d88af4cc8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/PagerDots.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.ui.compose +package com.android.systemui.common.ui.compose import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.Canvas @@ -43,9 +43,9 @@ import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import com.android.app.tracing.coroutines.launchTraced as launch import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch import platform.test.motion.compose.values.MotionTestValueKey import platform.test.motion.compose.values.motionTestValues diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt index 547461e5faf2..a0216268308c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt @@ -49,6 +49,8 @@ import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult +import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.modifiers.thenIf import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton @@ -258,7 +260,11 @@ fun ContentScope.QuickSettingsLayout( BrightnessSliderContainer( viewModel = viewModel.brightnessSliderViewModel, containerColor = OverlayShade.Colors.PanelBackground, - modifier = Modifier.systemGestureExclusionInShade().fillMaxWidth(), + modifier = + Modifier.systemGestureExclusionInShade( + enabled = { layoutState.transitionState is TransitionState.Idle } + ) + .fillMaxWidth(), ) Box { @@ -289,18 +295,20 @@ object QuickSettingsShade { * right. */ @Composable - fun Modifier.systemGestureExclusionInShade(): Modifier { + fun Modifier.systemGestureExclusionInShade(enabled: () -> Boolean): Modifier { val density = LocalDensity.current - return systemGestureExclusion { layoutCoordinates -> - val sidePadding = with(density) { Dimensions.Padding.toPx() } - Rect( - offset = Offset(x = -sidePadding, y = 0f), - size = - Size( - width = layoutCoordinates.size.width.toFloat() + 2 * sidePadding, - height = layoutCoordinates.size.height.toFloat(), - ), - ) + return thenIf(enabled()) { + Modifier.systemGestureExclusion { layoutCoordinates -> + val sidePadding = with(density) { Dimensions.Padding.toPx() } + Rect( + offset = Offset(x = -sidePadding, y = 0f), + size = + Size( + width = layoutCoordinates.size.width.toFloat() + 2 * sidePadding, + height = layoutCoordinates.size.height.toFloat(), + ), + ) + } } } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index 9bb3bac824e9..365567b17ec0 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -17,7 +17,6 @@ import android.content.Context import android.content.res.Resources import android.graphics.Color import android.graphics.Rect -import android.graphics.RectF import android.icu.text.NumberFormat import android.util.TypedValue import android.view.LayoutInflater @@ -29,6 +28,7 @@ import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockAnimations import com.android.systemui.plugins.clocks.ClockConfig import com.android.systemui.plugins.clocks.ClockController +import com.android.systemui.plugins.clocks.ClockEventListener import com.android.systemui.plugins.clocks.ClockEvents import com.android.systemui.plugins.clocks.ClockFaceConfig import com.android.systemui.plugins.clocks.ClockFaceController @@ -102,7 +102,7 @@ class DefaultClockController( isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float, - onBoundsChanged: (RectF) -> Unit, + clockListener: ClockEventListener?, ) { largeClock.recomputePadding(null) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt index 6dfd2268005f..5acd4468fe92 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt @@ -16,13 +16,13 @@ package com.android.systemui.shared.clocks -import android.graphics.RectF import com.android.systemui.animation.GSFAxes import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.AxisType import com.android.systemui.plugins.clocks.ClockConfig import com.android.systemui.plugins.clocks.ClockController +import com.android.systemui.plugins.clocks.ClockEventListener import com.android.systemui.plugins.clocks.ClockEvents import com.android.systemui.plugins.clocks.ClockFontAxis import com.android.systemui.plugins.clocks.ClockFontAxis.Companion.merge @@ -107,11 +107,11 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float, - onBoundsChanged: (RectF) -> Unit, + clockListener: ClockEventListener?, ) { events.onFontAxesChanged(clockCtx.settings.axes) smallClock.run { - layerController.onViewBoundsChanged = onBoundsChanged + layerController.onViewBoundsChanged = { clockListener?.onBoundsChanged(it) } events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme)) animations.doze(dozeFraction) animations.fold(foldFraction) @@ -119,7 +119,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController } largeClock.run { - layerController.onViewBoundsChanged = onBoundsChanged + layerController.onViewBoundsChanged = { clockListener?.onBoundsChanged(it) } events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme)) animations.doze(dozeFraction) animations.fold(foldFraction) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt new file mode 100644 index 000000000000..239e02640908 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay + +import android.content.ClipData +import android.content.ComponentName +import android.content.Intent +import android.net.Uri +import android.text.SpannableString +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ActionIntentCreatorTest : SysuiTestCase() { + val creator = ActionIntentCreator() + + @Test + fun test_getTextEditorIntent() { + val intent = creator.getTextEditorIntent(context) + assertEquals(ComponentName(context, EditTextActivity::class.java), intent.component) + assertFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + } + + @Test + fun test_getRemoteCopyIntent() { + context.getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage, "") + + val clipData = ClipData.newPlainText("Test", "Test Item") + var intent = creator.getRemoteCopyIntent(clipData, context) + + assertEquals(null, intent.component) + assertFlags(intent, EXTERNAL_INTENT_FLAGS) + assertEquals(clipData, intent.clipData) + + // Try again with a remote copy component + val fakeComponent = + ComponentName("com.android.remotecopy", "com.android.remotecopy.RemoteCopyActivity") + context + .getOrCreateTestableResources() + .addOverride(R.string.config_remoteCopyPackage, fakeComponent.flattenToString()) + + intent = creator.getRemoteCopyIntent(clipData, context) + assertEquals(fakeComponent, intent.component) + } + + @Test + fun test_getImageEditIntent() { + context.getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, "") + val fakeUri = Uri.parse("content://foo") + var intent = creator.getImageEditIntent(fakeUri, context) + + assertEquals(Intent.ACTION_EDIT, intent.action) + assertEquals("image/*", intent.type) + assertEquals(null, intent.component) + assertEquals("clipboard", intent.getStringExtra("edit_source")) + assertFlags(intent, EXTERNAL_INTENT_FLAGS) + + // try again with an editor component + val fakeComponent = + ComponentName("com.android.remotecopy", "com.android.remotecopy.RemoteCopyActivity") + context + .getOrCreateTestableResources() + .addOverride(R.string.config_screenshotEditor, fakeComponent.flattenToString()) + intent = creator.getImageEditIntent(fakeUri, context) + assertEquals(fakeComponent, intent.component) + } + + @Test + fun test_getShareIntent_plaintext() { + val clipData = ClipData.newPlainText("Test", "Test Item") + val intent = creator.getShareIntent(clipData, context) + + assertEquals(Intent.ACTION_CHOOSER, intent.action) + assertFlags(intent, EXTERNAL_INTENT_FLAGS) + val target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertEquals("Test Item", target?.getStringExtra(Intent.EXTRA_TEXT)) + assertEquals("text/plain", target?.type) + } + + @Test + fun test_getShareIntent_html() { + val clipData = ClipData.newHtmlText("Test", "Some HTML", "<b>Some HTML</b>") + val intent = creator.getShareIntent(clipData, getContext()) + + assertEquals(Intent.ACTION_CHOOSER, intent.action) + assertFlags(intent, EXTERNAL_INTENT_FLAGS) + val target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertEquals("Some HTML", target?.getStringExtra(Intent.EXTRA_TEXT)) + assertEquals("text/plain", target?.type) + } + + @Test + fun test_getShareIntent_image() { + val uri = Uri.parse("content://something") + val clipData = ClipData("Test", arrayOf("image/png"), ClipData.Item(uri)) + val intent = creator.getShareIntent(clipData, context) + + assertEquals(Intent.ACTION_CHOOSER, intent.action) + assertFlags(intent, EXTERNAL_INTENT_FLAGS) + val target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertEquals(uri, target?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)) + assertEquals(uri, target?.clipData?.getItemAt(0)?.uri) + assertEquals("image/png", target?.type) + } + + @Test + fun test_getShareIntent_spannableText() { + val clipData = ClipData.newPlainText("Test", SpannableString("Test Item")) + val intent = creator.getShareIntent(clipData, context) + + assertEquals(Intent.ACTION_CHOOSER, intent.action) + assertFlags(intent, EXTERNAL_INTENT_FLAGS) + val target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertEquals("Test Item", target?.getStringExtra(Intent.EXTRA_TEXT)) + assertEquals("text/plain", target?.type) + } + + // Assert that the given flags are set + private fun assertFlags(intent: Intent, flags: Int) { + assertTrue((intent.flags and flags) == flags) + } + + companion object { + private const val EXTERNAL_INTENT_FLAGS: Int = + (Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK or + Intent.FLAG_GRANT_READ_URI_PERMISSION) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java index ea6cb3b6d178..126b3fa9e7ca 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java @@ -36,13 +36,15 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) -public class IntentCreatorTest extends SysuiTestCase { +public class DefaultIntentCreatorTest extends SysuiTestCase { private static final int EXTERNAL_INTENT_FLAGS = Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION; + private final DefaultIntentCreator mIntentCreator = new DefaultIntentCreator(); + @Test public void test_getTextEditorIntent() { - Intent intent = IntentCreator.getTextEditorIntent(getContext()); + Intent intent = mIntentCreator.getTextEditorIntent(getContext()); assertEquals(new ComponentName(getContext(), EditTextActivity.class), intent.getComponent()); assertFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); @@ -54,7 +56,7 @@ public class IntentCreatorTest extends SysuiTestCase { ""); ClipData clipData = ClipData.newPlainText("Test", "Test Item"); - Intent intent = IntentCreator.getRemoteCopyIntent(clipData, getContext()); + Intent intent = mIntentCreator.getRemoteCopyIntent(clipData, getContext()); assertEquals(null, intent.getComponent()); assertFlags(intent, EXTERNAL_INTENT_FLAGS); @@ -66,7 +68,7 @@ public class IntentCreatorTest extends SysuiTestCase { getContext().getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage, fakeComponent.flattenToString()); - intent = IntentCreator.getRemoteCopyIntent(clipData, getContext()); + intent = mIntentCreator.getRemoteCopyIntent(clipData, getContext()); assertEquals(fakeComponent, intent.getComponent()); } @@ -75,7 +77,7 @@ public class IntentCreatorTest extends SysuiTestCase { getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, ""); Uri fakeUri = Uri.parse("content://foo"); - Intent intent = IntentCreator.getImageEditIntent(fakeUri, getContext()); + Intent intent = mIntentCreator.getImageEditIntent(fakeUri, getContext()); assertEquals(Intent.ACTION_EDIT, intent.getAction()); assertEquals("image/*", intent.getType()); @@ -88,14 +90,14 @@ public class IntentCreatorTest extends SysuiTestCase { "com.android.remotecopy.RemoteCopyActivity"); getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, fakeComponent.flattenToString()); - intent = IntentCreator.getImageEditIntent(fakeUri, getContext()); + intent = mIntentCreator.getImageEditIntent(fakeUri, getContext()); assertEquals(fakeComponent, intent.getComponent()); } @Test public void test_getShareIntent_plaintext() { ClipData clipData = ClipData.newPlainText("Test", "Test Item"); - Intent intent = IntentCreator.getShareIntent(clipData, getContext()); + Intent intent = mIntentCreator.getShareIntent(clipData, getContext()); assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); assertFlags(intent, EXTERNAL_INTENT_FLAGS); @@ -108,7 +110,7 @@ public class IntentCreatorTest extends SysuiTestCase { public void test_getShareIntent_html() { ClipData clipData = ClipData.newHtmlText("Test", "Some HTML", "<b>Some HTML</b>"); - Intent intent = IntentCreator.getShareIntent(clipData, getContext()); + Intent intent = mIntentCreator.getShareIntent(clipData, getContext()); assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); assertFlags(intent, EXTERNAL_INTENT_FLAGS); @@ -122,7 +124,7 @@ public class IntentCreatorTest extends SysuiTestCase { Uri uri = Uri.parse("content://something"); ClipData clipData = new ClipData("Test", new String[]{"image/png"}, new ClipData.Item(uri)); - Intent intent = IntentCreator.getShareIntent(clipData, getContext()); + Intent intent = mIntentCreator.getShareIntent(clipData, getContext()); assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); assertFlags(intent, EXTERNAL_INTENT_FLAGS); @@ -135,7 +137,7 @@ public class IntentCreatorTest extends SysuiTestCase { @Test public void test_getShareIntent_spannableText() { ClipData clipData = ClipData.newPlainText("Test", new SpannableString("Test Item")); - Intent intent = IntentCreator.getShareIntent(clipData, getContext()); + Intent intent = mIntentCreator.getShareIntent(clipData, getContext()); assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); assertFlags(intent, EXTERNAL_INTENT_FLAGS); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt index 299105e2dabd..e41d46ce90af 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt @@ -20,6 +20,7 @@ import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.testKosmos @@ -29,6 +30,9 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyString +import org.mockito.kotlin.eq +import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) @SmallTest @@ -99,6 +103,11 @@ class PerDisplayInstanceRepositoryImplTest : SysuiTestCase() { assertThat(fakePerDisplayInstanceProviderWithTeardown.destroyed).isEmpty() } + @Test + fun start_registersDumpable() { + verify(kosmos.dumpManager).registerNormalDumpable(anyString(), eq(underTest)) + } + private fun createDisplay(displayId: Int): Display = display(type = Display.TYPE_INTERNAL, id = displayId) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 2c852c3f6185..94db429c2225 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -598,7 +598,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardClockPositionAlgorithm, mMSDLPlayer, mBrightnessMirrorShowingRepository, - new BlurConfig(0f, 0f)); + new BlurConfig(0f, 0f), + () -> mKosmos.getFakeShadeDisplaysRepository()); mNotificationPanelViewController.initDependencies( mCentralSurfaces, null, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index f54c36754d31..89263fc42739 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -195,9 +195,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND) public void updateSystemUiStateFlags_updatesSysuiStateInteractor() { var DISPLAY_ID = 10; - var displayMock = display(TYPE_INTERNAL, /* flags= */ 0, /* id= */DISPLAY_ID, - /* state= */ null); - when(mView.getDisplay()).thenReturn(displayMock); + mKosmos.getFakeShadeDisplaysRepository().setPendingDisplayId(DISPLAY_ID); mNotificationPanelViewController.updateSystemUiStateFlags(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt index 4f301031e77c..fad66581682f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt @@ -108,7 +108,7 @@ class DefaultClockProviderTest : SysuiTestCase() { verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA) verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA) - clock.initialize(true, 0f, 0f, {}) + clock.initialize(true, 0f, 0f, null) val expectedColor = 0 verify(mockSmallClockView).setColors(DOZE_COLOR, expectedColor) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt index 4bd02e0fab22..17509dc6a80f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt @@ -19,6 +19,10 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ConditionExtensionsTest : SysuiTestCase() { private lateinit var testScope: TestScope + private val testCallback = + Condition.Callback { + // This is a no-op + } @Before fun setUp() { @@ -33,7 +37,7 @@ class ConditionExtensionsTest : SysuiTestCase() { assertThat(condition.isConditionSet).isFalse() - condition.start() + condition.testStart() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isTrue() } @@ -46,7 +50,7 @@ class ConditionExtensionsTest : SysuiTestCase() { assertThat(condition.isConditionSet).isFalse() - condition.start() + condition.testStart() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isFalse() } @@ -56,7 +60,7 @@ class ConditionExtensionsTest : SysuiTestCase() { testScope.runTest { val flow = emptyFlow<Boolean>() val condition = flow.toCondition(scope = this, Condition.START_EAGERLY) - condition.start() + condition.testStop() assertThat(condition.isConditionSet).isFalse() assertThat(condition.isConditionMet).isFalse() @@ -72,7 +76,7 @@ class ConditionExtensionsTest : SysuiTestCase() { strategy = Condition.START_EAGERLY, initialValue = true, ) - condition.start() + condition.testStart() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isTrue() @@ -88,7 +92,7 @@ class ConditionExtensionsTest : SysuiTestCase() { strategy = Condition.START_EAGERLY, initialValue = false, ) - condition.start() + condition.testStart() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isFalse() @@ -99,7 +103,7 @@ class ConditionExtensionsTest : SysuiTestCase() { testScope.runTest { val flow = MutableStateFlow(false) val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY) - condition.start() + condition.testStart() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isFalse() @@ -110,7 +114,7 @@ class ConditionExtensionsTest : SysuiTestCase() { flow.value = false assertThat(condition.isConditionMet).isFalse() - condition.stop() + condition.testStop() } @Test @@ -120,10 +124,18 @@ class ConditionExtensionsTest : SysuiTestCase() { val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY) assertThat(flow.subscriptionCount.value).isEqualTo(0) - condition.start() + condition.testStart() assertThat(flow.subscriptionCount.value).isEqualTo(1) - condition.stop() + condition.testStop() assertThat(flow.subscriptionCount.value).isEqualTo(0) } + + fun Condition.testStart() { + addCallback(testCallback) + } + + fun Condition.testStop() { + removeCallback(testCallback) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java index 5416536305fc..da660e2f6009 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java @@ -40,7 +40,7 @@ public class FakeCondition extends Condition { } @Override - protected int getStartStrategy() { + public int getStartStrategy() { return START_EAGERLY; } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt index 7ed2bd38bcd2..0b9b297130a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt @@ -34,6 +34,8 @@ import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.addNotif +import com.android.systemui.statusbar.notification.data.repository.removeNotif import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.notification.shared.CallType @@ -409,6 +411,63 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() { @Test @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun shownNotificationChips_lastAppVisibleTimeMaintainedAcrossNotifAddsAndRemoves() = + kosmos.runTest { + val latest by collectLastValue(underTest.shownNotificationChips) + + val notif1Info = NotifInfo("notif1", mock<StatusBarIconView>(), uid = 100) + val notif2Info = NotifInfo("notif2", mock<StatusBarIconView>(), uid = 200) + + // Have notif1's app start as showing and then hide later so we get the chip + activityManagerRepository.fake.startingIsAppVisibleValue = true + fakeSystemClock.setCurrentTimeMillis(9_000) + activeNotificationListRepository.addNotif( + activeNotificationModel( + key = notif1Info.key, + uid = notif1Info.uid, + statusBarChipIcon = notif1Info.icon, + promotedContent = + PromotedNotificationContentModel.Builder(notif1Info.key).build(), + ) + ) + activityManagerRepository.fake.setIsAppVisible(notif1Info.uid, isAppVisible = false) + + assertThat(latest!![0].key).isEqualTo(notif1Info.key) + assertThat(latest!![0].lastAppVisibleTime).isEqualTo(9_000) + + // WHEN a new notification is added + activityManagerRepository.fake.startingIsAppVisibleValue = true + fakeSystemClock.setCurrentTimeMillis(10_000) + activeNotificationListRepository.addNotif( + activeNotificationModel( + key = notif2Info.key, + uid = notif2Info.uid, + statusBarChipIcon = notif2Info.icon, + promotedContent = + PromotedNotificationContentModel.Builder(notif2Info.key).build(), + ) + ) + activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = false) + + // THEN the new notification is first + assertThat(latest!![0].key).isEqualTo(notif2Info.key) + assertThat(latest!![0].lastAppVisibleTime).isEqualTo(10_000) + + // And THEN the original notification maintains its lastAppVisibleTime + assertThat(latest!![1].key).isEqualTo(notif1Info.key) + assertThat(latest!![1].lastAppVisibleTime).isEqualTo(9_000) + + // WHEN notif1 is removed + fakeSystemClock.setCurrentTimeMillis(11_000) + activeNotificationListRepository.removeNotif(notif1Info.key) + + // THEN notif2 still has its lastAppVisibleTime + assertThat(latest!![0].key).isEqualTo(notif2Info.key) + assertThat(latest!![0].lastAppVisibleTime).isEqualTo(10_000) + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun shownNotificationChips_sortedByLastAppVisibleTime() = kosmos.runTest { val latest by collectLastValue(underTest.shownNotificationChips) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index 1b5b0d6ff897..e2d1498270c8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -16,18 +16,19 @@ package com.android.systemui.statusbar.chips.ui.viewmodel +import android.content.Context import android.content.DialogInterface import android.content.packageManager import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Expandable +import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue @@ -181,7 +182,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.primaryChip) - assertIsCallChip(latest, notificationKey) + assertIsCallChip(latest, notificationKey, context) } @Test @@ -196,7 +197,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.primaryChip) - assertIsCallChip(latest, callNotificationKey) + assertIsCallChip(latest, callNotificationKey, context) // WHEN the higher priority media projection chip is added mediaProjectionState.value = @@ -241,7 +242,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { mediaProjectionState.value = MediaProjectionState.NotProjecting // THEN the lower priority call is used - assertIsCallChip(latest, callNotificationKey) + assertIsCallChip(latest, callNotificationKey, context) } /** Regression test for b/347726238. */ @@ -395,18 +396,29 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all) } - fun assertIsCallChip(latest: OngoingActivityChipModel?, notificationKey: String) { + fun assertIsCallChip( + latest: OngoingActivityChipModel?, + notificationKey: String, + context: Context, + ) { assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java) assertThat((latest as OngoingActivityChipModel.Active).key).isEqualTo(notificationKey) if (StatusBarConnectedDisplays.isEnabled) { assertNotificationIcon(latest, notificationKey) - return + } else { + val contentDescription = + if (latest.icon is OngoingActivityChipModel.ChipIcon.SingleColorIcon) { + ((latest.icon) as OngoingActivityChipModel.ChipIcon.SingleColorIcon) + .impl + .contentDescription + } else { + (latest.icon as OngoingActivityChipModel.ChipIcon.StatusBarView) + .contentDescription + } + assertThat(contentDescription.loadContentDescription(context)) + .contains(context.getString(R.string.ongoing_call_content_description)) } - val icon = - ((latest.icon) as OngoingActivityChipModel.ChipIcon.SingleColorIcon).impl - as Icon.Resource - assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone) } private fun assertNotificationIcon( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt index e3f93f237742..96c4a59f752d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt @@ -254,7 +254,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val unused by collectLastValue(underTest.chips) assertIsScreenRecordChip(latest!!.primary) - assertIsCallChip(latest!!.secondary, callNotificationKey) + assertIsCallChip(latest!!.secondary, callNotificationKey, context) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel()) } @@ -285,7 +285,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { assertThat(latest!!.active.size).isEqualTo(2) assertIsScreenRecordChip(latest!!.active[0]) - assertIsCallChip(latest!!.active[1], callNotificationKey) + assertIsCallChip(latest!!.active[1], callNotificationKey, context) assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(2) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) @@ -617,7 +617,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val unused by collectLastValue(underTest.chips) assertIsShareToAppChip(latest!!.primary) - assertIsCallChip(latest!!.secondary, callNotificationKey) + assertIsCallChip(latest!!.secondary, callNotificationKey, context) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel()) } @@ -636,7 +636,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { assertThat(latest!!.active.size).isEqualTo(2) assertIsShareToAppChip(latest!!.active[0]) - assertIsCallChip(latest!!.active[1], callNotificationKey) + assertIsCallChip(latest!!.active[1], callNotificationKey, context) assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(2) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) @@ -654,7 +654,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.primaryChip) - assertIsCallChip(latest, callNotificationKey) + assertIsCallChip(latest, callNotificationKey, context) } @DisableChipsModernization @@ -671,7 +671,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.chipsLegacy) val unused by collectLastValue(underTest.chips) - assertIsCallChip(latest!!.primary, callNotificationKey) + assertIsCallChip(latest!!.primary, callNotificationKey, context) assertThat(latest!!.secondary) .isInstanceOf(OngoingActivityChipModel.Inactive::class.java) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel()) @@ -691,7 +691,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val unused by collectLastValue(underTest.chipsLegacy) assertThat(latest!!.active.size).isEqualTo(1) - assertIsCallChip(latest!!.active[0], callNotificationKey) + assertIsCallChip(latest!!.active[0], callNotificationKey, context) assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(3) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) @@ -950,7 +950,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { ) ) - assertIsCallChip(latest!!.primary, callNotificationKey) + assertIsCallChip(latest!!.primary, callNotificationKey, context) assertIsNotifChip(latest!!.secondary, context, firstIcon, "firstNotif") assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel()) } @@ -984,7 +984,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { ) assertThat(latest!!.active.size).isEqualTo(2) - assertIsCallChip(latest!!.active[0], callNotificationKey) + assertIsCallChip(latest!!.active[0], callNotificationKey, context) assertIsNotifChip(latest!!.active[1], context, firstIcon, "firstNotif") assertThat(latest!!.overflow.size).isEqualTo(1) assertIsNotifChip(latest!!.overflow[0], context, secondIcon, "secondNotif") @@ -1011,7 +1011,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { ) assertIsScreenRecordChip(latest!!.primary) - assertIsCallChip(latest!!.secondary, callNotificationKey) + assertIsCallChip(latest!!.secondary, callNotificationKey, context) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel()) } @@ -1057,7 +1057,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { assertThat(latest!!.active.size).isEqualTo(2) assertIsScreenRecordChip(latest!!.active[0]) - assertIsCallChip(latest!!.active[1], callNotificationKey) + assertIsCallChip(latest!!.active[1], callNotificationKey, context) assertThat(latest!!.overflow.size).isEqualTo(1) assertIsNotifChip(latest!!.overflow[0], context, notifIcon, "notif") assertThat(latest!!.inactive.size).isEqualTo(2) @@ -1092,7 +1092,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { addOngoingCallState(callNotificationKey) // THEN the higher priority call chip is used - assertIsCallChip(latest, callNotificationKey) + assertIsCallChip(latest, callNotificationKey, context) // WHEN the higher priority media projection chip is added mediaProjectionState.value = @@ -1145,7 +1145,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { mediaProjectionState.value = MediaProjectionState.NotProjecting // THEN the lower priority call is used - assertIsCallChip(latest, callNotificationKey) + assertIsCallChip(latest, callNotificationKey, context) // WHEN the higher priority call is removed removeOngoingCallState(key = callNotificationKey) @@ -1186,7 +1186,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // THEN the higher priority call chip is used as primary and notif is demoted to // secondary - assertIsCallChip(latest!!.primary, callNotificationKey) + assertIsCallChip(latest!!.primary, callNotificationKey, context) assertIsNotifChip(latest!!.secondary, context, notifIcon, "notif") assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel()) @@ -1201,7 +1201,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // THEN the higher priority media projection chip is used as primary and call is demoted // to secondary (and notif is dropped altogether) assertIsShareToAppChip(latest!!.primary) - assertIsCallChip(latest!!.secondary, callNotificationKey) + assertIsCallChip(latest!!.secondary, callNotificationKey, context) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel()) // WHEN the higher priority screen record chip is added @@ -1264,7 +1264,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // THEN the higher priority call chip and notif are active in that order assertThat(latest!!.active.size).isEqualTo(2) - assertIsCallChip(latest!!.active[0], callNotificationKey) + assertIsCallChip(latest!!.active[0], callNotificationKey, context) assertIsNotifChip(latest!!.active[1], context, notifIcon, "notif") assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(3) @@ -1282,7 +1282,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // notif is demoted to overflow assertThat(latest!!.active.size).isEqualTo(2) assertIsShareToAppChip(latest!!.active[0]) - assertIsCallChip(latest!!.active[1], callNotificationKey) + assertIsCallChip(latest!!.active[1], callNotificationKey, context) assertThat(latest!!.overflow.size).isEqualTo(1) assertIsNotifChip(latest!!.overflow[0], context, notifIcon, "notif") assertThat(latest!!.inactive.size).isEqualTo(2) @@ -1295,7 +1295,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // media projection and notif are demoted in overflow assertThat(latest!!.active.size).isEqualTo(2) assertIsScreenRecordChip(latest!!.active[0]) - assertIsCallChip(latest!!.active[1], callNotificationKey) + assertIsCallChip(latest!!.active[1], callNotificationKey, context) assertThat(latest!!.overflow.size).isEqualTo(2) assertIsShareToAppChip(latest!!.overflow[0]) assertIsNotifChip(latest!!.overflow[1], context, notifIcon, "notif") diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt index 0f1cdac05f7a..fdc6657acd1d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -31,6 +31,8 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource +import com.android.systemui.shade.domain.interactor.enableDualShade +import com.android.systemui.shade.domain.interactor.enableSingleShade import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_DELAYED_STACK_FADE_IN @@ -69,6 +71,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { @Test fun updateBounds() = testScope.runTest { + kosmos.enableSingleShade() val radius = MutableStateFlow(32) val leftOffset = MutableStateFlow(0) val shape by @@ -106,7 +109,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { ) ) - // When: QuickSettings shows up full screen + // When: QuickSettings shows up full screen on single shade. fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) val transitionState = MutableStateFlow<ObservableTransitionState>( @@ -115,6 +118,10 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { sceneInteractor.setTransitionState(transitionState) // Then: shape is null assertThat(shape).isNull() + + // Same scenario on Dual Shade, shape should have clipping bounds + kosmos.enableDualShade() + assertThat(shape).isNotNull() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt index 83fd5dcd5f82..a13b8647e170 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt @@ -34,123 +34,122 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @RunWithLooper -class BundleEntryTest : SysuiTestCase() { - private lateinit var underTest: BundleEntry +class BundleEntryAdapterTest : SysuiTestCase() { + private lateinit var underTest: BundleEntryAdapter - @get:Rule - val setFlagsRule = SetFlagsRule() + @get:Rule val setFlagsRule = SetFlagsRule() @Before fun setUp() { - underTest = BundleEntry("key") + underTest = BundleEntryAdapter(BundleEntry("key")) } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getParent_adapter() { - assertThat(underTest.entryAdapter.parent).isEqualTo(GroupEntry.ROOT_ENTRY) + assertThat(underTest.parent).isEqualTo(GroupEntry.ROOT_ENTRY) } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isTopLevelEntry_adapter() { - assertThat(underTest.entryAdapter.isTopLevelEntry).isTrue() + assertThat(underTest.isTopLevelEntry).isTrue() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getRow_adapter() { - assertThat(underTest.entryAdapter.row).isNull() + assertThat(underTest.row).isNull() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isGroupRoot_adapter() { - assertThat(underTest.entryAdapter.isGroupRoot).isTrue() + assertThat(underTest.isGroupRoot).isTrue() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getKey_adapter() { - assertThat(underTest.entryAdapter.key).isEqualTo("key") + assertThat(underTest.key).isEqualTo("key") } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isClearable_adapter() { - assertThat(underTest.entryAdapter.isClearable).isTrue() + assertThat(underTest.isClearable).isTrue() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getSummarization_adapter() { - assertThat(underTest.entryAdapter.summarization).isNull() + assertThat(underTest.summarization).isNull() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getContrastedColor_adapter() { - assertThat(underTest.entryAdapter.getContrastedColor(context, false, Color.WHITE)) + assertThat(underTest.getContrastedColor(context, false, Color.WHITE)) .isEqualTo(Notification.COLOR_DEFAULT) } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun canPeek_adapter() { - assertThat(underTest.entryAdapter.canPeek()).isFalse() + assertThat(underTest.canPeek()).isFalse() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getWhen_adapter() { - assertThat(underTest.entryAdapter.`when`).isEqualTo(0) + assertThat(underTest.`when`).isEqualTo(0) } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isColorized() { - assertThat(underTest.entryAdapter.isColorized).isFalse() + assertThat(underTest.isColorized).isFalse() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getSbn() { - assertThat(underTest.entryAdapter.sbn).isNull() + assertThat(underTest.sbn).isNull() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun canDragAndDrop() { - assertThat(underTest.entryAdapter.canDragAndDrop()).isFalse() + assertThat(underTest.canDragAndDrop()).isFalse() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isBubble() { - assertThat(underTest.entryAdapter.isBubbleCapable).isFalse() + assertThat(underTest.isBubbleCapable).isFalse() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getStyle() { - assertThat(underTest.entryAdapter.style).isNull() + assertThat(underTest.style).isNull() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getSectionBucket() { - assertThat(underTest.entryAdapter.sectionBucket).isEqualTo(underTest.bucket) + assertThat(underTest.sectionBucket).isEqualTo(underTest.entry.bucket) } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isAmbient() { - assertThat(underTest.entryAdapter.isAmbient).isFalse() + assertThat(underTest.isAmbient).isFalse() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun canShowFullScreen() { - assertThat(underTest.entryAdapter.isFullScreenCapable()).isFalse() + assertThat(underTest.isFullScreenCapable()).isFalse() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt new file mode 100644 index 000000000000..b6889afa4e8a --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection + +import android.app.ActivityManager +import android.app.Notification +import android.app.NotificationManager +import android.app.PendingIntent +import android.os.UserHandle +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R +import com.android.systemui.statusbar.RankingBuilder +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.entryAdapterFactory +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi +import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +@RunWithLooper +class NotificationEntryAdapterTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private val factory: EntryAdapterFactory = kosmos.entryAdapterFactory + private lateinit var underTest: NotificationEntryAdapter + + @get:Rule val setFlagsRule = SetFlagsRule() + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getParent_adapter() { + val ge = GroupEntryBuilder().build() + val notification: Notification = + Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + + val entry = + NotificationEntryBuilder() + .setNotification(notification) + .setUser(UserHandle(ActivityManager.getCurrentUser())) + .setParent(ge) + .build() + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.parent).isEqualTo(entry.parent) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun isTopLevelEntry_adapter() { + val notification: Notification = + Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + + val entry = + NotificationEntryBuilder() + .setNotification(notification) + .setUser(UserHandle(ActivityManager.getCurrentUser())) + .setParent(GroupEntry.ROOT_ENTRY) + .build() + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.isTopLevelEntry).isTrue() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getKey_adapter() { + val notification: Notification = + Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + + val entry = + NotificationEntryBuilder() + .setNotification(notification) + .setUser(UserHandle(ActivityManager.getCurrentUser())) + .build() + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.key).isEqualTo(entry.key) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getRow_adapter() { + val row = Mockito.mock(ExpandableNotificationRow::class.java) + val notification: Notification = + Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + + val entry = + NotificationEntryBuilder() + .setNotification(notification) + .setUser(UserHandle(ActivityManager.getCurrentUser())) + .build() + entry.row = row + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.row).isEqualTo(entry.row) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun isGroupRoot_adapter_groupSummary() { + val row = Mockito.mock(ExpandableNotificationRow::class.java) + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setGroupSummary(true) + .setGroup("key") + .build() + + val entry = + NotificationEntryBuilder() + .setNotification(notification) + .setUser(UserHandle(ActivityManager.getCurrentUser())) + .setParent(GroupEntry.ROOT_ENTRY) + .build() + entry.row = row + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.isGroupRoot).isFalse() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun isGroupRoot_adapter_groupChild() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setGroupSummary(true) + .setGroup("key") + .build() + + val parent = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() + val groupEntry = GroupEntryBuilder().setSummary(parent) + + val entry = + NotificationEntryBuilder() + .setNotification(notification) + .setUser(UserHandle(ActivityManager.getCurrentUser())) + .setParent(groupEntry.build()) + .build() + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.isGroupRoot).isFalse() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun isClearable_adapter() { + val row = Mockito.mock(ExpandableNotificationRow::class.java) + val notification: Notification = + Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + + val entry = + NotificationEntryBuilder() + .setNotification(notification) + .setUser(UserHandle(ActivityManager.getCurrentUser())) + .build() + entry.row = row + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.isClearable).isEqualTo(entry.isClearable) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getSummarization_adapter() { + val row = Mockito.mock(ExpandableNotificationRow::class.java) + val notification: Notification = + Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + + val entry = + NotificationEntryBuilder() + .setNotification(notification) + .setUser(UserHandle(ActivityManager.getCurrentUser())) + .build() + val ranking = RankingBuilder(entry.ranking).setSummarization("hello").build() + entry.setRanking(ranking) + entry.row = row + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.summarization).isEqualTo("hello") + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getIcons_adapter() { + val row = Mockito.mock(ExpandableNotificationRow::class.java) + val notification: Notification = + Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + + val entry = + NotificationEntryBuilder() + .setNotification(notification) + .setUser(UserHandle(ActivityManager.getCurrentUser())) + .build() + entry.row = row + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.icons).isEqualTo(entry.icons) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun isColorized() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setColorized(true) + .build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.isColorized).isEqualTo(entry.sbn.notification.isColorized) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getSbn() { + val notification: Notification = + Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.sbn).isEqualTo(entry.sbn) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun canDragAndDrop() { + val pi = Mockito.mock(PendingIntent::class.java) + Mockito.`when`(pi.isActivity).thenReturn(true) + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setContentIntent(pi) + .build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.canDragAndDrop()).isTrue() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun isBubble() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setFlag(Notification.FLAG_BUBBLE, true) + .build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.isBubbleCapable).isEqualTo(entry.isBubble) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getStyle() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setStyle(Notification.BigTextStyle()) + .build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.style).isEqualTo(entry.notificationStyle) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getSectionBucket() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setStyle(Notification.BigTextStyle()) + .build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + entry.bucket = BUCKET_ALERTING + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.sectionBucket).isEqualTo(BUCKET_ALERTING) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun isAmbient() { + val notification: Notification = + Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + + val entry = + NotificationEntryBuilder() + .setNotification(notification) + .setImportance(NotificationManager.IMPORTANCE_MIN) + .build() + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.isAmbient).isTrue() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun canShowFullScreen() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setFullScreenIntent(Mockito.mock(PendingIntent::class.java), true) + .build() + + val entry = + NotificationEntryBuilder() + .setNotification(notification) + .setImportance(NotificationManager.IMPORTANCE_MIN) + .build() + + underTest = factory.create(entry) as NotificationEntryAdapter + assertThat(underTest.isFullScreenCapable).isTrue() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun onNotificationBubbleIconClicked() { + val notification: Notification = + Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + + val entry = + NotificationEntryBuilder() + .setNotification(notification) + .setImportance(NotificationManager.IMPORTANCE_MIN) + .build() + + underTest = factory.create(entry) as NotificationEntryAdapter + + underTest.onNotificationBubbleIconClicked() + verify((factory as? EntryAdapterFactoryImpl)?.getNotificationActivityStarter()) + ?.onNotificationBubbleIconClicked(entry) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 481081344dbb..790b2c343a11 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -21,24 +21,17 @@ import static android.app.Notification.CATEGORY_CALL; import static android.app.Notification.CATEGORY_EVENT; import static android.app.Notification.CATEGORY_MESSAGE; import static android.app.Notification.CATEGORY_REMINDER; -import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED; import static android.app.Notification.FLAG_PROMOTED_ONGOING; -import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn; -import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING; - -import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.Notification; @@ -66,8 +59,6 @@ import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; @@ -458,336 +449,6 @@ public class NotificationEntryTest extends SysuiTestCase { // no crash, good } - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void getParent_adapter() { - GroupEntry ge = new GroupEntryBuilder() - .build(); - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_NAME) - .setOpPkg(TEST_PACKAGE_NAME) - .setUid(TEST_UID) - .setChannel(mChannel) - .setId(mId++) - .setNotification(notification) - .setUser(new UserHandle(ActivityManager.getCurrentUser())) - .setParent(ge) - .build(); - - assertThat(entry.getEntryAdapter().getParent()).isEqualTo(entry.getParent()); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void isTopLevelEntry_adapter() { - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_NAME) - .setOpPkg(TEST_PACKAGE_NAME) - .setUid(TEST_UID) - .setChannel(mChannel) - .setId(mId++) - .setNotification(notification) - .setUser(new UserHandle(ActivityManager.getCurrentUser())) - .setParent(GroupEntry.ROOT_ENTRY) - .build(); - - assertThat(entry.getEntryAdapter().isTopLevelEntry()).isTrue(); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void getKey_adapter() { - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_NAME) - .setOpPkg(TEST_PACKAGE_NAME) - .setUid(TEST_UID) - .setChannel(mChannel) - .setId(mId++) - .setNotification(notification) - .setUser(new UserHandle(ActivityManager.getCurrentUser())) - .build(); - - assertThat(entry.getEntryAdapter().getKey()).isEqualTo(entry.getKey()); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void getRow_adapter() { - ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_NAME) - .setOpPkg(TEST_PACKAGE_NAME) - .setUid(TEST_UID) - .setChannel(mChannel) - .setId(mId++) - .setNotification(notification) - .setUser(new UserHandle(ActivityManager.getCurrentUser())) - .build(); - entry.setRow(row); - - assertThat(entry.getEntryAdapter().getRow()).isEqualTo(entry.getRow()); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void isGroupRoot_adapter_groupSummary() { - ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setGroupSummary(true) - .setGroup("key") - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_NAME) - .setOpPkg(TEST_PACKAGE_NAME) - .setUid(TEST_UID) - .setChannel(mChannel) - .setId(mId++) - .setNotification(notification) - .setUser(new UserHandle(ActivityManager.getCurrentUser())) - .setParent(GroupEntry.ROOT_ENTRY) - .build(); - entry.setRow(row); - - assertThat(entry.getEntryAdapter().isGroupRoot()).isFalse(); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void isGroupRoot_adapter_groupChild() { - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setGroupSummary(true) - .setGroup("key") - .build(); - - NotificationEntry parent = new NotificationEntryBuilder() - .setParent(GroupEntry.ROOT_ENTRY) - .build(); - GroupEntryBuilder groupEntry = new GroupEntryBuilder() - .setSummary(parent); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_NAME) - .setOpPkg(TEST_PACKAGE_NAME) - .setUid(TEST_UID) - .setChannel(mChannel) - .setId(mId++) - .setNotification(notification) - .setUser(new UserHandle(ActivityManager.getCurrentUser())) - .setParent(groupEntry.build()) - .build(); - - assertThat(entry.getEntryAdapter().isGroupRoot()).isFalse(); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void isClearable_adapter() { - ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_NAME) - .setOpPkg(TEST_PACKAGE_NAME) - .setUid(TEST_UID) - .setChannel(mChannel) - .setId(mId++) - .setNotification(notification) - .setUser(new UserHandle(ActivityManager.getCurrentUser())) - .build(); - entry.setRow(row); - - assertThat(entry.getEntryAdapter().isClearable()).isEqualTo(entry.isClearable()); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void getSummarization_adapter() { - ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_NAME) - .setOpPkg(TEST_PACKAGE_NAME) - .setUid(TEST_UID) - .setChannel(mChannel) - .setId(mId++) - .setNotification(notification) - .setUser(new UserHandle(ActivityManager.getCurrentUser())) - .build(); - Ranking ranking = new RankingBuilder(entry.getRanking()) - .setSummarization("hello") - .build(); - entry.setRanking(ranking); - entry.setRow(row); - - assertThat(entry.getEntryAdapter().getSummarization()).isEqualTo("hello"); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void getIcons_adapter() { - ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_NAME) - .setOpPkg(TEST_PACKAGE_NAME) - .setUid(TEST_UID) - .setChannel(mChannel) - .setId(mId++) - .setNotification(notification) - .setUser(new UserHandle(ActivityManager.getCurrentUser())) - .build(); - entry.setRow(row); - - assertThat(entry.getEntryAdapter().getIcons()).isEqualTo(entry.getIcons()); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void isColorized() { - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setColorized(true) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .build(); - assertThat(entry.getEntryAdapter().isColorized()).isEqualTo( - entry.getSbn().getNotification().isColorized()); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void getSbn() { - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .build(); - assertThat(entry.getEntryAdapter().getSbn()).isEqualTo( - entry.getSbn()); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void canDragAndDrop() { - PendingIntent pi = mock(PendingIntent.class); - when(pi.isActivity()).thenReturn(true); - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setContentIntent(pi) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .build(); - assertThat(entry.getEntryAdapter().canDragAndDrop()).isTrue(); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void isBubble() { - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setFlag(FLAG_BUBBLE, true) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .build(); - assertThat(entry.getEntryAdapter().isBubbleCapable()).isEqualTo(entry.isBubble()); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void getStyle() { - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setStyle(new Notification.BigTextStyle()) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .build(); - assertThat(entry.getEntryAdapter().getStyle()).isEqualTo(entry.getNotificationStyle()); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void getSectionBucket() { - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setStyle(new Notification.BigTextStyle()) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .build(); - entry.setBucket(BUCKET_ALERTING); - - assertThat(entry.getEntryAdapter().getSectionBucket()).isEqualTo(BUCKET_ALERTING); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void isAmbient() { - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setImportance(IMPORTANCE_MIN) - .build(); - - assertThat(entry.getEntryAdapter().isAmbient()).isTrue(); - } - - @Test - @EnableFlags(NotificationBundleUi.FLAG_NAME) - public void canShowFullScreen() { - Notification notification = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setFullScreenIntent(mock(PendingIntent.class), true) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setImportance(IMPORTANCE_MIN) - .build(); - - assertThat(entry.getEntryAdapter().isFullScreenCapable()).isTrue(); - } - private Notification.Action createContextualAction(String title) { return new Notification.Action.Builder( Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt index 8e6aedcae506..9e27c79d54b8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.notification.collection.render -import android.os.Build -import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule @@ -26,6 +24,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.log.assertLogsWtf +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.ListEntry @@ -36,12 +35,13 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper +import com.android.systemui.statusbar.notification.row.entryAdapterFactory import com.android.systemui.statusbar.notification.shared.NotificationBundleUi +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat -import org.junit.Assume import org.junit.Before import org.junit.Rule import org.junit.Test @@ -54,11 +54,11 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidJUnit4::class) class GroupExpansionManagerTest : SysuiTestCase() { - @get:Rule - val setFlagsRule = SetFlagsRule() + @get:Rule val setFlagsRule = SetFlagsRule() private lateinit var underTest: GroupExpansionManagerImpl + private val kosmos = testKosmos() private lateinit var testHelper: NotificationTestHelper private val dumpManager: DumpManager = mock() private val groupMembershipManager: GroupMembershipManager = mock() @@ -66,15 +66,14 @@ class GroupExpansionManagerTest : SysuiTestCase() { private val pipeline: NotifPipeline = mock() private lateinit var beforeRenderListListener: OnBeforeRenderListListener + private val factory: EntryAdapterFactoryImpl = kosmos.entryAdapterFactory private lateinit var summary1: NotificationEntry private lateinit var summary2: NotificationEntry private lateinit var entries: List<ListEntry> private fun notificationEntry(pkg: String, id: Int, parent: ExpandableNotificationRow?) = NotificationEntryBuilder().setPkg(pkg).setId(id).build().apply { - row = testHelper.createRow().apply { - setIsChildInGroup(true, parent) - } + row = testHelper.createRow().apply { setIsChildInGroup(true, parent) } } @Before @@ -91,7 +90,7 @@ class GroupExpansionManagerTest : SysuiTestCase() { listOf( notificationEntry("foo", 2, summary1.row), notificationEntry("foo", 3, summary1.row), - notificationEntry("foo", 4, summary1.row) + notificationEntry("foo", 4, summary1.row), ) ) .build(), @@ -101,11 +100,11 @@ class GroupExpansionManagerTest : SysuiTestCase() { listOf( notificationEntry("bar", 2, summary2.row), notificationEntry("bar", 3, summary2.row), - notificationEntry("bar", 4, summary2.row) + notificationEntry("bar", 4, summary2.row), ) ) .build(), - notificationEntry("baz", 1, null) + notificationEntry("baz", 1, null), ) whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1) @@ -135,18 +134,20 @@ class GroupExpansionManagerTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun notifyOnlyOnChange_withEntryAdapter() { + val entryAdapter1 = factory.create(summary1) + val entryAdapter2 = factory.create(summary2) var listenerCalledCount = 0 underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } - underTest.setGroupExpanded(summary1.entryAdapter, false) + underTest.setGroupExpanded(entryAdapter1, false) assertThat(listenerCalledCount).isEqualTo(0) - underTest.setGroupExpanded(summary1.entryAdapter, true) + underTest.setGroupExpanded(entryAdapter1, true) assertThat(listenerCalledCount).isEqualTo(1) - underTest.setGroupExpanded(summary2.entryAdapter, true) + underTest.setGroupExpanded(entryAdapter2, true) assertThat(listenerCalledCount).isEqualTo(2) - underTest.setGroupExpanded(summary1.entryAdapter, true) + underTest.setGroupExpanded(entryAdapter1, true) assertThat(listenerCalledCount).isEqualTo(2) - underTest.setGroupExpanded(summary2.entryAdapter, false) + underTest.setGroupExpanded(entryAdapter2, false) assertThat(listenerCalledCount).isEqualTo(3) } @@ -168,16 +169,17 @@ class GroupExpansionManagerTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun expandUnattachedEntryAdapter() { + val entryAdapter = factory.create(summary1) // First, expand the entry when it is attached. - underTest.setGroupExpanded(summary1.entryAdapter, true) - assertThat(underTest.isGroupExpanded(summary1.entryAdapter)).isTrue() + underTest.setGroupExpanded(entryAdapter, true) + assertThat(underTest.isGroupExpanded(entryAdapter)).isTrue() // Un-attach it, and un-expand it. NotificationEntryBuilder.setNewParent(summary1, null) - underTest.setGroupExpanded(summary1.entryAdapter, false) + underTest.setGroupExpanded(entryAdapter, false) // Expanding again should throw. - assertLogsWtf { underTest.setGroupExpanded(summary1.entryAdapter, true) } + assertLogsWtf { underTest.setGroupExpanded(entryAdapter, true) } } @Test @@ -207,6 +209,7 @@ class GroupExpansionManagerTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun syncWithPipeline_withEntryAdapter() { + val entryAdapter = factory.create(summary1) underTest.attach(pipeline) beforeRenderListListener = withArgCaptor { verify(pipeline).addOnBeforeRenderListListener(capture()) @@ -219,7 +222,7 @@ class GroupExpansionManagerTest : SysuiTestCase() { verify(listener, never()).onGroupExpansionChange(any(), any()) // Expand one of the groups. - underTest.setGroupExpanded(summary1.entryAdapter, true) + underTest.setGroupExpanded(entryAdapter, true) verify(listener).onGroupExpansionChange(summary1.row, true) // Empty the pipeline list and verify that the group is no longer expanded. @@ -231,11 +234,15 @@ class GroupExpansionManagerTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isGroupExpanded() { - underTest.setGroupExpanded(summary1.entryAdapter, true) - - assertThat(underTest.isGroupExpanded(summary1.entryAdapter)).isTrue(); - assertThat(underTest.isGroupExpanded( - (entries[0] as? GroupEntry)?.getChildren()?.get(0)?.entryAdapter)) - .isTrue(); + val entryAdapter = summary1.row.entryAdapter + underTest.setGroupExpanded(entryAdapter, true) + + assertThat(underTest.isGroupExpanded(entryAdapter)).isTrue() + assertThat( + underTest.isGroupExpanded( + (entries[0] as? GroupEntry)?.getChildren()?.get(0)?.row?.entryAdapter + ) + ) + .isTrue() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt index 2bbf094021cf..0ccf58507696 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt @@ -22,10 +22,13 @@ import android.platform.test.flag.junit.SetFlagsRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.row.entryAdapterFactory import com.android.systemui.statusbar.notification.shared.NotificationBundleUi +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test @@ -35,8 +38,10 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class GroupMembershipManagerTest : SysuiTestCase() { - @get:Rule - val setFlagsRule = SetFlagsRule() + @get:Rule val setFlagsRule = SetFlagsRule() + + private val kosmos = testKosmos() + private val factory: EntryAdapterFactoryImpl = kosmos.entryAdapterFactory private var underTest = GroupMembershipManagerImpl() @@ -144,14 +149,14 @@ class GroupMembershipManagerTest : SysuiTestCase() { @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isChildEntryAdapterInGroup_topLevel() { val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() - assertThat(underTest.isChildInGroup(topLevelEntry.entryAdapter)).isFalse() + assertThat(underTest.isChildInGroup(factory.create(topLevelEntry))).isFalse() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isChildEntryAdapterInGroup_noParent() { val noParentEntry = NotificationEntryBuilder().setParent(null).build() - assertThat(underTest.isChildInGroup(noParentEntry.entryAdapter)).isFalse() + assertThat(underTest.isChildInGroup(factory.create(noParentEntry))).isFalse() } @Test @@ -165,7 +170,7 @@ class GroupMembershipManagerTest : SysuiTestCase() { .build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - assertThat(underTest.isChildInGroup(summary.entryAdapter)).isFalse() + assertThat(underTest.isChildInGroup(factory.create(summary))).isFalse() } @Test @@ -180,14 +185,14 @@ class GroupMembershipManagerTest : SysuiTestCase() { val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build() - assertThat(underTest.isChildInGroup(entry.entryAdapter)).isTrue() + assertThat(underTest.isChildInGroup(factory.create(entry))).isTrue() } @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isGroupRoot_topLevelEntry() { val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() - assertThat(underTest.isGroupRoot(entry.entryAdapter)).isFalse() + assertThat(underTest.isGroupRoot(factory.create(entry))).isFalse() } @Test @@ -201,7 +206,7 @@ class GroupMembershipManagerTest : SysuiTestCase() { .build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - assertThat(underTest.isGroupRoot(summary.entryAdapter)).isTrue() + assertThat(underTest.isGroupRoot(factory.create(summary))).isTrue() } @Test @@ -216,6 +221,6 @@ class GroupMembershipManagerTest : SysuiTestCase() { val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build() - assertThat(underTest.isGroupRoot(entry.entryAdapter)).isFalse() + assertThat(underTest.isGroupRoot(factory.create(entry))).isFalse() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt index f060caea4f24..bb12eff51288 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt @@ -37,9 +37,12 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.statusbar.SmartReplyController import com.android.systemui.statusbar.notification.ColorUpdateLogger +import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.collection.EntryAdapter +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactory import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider import com.android.systemui.statusbar.notification.collection.render.FakeNodeController import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager @@ -47,6 +50,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI +import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger import com.android.systemui.statusbar.notification.stack.NotificationListContainer @@ -112,6 +116,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { private val uiEventLogger: UiEventLogger = mock() private val msdlPlayer: MSDLPlayer = mock() private val rebindingTracker: NotificationRebindingTracker = mock() + private val entryAdapterFactory: EntryAdapterFactory = mock() private lateinit var controller: ExpandableNotificationRowController @Before @@ -154,6 +159,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { uiEventLogger, msdlPlayer, rebindingTracker, + entryAdapterFactory, ) whenever(view.childrenContainer).thenReturn(childrenContainer) @@ -277,8 +283,10 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { whenever(view.privateLayout).thenReturn(childView) val entryAdapter = mock(EntryAdapter::class.java) val sbn = - SbnBuilder().setNotification(Notification.Builder(mContext).build()) - .setUser(UserHandle.of(view.entry.sbn.userId)).build() + SbnBuilder() + .setNotification(Notification.Builder(mContext).build()) + .setUser(UserHandle.of(view.entry.sbn.userId)) + .build() whenever(entryAdapter.sbn).thenReturn(sbn) whenever(view.entryAdapter).thenReturn(entryAdapter) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index c376fad1503f..8e7733bb23ca 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -70,10 +70,14 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.ConversationNotificationProcessor; +import com.android.systemui.statusbar.notification.NotificationActivityStarter; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; @@ -87,6 +91,7 @@ import com.android.systemui.statusbar.notification.promoted.FakePromotedNotifica import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider; import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -101,11 +106,6 @@ import com.android.systemui.util.time.SystemClock; import com.android.systemui.util.time.SystemClockImpl; import com.android.systemui.wmshell.BubblesTestActivity; -import kotlin.coroutines.CoroutineContext; - -import kotlinx.coroutines.flow.StateFlowKt; -import kotlinx.coroutines.test.TestScope; - import org.mockito.ArgumentCaptor; import java.util.List; @@ -114,6 +114,10 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import kotlin.coroutines.CoroutineContext; +import kotlinx.coroutines.flow.StateFlowKt; +import kotlinx.coroutines.test.TestScope; + /** * A helper class to create {@link ExpandableNotificationRow} (for both individual and group * notifications). @@ -734,7 +738,16 @@ public class NotificationTestHelper { mBindPipelineEntryListener.onEntryInit(entry); mBindPipeline.manageRow(entry, row); + EntryAdapter entryAdapter = new EntryAdapterFactoryImpl( + mock(NotificationActivityStarter.class), + mock(MetricsLogger.class), + mock(PeopleNotificationIdentifier.class), + mock(NotificationIconStyleProvider.class), + mock(VisualStabilityCoordinator.class) + ).create(entry); + row.initialize( + entryAdapter, entry, mock(RemoteInputViewSubcomponent.Factory.class), APP_NAME, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index 14bbd38ece2c..761ed6186afc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java @@ -27,6 +27,7 @@ import android.view.NotificationHeaderView; import android.view.View; import android.widget.RemoteViews; +import androidx.compose.ui.platform.ComposeView; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -35,7 +36,9 @@ import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; +import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModelImpl; import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import org.junit.Assert; import org.junit.Before; @@ -273,6 +276,22 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { Assert.assertEquals(1f, header.getTopRoundness(), 0.001f); } + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + public void initBundleHeader_composeview_is_initialized_once() { + View currentView = mChildrenContainer.getChildAt(mChildrenContainer.getChildCount() - 1); + Assert.assertFalse(currentView instanceof ComposeView); + + BundleHeaderViewModelImpl viewModel = new BundleHeaderViewModelImpl(); + mChildrenContainer.initBundleHeader(viewModel); + currentView = mChildrenContainer.getChildAt(mChildrenContainer.getChildCount() - 1); + Assert.assertTrue(currentView instanceof ComposeView); + + mChildrenContainer.initBundleHeader(viewModel); + View finalView = mChildrenContainer.getChildAt(mChildrenContainer.getChildCount() - 1); + Assert.assertEquals(currentView, finalView); + } + private NotificationHeaderView createHeaderView(boolean lowPriority) { Notification notification = mNotificationTestHelper.createNotification(); final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt index 47967b3f0828..670c195ee5a1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.commandQueue import com.android.systemui.statusbar.lockscreenShadeTransitionController +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.domain.interactor.notificationAlertsInteractor @@ -54,6 +55,7 @@ import com.android.systemui.statusbar.notification.interruption.VisualInterrupti import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.entryAdapterFactory import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController import com.android.systemui.statusbar.notification.visualInterruptionDecisionProvider @@ -105,10 +107,14 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() { private val notificationAlertsInteractor = kosmos.notificationAlertsInteractor private val visualInterruptionDecisionProvider = kosmos.visualInterruptionDecisionProvider + private lateinit var factory: EntryAdapterFactoryImpl + private lateinit var underTest: StatusBarNotificationPresenter @Before fun setup() { + factory = kosmos.entryAdapterFactory + underTest = createPresenter() if (VisualInterruptionRefactor.isEnabled) { verifyAndCaptureSuppressors() @@ -451,7 +457,7 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() { private fun createRow(entry: NotificationEntry): ExpandableNotificationRow { val row: ExpandableNotificationRow = mock() if (NotificationBundleUi.isEnabled) { - whenever(row.entryAdapter).thenReturn(entry.entryAdapter) + whenever(row.entryAdapter).thenReturn(factory.create(entry)) } else { whenever(row.entry).thenReturn(entry) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java index 345ddae42798..c23e0e733b41 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java @@ -46,7 +46,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntry.NotifEntryAdapter; +import com.android.systemui.statusbar.notification.collection.NotificationEntryAdapter; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationContentView; @@ -135,7 +135,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class); final NotificationContentView privateLayout = mock(NotificationContentView.class); final NotificationEntry enrEntry = mock(NotificationEntry.class); - final NotifEntryAdapter enrEntryAdapter = mock(NotifEntryAdapter.class); + final NotificationEntryAdapter enrEntryAdapter = mock(NotificationEntryAdapter.class); when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); @@ -178,7 +178,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { // THEN verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class)); - verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class)); + verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class)); verify(enr).setUserExpanded(true); verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner); } @@ -203,7 +203,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { // THEN verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class)); - verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class)); + verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class)); verify(enr).setUserExpanded(true); verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner); } @@ -217,7 +217,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class); final NotificationContentView privateLayout = mock(NotificationContentView.class); final NotificationEntry enrEntry = mock(NotificationEntry.class); - final NotifEntryAdapter enrEntryAdapter = mock(NotifEntryAdapter.class); + final NotificationEntryAdapter enrEntryAdapter = mock(NotificationEntryAdapter.class); when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); @@ -260,7 +260,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { // THEN verify(mGroupExpansionManager, never()).toggleGroupExpansion(enrEntry); - verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class)); + verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class)); verify(enr, never()).setUserExpanded(anyBoolean()); verify(privateLayout, never()).setOnExpandedVisibleListener(any()); } @@ -290,7 +290,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner); verify(enr, never()).setUserExpanded(anyBoolean()); verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class)); - verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class)); + verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class)); } @Test @@ -318,7 +318,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { verify(privateLayout, never()).setOnExpandedVisibleListener(onExpandedVisibleRunner); verify(enr, never()).setUserExpanded(anyBoolean()); verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class)); - verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class)); + verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class)); } @Test @@ -346,7 +346,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner); verify(enr, never()).setUserExpanded(anyBoolean()); verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class)); - verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class)); + verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class)); } @Test @@ -374,6 +374,6 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { verify(privateLayout, never()).setOnExpandedVisibleListener(onExpandedVisibleRunner); verify(enr, never()).setUserExpanded(anyBoolean()); verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class)); - verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class)); + verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class)); } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt index ee5979c6ed9f..12cf3b6cd2cf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt @@ -1018,7 +1018,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { addOngoingCallState(key = "call") assertIsScreenRecordChip(latest!!.chips.active[0]) - assertIsCallChip(latest!!.chips.active[1], "call") + assertIsCallChip(latest!!.chips.active[1], "call", context) } @Test diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt index 4c1f6450dd78..b52db83d513c 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt @@ -42,9 +42,13 @@ interface ClockController { isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float, - onBoundsChanged: (RectF) -> Unit, + clockListener: ClockEventListener?, ) /** Optional method for dumping debug information */ fun dump(pw: PrintWriter) } + +interface ClockEventListener { + fun onBoundsChanged(bounds: RectF) +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt index 3e39ae9a3fe5..0c46e0784a31 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt +++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt @@ -18,4 +18,7 @@ package com.android.systemui.dagger.qualifiers import javax.inject.Qualifier /** Annotates a class that is display specific. */ -@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class DisplaySpecific +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +public annotation class DisplaySpecific diff --git a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Main.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Main.java index 7b097740ff11..7b097740ff11 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Main.java +++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Main.java diff --git a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/UiBackground.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/UiBackground.java index 3d37468c322a..3d37468c322a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/UiBackground.java +++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/UiBackground.java diff --git a/packages/SystemUI/res/layout/magic_action_button.xml b/packages/SystemUI/res/layout/magic_action_button.xml index 381d6b17dec5..63fc1e485635 100644 --- a/packages/SystemUI/res/layout/magic_action_button.xml +++ b/packages/SystemUI/res/layout/magic_action_button.xml @@ -6,7 +6,7 @@ android:background="@drawable/magic_action_button_background" android:drawablePadding="@dimen/magic_action_button_drawable_padding" android:ellipsize="none" - android:fontFamily="google-sans-flex" + android:fontFamily="google-sans-flex-medium" android:gravity="center" android:minWidth="0dp" android:paddingHorizontal="@dimen/magic_action_button_padding_horizontal" diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index f41eaec8e18b..a1fa54cf592d 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -40,7 +40,7 @@ android:layout_marginBottom="@dimen/volume_dialog_components_spacing" android:clipChildren="false" app:layout_constraintBottom_toTopOf="@id/volume_dialog_main_slider_container" - app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container" + app:layout_constraintEnd_toEndOf="@id/volume_dialog_background" app:layout_constraintHeight_default="spread" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -70,9 +70,9 @@ android:layout_marginTop="@dimen/volume_dialog_components_spacing" android:clipChildren="false" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container" + app:layout_constraintEnd_toEndOf="@id/volume_dialog_background" app:layout_constraintHeight_default="wrap" - app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container" + app:layout_constraintStart_toStartOf="@id/volume_dialog_background" app:layout_constraintTop_toBottomOf="@id/volume_dialog_main_slider_container" app:layout_constraintVertical_bias="0" app:layout_constraintWidth_default="wrap"> diff --git a/packages/SystemUI/res/layout/volume_dialog_top_section.xml b/packages/SystemUI/res/layout/volume_dialog_top_section.xml index 29f52480bfe0..b7455471d9a6 100644 --- a/packages/SystemUI/res/layout/volume_dialog_top_section.xml +++ b/packages/SystemUI/res/layout/volume_dialog_top_section.xml @@ -22,7 +22,7 @@ android:clipChildren="false" android:clipToPadding="false" android:gravity="center" - android:paddingEnd="@dimen/volume_dialog_ringer_drawer_diff_end_margin" + android:paddingEnd="@dimen/volume_dialog_buttons_margin" app:layoutDescription="@xml/volume_dialog_ringer_drawer_motion_scene"> <View diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index c8e31079fca0..8342a9cc244b 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -2189,11 +2189,6 @@ <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen> <dimen name="volume_dialog_ringer_drawer_margin">@dimen/volume_dialog_buttons_margin</dimen> - <!-- - (volume_dialog_slider_width - volume_dialog_button_size) / 2 - This centers ringer drawer against the volume slider - --> - <dimen name="volume_dialog_ringer_drawer_diff_end_margin">6dp</dimen> <dimen name="volume_dialog_ringer_drawer_button_size">@dimen/volume_dialog_button_size</dimen> <dimen name="volume_dialog_ringer_drawer_button_icon_radius">10dp</dimen> <dimen name="volume_dialog_ringer_selected_button_background_radius">20dp</dimen> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index ae3a76e2d2ca..8d7cab41ea20 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -51,6 +51,7 @@ android_library { ":wm_shell-shared-aidls", ], static_libs: [ + "com.android.systemui.dagger-api", "BiometricsSharedLib", "PlatformAnimationLib", "PluginCoreLib", diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt index a2b6e2cf9ae3..f276a82baaef 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt @@ -41,7 +41,7 @@ class CombinedCondition constructor( private val scope: CoroutineScope, private val conditions: Collection<Condition>, - @Evaluator.ConditionOperand private val operand: Int + @Evaluator.ConditionOperand private val operand: Int, ) : Condition(scope, null, false) { private var job: Job? = null @@ -54,7 +54,7 @@ constructor( lazilyEvaluate( conditions = groupedConditions.getOrDefault(true, emptyList()), - filterUnknown = true + filterUnknown = true, ) .distinctUntilChanged() .flatMapLatest { overriddenValue -> @@ -62,7 +62,7 @@ constructor( if (overriddenValue == null) { lazilyEvaluate( conditions = groupedConditions.getOrDefault(false, emptyList()), - filterUnknown = false + filterUnknown = false, ) } else { flowOf(overriddenValue) @@ -188,7 +188,6 @@ constructor( return startStrategy } - override fun getStartStrategy(): Int { - return _startStrategy - } + override val startStrategy: Int + get() = _startStrategy } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java deleted file mode 100644 index 670feeba6e8a..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.condition; - -import android.util.Log; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleEventObserver; -import androidx.lifecycle.LifecycleOwner; - -import kotlinx.coroutines.CoroutineScope; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -/** - * Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform - * its callbacks. - */ -public abstract class Condition { - private final String mTag = getClass().getSimpleName(); - - private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); - private final boolean mOverriding; - private final CoroutineScope mScope; - private Boolean mIsConditionMet; - private boolean mStarted = false; - - /** - * By default, conditions have an initial value of false and are not overriding. - */ - public Condition(CoroutineScope scope) { - this(scope, false, false); - } - - /** - * Constructor for specifying initial state and overriding condition attribute. - * - * @param initialConditionMet Initial state of the condition. - * @param overriding Whether this condition overrides others. - */ - protected Condition(CoroutineScope scope, Boolean initialConditionMet, boolean overriding) { - mIsConditionMet = initialConditionMet; - mOverriding = overriding; - mScope = scope; - } - - /** - * Starts monitoring the condition. - */ - protected abstract void start(); - - /** - * Stops monitoring the condition. - */ - protected abstract void stop(); - - /** - * Condition should be started as soon as there is an active subscription. - */ - public static final int START_EAGERLY = 0; - /** - * Condition should be started lazily only if needed. But once started, it will not be cancelled - * unless there are no more active subscriptions. - */ - public static final int START_LAZILY = 1; - /** - * Condition should be started lazily only if needed, and can be stopped when not needed. This - * should be used for conditions which are expensive to keep running. - */ - public static final int START_WHEN_NEEDED = 2; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({START_EAGERLY, START_LAZILY, START_WHEN_NEEDED}) - @interface StartStrategy { - } - - @StartStrategy - protected abstract int getStartStrategy(); - - /** - * Returns whether the current condition overrides - */ - public boolean isOverridingCondition() { - return mOverriding; - } - - /** - * Registers a callback to receive updates once started. This should be called before - * {@link #start()}. Also triggers the callback immediately if already started. - */ - public void addCallback(@NonNull Callback callback) { - if (shouldLog()) Log.d(mTag, "adding callback"); - mCallbacks.add(new WeakReference<>(callback)); - - if (mStarted) { - callback.onConditionChanged(this); - return; - } - - start(); - mStarted = true; - } - - /** - * Removes the provided callback from further receiving updates. - */ - public void removeCallback(@NonNull Callback callback) { - if (shouldLog()) Log.d(mTag, "removing callback"); - final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator(); - while (iterator.hasNext()) { - final Callback cb = iterator.next().get(); - if (cb == null || cb == callback) { - iterator.remove(); - } - } - - if (!mCallbacks.isEmpty() || !mStarted) { - return; - } - - stop(); - mStarted = false; - } - - /** - * Wrapper to {@link #addCallback(Callback)} when a lifecycle is in the resumed state - * and {@link #removeCallback(Callback)} when not resumed automatically. - */ - public Callback observe(LifecycleOwner owner, Callback listener) { - return observe(owner.getLifecycle(), listener); - } - - /** - * Wrapper to {@link #addCallback(Callback)} when a lifecycle is in the resumed state - * and {@link #removeCallback(Condition.Callback)} when not resumed automatically. - */ - public Callback observe(Lifecycle lifecycle, Callback listener) { - lifecycle.addObserver((LifecycleEventObserver) (lifecycleOwner, event) -> { - if (event == Lifecycle.Event.ON_RESUME) { - addCallback(listener); - } else if (event == Lifecycle.Event.ON_PAUSE) { - removeCallback(listener); - } - }); - return listener; - } - - /** - * Updates the value for whether the condition has been fulfilled, and sends an update if the - * value changes and any callback is registered. - * - * @param isConditionMet True if the condition has been fulfilled. False otherwise. - */ - protected void updateCondition(boolean isConditionMet) { - if (mIsConditionMet != null && mIsConditionMet == isConditionMet) { - return; - } - - if (shouldLog()) Log.d(mTag, "updating condition to " + isConditionMet); - mIsConditionMet = isConditionMet; - sendUpdate(); - } - - /** - * Clears the set condition value. This is purposefully separate from - * {@link #updateCondition(boolean)} to avoid confusion around {@code null} values. - */ - protected void clearCondition() { - if (mIsConditionMet == null) { - return; - } - - if (shouldLog()) Log.d(mTag, "clearing condition"); - - mIsConditionMet = null; - sendUpdate(); - } - - private void sendUpdate() { - final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator(); - while (iterator.hasNext()) { - final Callback cb = iterator.next().get(); - if (cb == null) { - iterator.remove(); - } else { - cb.onConditionChanged(this); - } - } - } - - /** - * Returns whether the condition is set. This method should be consulted to understand the - * value of {@link #isConditionMet()}. - * - * @return {@code true} if value is present, {@code false} otherwise. - */ - public boolean isConditionSet() { - return mIsConditionMet != null; - } - - /** - * Returns whether the condition has been met. Note that this method will return {@code false} - * if the condition is not set as well. - */ - public boolean isConditionMet() { - return Boolean.TRUE.equals(mIsConditionMet); - } - - protected final boolean shouldLog() { - return Log.isLoggable(mTag, Log.DEBUG); - } - - protected final String getTag() { - if (isOverridingCondition()) { - return mTag + "[OVRD]"; - } - - return mTag; - } - - /** - * Returns the state of the condition. - * - "Invalid", condition hasn't been set / not monitored - * - "True", condition has been met - * - "False", condition has not been met - */ - protected final String getState() { - if (!isConditionSet()) { - return "Invalid"; - } - return isConditionMet() ? "True" : "False"; - } - - /** - * Creates a new condition which will only be true when both this condition and all the provided - * conditions are true. - */ - public Condition and(@NonNull Collection<Condition> others) { - final List<Condition> conditions = new ArrayList<>(); - conditions.add(this); - conditions.addAll(others); - return new CombinedCondition(mScope, conditions, Evaluator.OP_AND); - } - - /** - * Creates a new condition which will only be true when both this condition and the provided - * condition is true. - */ - public Condition and(@NonNull Condition... others) { - return and(Arrays.asList(others)); - } - - /** - * Creates a new condition which will only be true when either this condition or any of the - * provided conditions are true. - */ - public Condition or(@NonNull Collection<Condition> others) { - final List<Condition> conditions = new ArrayList<>(); - conditions.add(this); - conditions.addAll(others); - return new CombinedCondition(mScope, conditions, Evaluator.OP_OR); - } - - /** - * Creates a new condition which will only be true when either this condition or the provided - * condition is true. - */ - public Condition or(@NonNull Condition... others) { - return or(Arrays.asList(others)); - } - - /** - * Callback that receives updates about whether the condition has been fulfilled. - */ - public interface Callback { - /** - * Called when the fulfillment of the condition changes. - * - * @param condition The condition in question. - */ - void onConditionChanged(Condition condition); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.kt new file mode 100644 index 000000000000..69236b48ed51 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.kt @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.shared.condition + +import android.util.Log +import androidx.annotation.IntDef +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import java.lang.ref.WeakReference +import kotlinx.coroutines.CoroutineScope + +/** + * Base class for a condition that needs to be fulfilled in order for [Monitor] to inform its + * callbacks. + */ +abstract class Condition +/** + * Constructor for specifying initial state and overriding condition attribute. + * + * @param initialConditionMet Initial state of the condition. + * @param overriding Whether this condition overrides others. + */ +@JvmOverloads +protected constructor( + private val _scope: CoroutineScope, + private var _isConditionMet: Boolean? = false, + /** Returns whether the current condition overrides */ + val isOverridingCondition: Boolean = false, +) { + private val mTag: String = javaClass.simpleName + + private val _callbacks = mutableListOf<WeakReference<Callback>>() + private var _started = false + + /** Starts monitoring the condition. */ + protected abstract fun start() + + /** Stops monitoring the condition. */ + protected abstract fun stop() + + @Retention(AnnotationRetention.SOURCE) + @IntDef(START_EAGERLY, START_LAZILY, START_WHEN_NEEDED) + annotation class StartStrategy + + @get:StartStrategy abstract val startStrategy: Int + + /** + * Registers a callback to receive updates once started. This should be called before [.start]. + * Also triggers the callback immediately if already started. + */ + fun addCallback(callback: Callback) { + if (shouldLog()) Log.d(mTag, "adding callback") + _callbacks.add(WeakReference(callback)) + + if (_started) { + callback.onConditionChanged(this) + return + } + + start() + _started = true + } + + /** Removes the provided callback from further receiving updates. */ + fun removeCallback(callback: Callback) { + if (shouldLog()) Log.d(mTag, "removing callback") + val iterator = _callbacks.iterator() + while (iterator.hasNext()) { + val cb = iterator.next().get() + if (cb == null || cb === callback) { + iterator.remove() + } + } + + if (_callbacks.isNotEmpty() || !_started) { + return + } + + stop() + _started = false + } + + /** + * Wrapper to [.addCallback] when a lifecycle is in the resumed state and [.removeCallback] when + * not resumed automatically. + */ + fun observe(owner: LifecycleOwner, listener: Callback): Callback { + return observe(owner.lifecycle, listener) + } + + /** + * Wrapper to [.addCallback] when a lifecycle is in the resumed state and [.removeCallback] when + * not resumed automatically. + */ + fun observe(lifecycle: Lifecycle, listener: Callback): Callback { + lifecycle.addObserver( + LifecycleEventObserver { lifecycleOwner: LifecycleOwner?, event: Lifecycle.Event -> + if (event == Lifecycle.Event.ON_RESUME) { + addCallback(listener) + } else if (event == Lifecycle.Event.ON_PAUSE) { + removeCallback(listener) + } + } + ) + return listener + } + + /** + * Updates the value for whether the condition has been fulfilled, and sends an update if the + * value changes and any callback is registered. + * + * @param isConditionMet True if the condition has been fulfilled. False otherwise. + */ + protected fun updateCondition(isConditionMet: Boolean) { + if (_isConditionMet != null && _isConditionMet == isConditionMet) { + return + } + + if (shouldLog()) Log.d(mTag, "updating condition to $isConditionMet") + _isConditionMet = isConditionMet + sendUpdate() + } + + /** + * Clears the set condition value. This is purposefully separate from [.updateCondition] to + * avoid confusion around `null` values. + */ + fun clearCondition() { + if (_isConditionMet == null) { + return + } + + if (shouldLog()) Log.d(mTag, "clearing condition") + + _isConditionMet = null + sendUpdate() + } + + private fun sendUpdate() { + val iterator = _callbacks.iterator() + while (iterator.hasNext()) { + val cb = iterator.next().get() + if (cb == null) { + iterator.remove() + } else { + cb.onConditionChanged(this) + } + } + } + + val isConditionSet: Boolean + /** + * Returns whether the condition is set. This method should be consulted to understand the + * value of [.isConditionMet]. + * + * @return `true` if value is present, `false` otherwise. + */ + get() = _isConditionMet != null + + val isConditionMet: Boolean + /** + * Returns whether the condition has been met. Note that this method will return `false` if + * the condition is not set as well. + */ + get() = true == _isConditionMet + + protected fun shouldLog(): Boolean { + return Log.isLoggable(mTag, Log.DEBUG) + } + + val tag: String + get() { + if (isOverridingCondition) { + return "$mTag[OVRD]" + } + + return mTag + } + + val state: String + /** + * Returns the state of the condition. + * - "Invalid", condition hasn't been set / not monitored + * - "True", condition has been met + * - "False", condition has not been met + */ + get() { + if (!isConditionSet) { + return "Invalid" + } + return if (isConditionMet) "True" else "False" + } + + /** + * Creates a new condition which will only be true when both this condition and all the provided + * conditions are true. + */ + fun and(others: Collection<Condition>): Condition { + val conditions: List<Condition> = listOf(this, *others.toTypedArray()) + return CombinedCondition(_scope, conditions, Evaluator.OP_AND) + } + + /** + * Creates a new condition which will only be true when both this condition and the provided + * condition is true. + */ + fun and(vararg others: Condition): Condition { + return and(listOf(*others)) + } + + /** + * Creates a new condition which will only be true when either this condition or any of the + * provided conditions are true. + */ + fun or(others: Collection<Condition>): Condition { + val conditions: MutableList<Condition> = ArrayList() + conditions.add(this) + conditions.addAll(others) + return CombinedCondition(_scope, conditions, Evaluator.OP_OR) + } + + /** + * Creates a new condition which will only be true when either this condition or the provided + * condition is true. + */ + fun or(vararg others: Condition): Condition { + return or(listOf(*others)) + } + + /** Callback that receives updates about whether the condition has been fulfilled. */ + fun interface Callback { + /** + * Called when the fulfillment of the condition changes. + * + * @param condition The condition in question. + */ + fun onConditionChanged(condition: Condition) + } + + companion object { + /** Condition should be started as soon as there is an active subscription. */ + const val START_EAGERLY: Int = 0 + + /** + * Condition should be started lazily only if needed. But once started, it will not be + * cancelled unless there are no more active subscriptions. + */ + const val START_LAZILY: Int = 1 + + /** + * Condition should be started lazily only if needed, and can be stopped when not needed. + * This should be used for conditions which are expensive to keep running. + */ + const val START_WHEN_NEEDED: Int = 2 + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt index 84edc3577007..0f535cdfa5cc 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt @@ -14,7 +14,7 @@ import kotlinx.coroutines.launch fun Flow<Boolean>.toCondition( scope: CoroutineScope, @StartStrategy strategy: Int, - initialValue: Boolean? = null + initialValue: Boolean? = null, ): Condition { return object : Condition(scope, initialValue, false) { var job: Job? = null @@ -28,7 +28,8 @@ fun Flow<Boolean>.toCondition( job = null } - override fun getStartStrategy() = strategy + override val startStrategy: Int + get() = strategy } } @@ -36,13 +37,16 @@ fun Flow<Boolean>.toCondition( fun Condition.toFlow(): Flow<Boolean?> { return callbackFlow { val callback = - Condition.Callback { condition -> - if (condition.isConditionSet) { - trySend(condition.isConditionMet) - } else { - trySend(null) + object : Condition.Callback { + override fun onConditionChanged(condition: Condition) { + if (condition.isConditionSet) { + trySend(condition.isConditionMet) + } else { + trySend(null) + } } } + addCallback(callback) callback.onConditionChanged(this@toFlow) awaitClose { removeCallback(callback) } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index e827b2d54a19..1549b699eee6 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -56,6 +56,7 @@ import com.android.systemui.log.core.Logger import com.android.systemui.modes.shared.ModesUi import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockController +import com.android.systemui.plugins.clocks.ClockEventListener import com.android.systemui.plugins.clocks.ClockFaceController import com.android.systemui.plugins.clocks.ClockMessageBuffers import com.android.systemui.plugins.clocks.ClockTickRate @@ -148,7 +149,7 @@ constructor( val clockStr = clock.toString() loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } } - clock.initialize(isDarkTheme(), dozeAmount.value, 0f, { onClockBoundsChanged.value = it }) + clock.initialize(isDarkTheme(), dozeAmount.value, 0f, clockListener) if (!regionSamplingEnabled) { updateColors() @@ -312,6 +313,13 @@ constructor( private var zenData: ZenData? = null private var alarmData: AlarmData? = null + private val clockListener = + object : ClockEventListener { + override fun onBoundsChanged(bounds: RectF) { + onClockBoundsChanged.value = bounds + } + } + private val configListener = object : ConfigurationController.ConfigurationListener { override fun onThemeChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt new file mode 100644 index 000000000000..df6c1b18e3e9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay + +import android.content.ClipData +import android.content.ClipDescription +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.text.TextUtils +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.res.R +import javax.inject.Inject + +@SysUISingleton +class ActionIntentCreator @Inject constructor() : IntentCreator { + override fun getTextEditorIntent(context: Context?) = + Intent(context, EditTextActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + } + + override fun getShareIntent(clipData: ClipData, context: Context?): Intent { + val shareIntent = Intent(Intent.ACTION_SEND) + + // From the ACTION_SEND docs: + // "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the + // MIME type of the data in EXTRA_STREAM" + val uri = clipData.getItemAt(0).uri + shareIntent.apply { + if (uri != null) { + // We don't use setData here because some apps interpret this as "to:". + setType(clipData.description.getMimeType(0)) + // Include URI in ClipData also, so that grantPermission picks it up. + setClipData( + ClipData( + ClipDescription("content", arrayOf(clipData.description.getMimeType(0))), + ClipData.Item(uri), + ) + ) + putExtra(Intent.EXTRA_STREAM, uri) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } else { + putExtra(Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context).toString()) + setType("text/plain") + } + } + + return Intent.createChooser(shareIntent, null) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + override fun getImageEditIntent(uri: Uri?, context: Context): Intent { + val editorPackage = context.getString(R.string.config_screenshotEditor) + return Intent(Intent.ACTION_EDIT).apply { + if (!TextUtils.isEmpty(editorPackage)) { + setComponent(ComponentName.unflattenFromString(editorPackage)) + } + setDataAndType(uri, "image/*") + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD) + } + } + + override fun getRemoteCopyIntent(clipData: ClipData?, context: Context): Intent { + val remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage) + return Intent(REMOTE_COPY_ACTION).apply { + if (!TextUtils.isEmpty(remoteCopyPackage)) { + setComponent(ComponentName.unflattenFromString(remoteCopyPackage)) + } + + setClipData(clipData) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + } + } + + companion object { + private const val EXTRA_EDIT_SOURCE: String = "edit_source" + private const val EDIT_SOURCE_CLIPBOARD: String = "clipboard" + private const val REMOTE_COPY_ACTION: String = "android.intent.action.REMOTE_COPY" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index ac747845267c..314b6e7f5a28 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -65,7 +65,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext; import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.res.R; import com.android.systemui.screenshot.TimeoutHandler; @@ -94,13 +93,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private final ClipboardOverlayWindow mWindow; private final TimeoutHandler mTimeoutHandler; private final ClipboardOverlayUtils mClipboardUtils; - private final FeatureFlags mFeatureFlags; private final Executor mBgExecutor; private final ClipboardImageLoader mClipboardImageLoader; private final ClipboardTransitionExecutor mTransitionExecutor; private final ClipboardOverlayView mView; private final ClipboardIndicationProvider mClipboardIndicationProvider; + private final IntentCreator mIntentCreator; private Runnable mOnSessionCompleteListener; private Runnable mOnRemoteCopyTapped; @@ -189,13 +188,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv BroadcastDispatcher broadcastDispatcher, BroadcastSender broadcastSender, TimeoutHandler timeoutHandler, - FeatureFlags featureFlags, ClipboardOverlayUtils clipboardUtils, @Background Executor bgExecutor, ClipboardImageLoader clipboardImageLoader, ClipboardTransitionExecutor transitionExecutor, ClipboardIndicationProvider clipboardIndicationProvider, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + IntentCreator intentCreator) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mClipboardImageLoader = clipboardImageLoader; @@ -203,6 +202,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv mClipboardIndicationProvider = clipboardIndicationProvider; mClipboardLogger = new ClipboardLogger(uiEventLogger); + mIntentCreator = intentCreator; mView = clipboardOverlayView; mWindow = clipboardOverlayWindow; @@ -211,7 +211,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv hideImmediate(); }); - mFeatureFlags = featureFlags; mTimeoutHandler = timeoutHandler; mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS); @@ -508,7 +507,8 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv } private void maybeShowRemoteCopy(ClipData clipData) { - Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext); + Intent remoteCopyIntent = mIntentCreator.getRemoteCopyIntent(clipData, mContext); + // Only show remote copy if it's available. PackageManager packageManager = mContext.getPackageManager(); if (packageManager.resolveActivity( @@ -558,19 +558,19 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private void editImage(Uri uri) { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); - mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext)); + mContext.startActivity(mIntentCreator.getImageEditIntent(uri, mContext)); animateOut(); } private void editText() { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); - mContext.startActivity(IntentCreator.getTextEditorIntent(mContext)); + mContext.startActivity(mIntentCreator.getTextEditorIntent(mContext)); animateOut(); } private void shareContent(ClipData clip) { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED); - mContext.startActivity(IntentCreator.getShareIntent(clip, mContext)); + mContext.startActivity(mIntentCreator.getShareIntent(clip, mContext)); animateOut(); } @@ -717,22 +717,22 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv public void onRemoteCopyButtonTapped() { if (clipboardSharedTransitions()) { finish(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED, - IntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext)); + mIntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext)); } } @Override public void onShareButtonTapped() { if (clipboardSharedTransitions()) { + Intent shareIntent = + mIntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext); switch (mClipboardModel.getType()) { case TEXT: case URI: - finish(CLIPBOARD_OVERLAY_SHARE_TAPPED, - IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext)); + finish(CLIPBOARD_OVERLAY_SHARE_TAPPED, shareIntent); break; case IMAGE: - finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED, - IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext)); + finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED, shareIntent); break; } } @@ -744,11 +744,11 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv switch (mClipboardModel.getType()) { case TEXT: finish(CLIPBOARD_OVERLAY_EDIT_TAPPED, - IntentCreator.getTextEditorIntent(mContext)); + mIntentCreator.getTextEditorIntent(mContext)); break; case IMAGE: finishWithSharedTransition(CLIPBOARD_OVERLAY_EDIT_TAPPED, - IntentCreator.getImageEditIntent(mClipboardModel.getUri(), mContext)); + mIntentCreator.getImageEditIntent(mClipboardModel.getUri(), mContext)); break; default: Log.w(TAG, "Got preview tapped callback for non-editable type " diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java new file mode 100644 index 000000000000..4b24536ad28f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay; + +import android.content.ClipData; +import android.content.ClipDescription; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.text.TextUtils; + +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.res.R; + +import javax.inject.Inject; + +@SysUISingleton +public class DefaultIntentCreator implements IntentCreator { + private static final String EXTRA_EDIT_SOURCE = "edit_source"; + private static final String EDIT_SOURCE_CLIPBOARD = "clipboard"; + private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY"; + + @Inject + public DefaultIntentCreator() {} + + public Intent getTextEditorIntent(Context context) { + Intent intent = new Intent(context, EditTextActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return intent; + } + + public Intent getShareIntent(ClipData clipData, Context context) { + Intent shareIntent = new Intent(Intent.ACTION_SEND); + + // From the ACTION_SEND docs: + // "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the + // MIME type of the data in EXTRA_STREAM" + Uri uri = clipData.getItemAt(0).getUri(); + if (uri != null) { + // We don't use setData here because some apps interpret this as "to:". + shareIntent.setType(clipData.getDescription().getMimeType(0)); + // Include URI in ClipData also, so that grantPermission picks it up. + shareIntent.setClipData(new ClipData( + new ClipDescription( + "content", new String[]{clipData.getDescription().getMimeType(0)}), + new ClipData.Item(uri))); + shareIntent.putExtra(Intent.EXTRA_STREAM, uri); + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } else { + shareIntent.putExtra( + Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context).toString()); + shareIntent.setType("text/plain"); + } + Intent chooserIntent = Intent.createChooser(shareIntent, null) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + return chooserIntent; + } + + public Intent getImageEditIntent(Uri uri, Context context) { + String editorPackage = context.getString(R.string.config_screenshotEditor); + Intent editIntent = new Intent(Intent.ACTION_EDIT); + if (!TextUtils.isEmpty(editorPackage)) { + editIntent.setComponent(ComponentName.unflattenFromString(editorPackage)); + } + editIntent.setDataAndType(uri, "image/*"); + editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + editIntent.putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD); + return editIntent; + } + + public Intent getRemoteCopyIntent(ClipData clipData, Context context) { + Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION); + + String remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage); + if (!TextUtils.isEmpty(remoteCopyPackage)) { + nearbyIntent.setComponent(ComponentName.unflattenFromString(remoteCopyPackage)); + } + + nearbyIntent.setClipData(clipData); + nearbyIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return nearbyIntent; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java index a18b4c84b081..c8a6b05f090b 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,79 +17,13 @@ package com.android.systemui.clipboardoverlay; import android.content.ClipData; -import android.content.ClipDescription; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.text.TextUtils; -import com.android.systemui.res.R; - -class IntentCreator { - private static final String EXTRA_EDIT_SOURCE = "edit_source"; - private static final String EDIT_SOURCE_CLIPBOARD = "clipboard"; - private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY"; - - static Intent getTextEditorIntent(Context context) { - Intent intent = new Intent(context, EditTextActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - return intent; - } - - static Intent getShareIntent(ClipData clipData, Context context) { - Intent shareIntent = new Intent(Intent.ACTION_SEND); - - // From the ACTION_SEND docs: - // "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the - // MIME type of the data in EXTRA_STREAM" - Uri uri = clipData.getItemAt(0).getUri(); - if (uri != null) { - // We don't use setData here because some apps interpret this as "to:". - shareIntent.setType(clipData.getDescription().getMimeType(0)); - // Include URI in ClipData also, so that grantPermission picks it up. - shareIntent.setClipData(new ClipData( - new ClipDescription( - "content", new String[]{clipData.getDescription().getMimeType(0)}), - new ClipData.Item(uri))); - shareIntent.putExtra(Intent.EXTRA_STREAM, uri); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } else { - shareIntent.putExtra( - Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context).toString()); - shareIntent.setType("text/plain"); - } - Intent chooserIntent = Intent.createChooser(shareIntent, null) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - - return chooserIntent; - } - - static Intent getImageEditIntent(Uri uri, Context context) { - String editorPackage = context.getString(R.string.config_screenshotEditor); - Intent editIntent = new Intent(Intent.ACTION_EDIT); - if (!TextUtils.isEmpty(editorPackage)) { - editIntent.setComponent(ComponentName.unflattenFromString(editorPackage)); - } - editIntent.setDataAndType(uri, "image/*"); - editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - editIntent.putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD); - return editIntent; - } - - static Intent getRemoteCopyIntent(ClipData clipData, Context context) { - Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION); - - String remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage); - if (!TextUtils.isEmpty(remoteCopyPackage)) { - nearbyIntent.setComponent(ComponentName.unflattenFromString(remoteCopyPackage)); - } - - nearbyIntent.setClipData(clipData); - nearbyIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - return nearbyIntent; - } +public interface IntentCreator { + Intent getTextEditorIntent(Context context); + Intent getShareIntent(ClipData clipData, Context context); + Intent getImageEditIntent(Uri uri, Context context); + Intent getRemoteCopyIntent(ClipData clipData, Context context); } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java index 6c10eea07ffc..c86a84b17efe 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java @@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; import static com.android.systemui.Flags.clipboardOverlayMultiuser; import static com.android.systemui.Flags.enableViewCaptureTracing; +import static com.android.systemui.shared.Flags.usePreferredImageEditor; import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -32,7 +33,10 @@ import android.view.WindowManager; import com.android.app.viewcapture.ViewCapture; import com.android.app.viewcapture.ViewCaptureAwareWindowManager; +import com.android.systemui.clipboardoverlay.ActionIntentCreator; import com.android.systemui.clipboardoverlay.ClipboardOverlayView; +import com.android.systemui.clipboardoverlay.DefaultIntentCreator; +import com.android.systemui.clipboardoverlay.IntentCreator; import com.android.systemui.res.R; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; @@ -102,6 +106,17 @@ public interface ClipboardOverlayModule { /* isViewCaptureEnabled= */ enableViewCaptureTracing()); } + @Provides + static IntentCreator provideIntentCreator( + Lazy<DefaultIntentCreator> defaultIntentCreator, + Lazy<ActionIntentCreator> actionIntentCreator) { + if (usePreferredImageEditor()) { + return actionIntentCreator.get(); + } else { + return defaultIntentCreator.get(); + } + } + @Qualifier @Documented @Retention(RUNTIME) diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt index ba7e17bdd7a5..07cc136e6bc6 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt @@ -23,7 +23,6 @@ import androidx.annotation.ColorInt import androidx.annotation.DimenRes import androidx.annotation.LayoutRes import com.android.settingslib.Utils -import com.android.systemui.statusbar.data.repository.StatusBarConfigurationState import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged import com.android.systemui.statusbar.policy.onThemeChanged @@ -79,7 +78,7 @@ class ConfigurationStateImpl constructor( @Assisted private val configurationController: ConfigurationController, @Assisted private val context: Context, -) : ConfigurationState, StatusBarConfigurationState { +) : ConfigurationState { private val layoutInflater = LayoutInflater.from(context) diff --git a/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java index e456310febfd..4be9601f4277 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java +++ b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java @@ -102,7 +102,7 @@ public class DeviceInactiveCondition extends Condition { } @Override - protected int getStartStrategy() { + public int getStartStrategy() { return START_EAGERLY; } diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt index 3458c9549665..d27e33e53dbb 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt @@ -126,7 +126,7 @@ constructor( get() = perDisplayInstances.keys private suspend fun start() { - dumpManager.registerDumpable(this) + dumpManager.registerNormalDumpable("PerDisplayRepository-${debugName}", this) displayRepository.displayIds.collectLatest { displayIds -> val toRemove = perDisplayInstances.keys - displayIds toRemove.forEach { displayId -> @@ -184,8 +184,13 @@ constructor( * Provides an instance of a given class **only** for the default display, even if asked for another * display. * - * This is useful in case of flag refactors: it can be provided instead of an instance of + * This is useful in case of **flag refactors**: it can be provided instead of an instance of * [PerDisplayInstanceRepositoryImpl] when a flag related to multi display refactoring is off. + * + * Note that this still requires all instances to be provided by a [PerDisplayInstanceProvider]. If + * you want to provide an existing instance instead for the default display, either implement it in + * a custom [PerDisplayInstanceProvider] (e.g. inject it in the constructor and return it if the + * displayId is zero), or use [SingleInstanceRepositoryImpl]. */ class DefaultDisplayOnlyInstanceRepositoryImpl<T>( override val debugName: String, @@ -198,3 +203,18 @@ class DefaultDisplayOnlyInstanceRepositoryImpl<T>( override fun get(displayId: Int): T? = lazyDefaultDisplayInstance } + +/** + * Always returns [instance] for any display. + * + * This can be used to provide a single instance based on a flag value during a refactor. Similar to + * [DefaultDisplayOnlyInstanceRepositoryImpl], but also avoids creating the + * [PerDisplayInstanceProvider]. This is useful when you want to provide an existing instance only, + * without even instantiating a [PerDisplayInstanceProvider]. + */ +class SingleInstanceRepositoryImpl<T>(override val debugName: String, private val instance: T) : + PerDisplayRepository<T> { + override val displayIds: Set<Int> = setOf(Display.DEFAULT_DISPLAY) + + override fun get(displayId: Int): T? = instance +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java index d81949d08a66..c17094b7456c 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java @@ -63,7 +63,7 @@ public class AssistantAttentionCondition extends Condition { } @Override - protected int getStartStrategy() { + public int getStartStrategy() { return START_EAGERLY; } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java index c7fe1e1e754b..fb4ed1419688 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java @@ -63,7 +63,7 @@ public class DreamCondition extends Condition { } @Override - protected int getStartStrategy() { + public int getStartStrategy() { return START_EAGERLY; } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt index ca157afb7721..293461daef7b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt @@ -91,6 +91,7 @@ object RefactorFlagUtils { * } * ```` */ + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) inline fun unsafeAssertInNewMode(isEnabled: Boolean, flagName: Any) = check(isEnabled) { "New code path not supported when $flagName is disabled." } diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt index 4c1da0198498..57d709835b2c 100644 --- a/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt @@ -54,7 +54,6 @@ constructor( job?.cancel() } - override fun getStartStrategy(): Int { - return START_EAGERLY - } + override val startStrategy: Int + get() = START_EAGERLY } diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java index 7f21d0707f63..5ec81a9a94a1 100644 --- a/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java @@ -131,7 +131,7 @@ public class ForceLowLightCondition extends Condition { } @Override - protected int getStartStrategy() { + public int getStartStrategy() { return START_EAGERLY; } } diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java index e91be5028777..c1a24e7e020e 100644 --- a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java @@ -54,7 +54,7 @@ public class LowLightCondition extends Condition { } @Override - protected int getStartStrategy() { + public int getStartStrategy() { // As this condition keeps the lowlight sensor active, it should only run when needed. return START_WHEN_NEEDED; } diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java index fd6ce1762a28..81572554cb86 100644 --- a/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java @@ -70,7 +70,7 @@ public class ScreenSaverEnabledCondition extends Condition { } @Override - protected int getStartStrategy() { + public int getStartStrategy() { return START_EAGERLY; } diff --git a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt index a344a5cce6ba..3f804f768d8d 100644 --- a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt +++ b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt @@ -42,7 +42,9 @@ object ModesUi { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, Flags.FLAG_MODES_UI) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, Flags.FLAG_MODES_UI) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index c4d847f18269..efed260b4c99 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -44,7 +44,6 @@ import android.app.StatusBarManager.WindowVisibleState; import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManager; -import android.inputmethodservice.InputMethodService; import android.inputmethodservice.InputMethodService.BackDispositionMode; import android.inputmethodservice.InputMethodService.ImeWindowVisibility; import android.os.Handler; @@ -503,9 +502,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, @BackDispositionMode int backDisposition, boolean showImeSwitcher) { - // Count imperceptible changes as visible so we transition taskbar out quickly. - final boolean isImeVisible = mNavBarHelper.isImeVisible(vis) - || (vis & InputMethodService.IME_VISIBLE_IMPERCEPTIBLE) != 0; + final boolean isImeVisible = mNavBarHelper.isImeVisible(vis); final int flags = Utilities.updateNavbarFlagsFromIme(mNavbarFlags, backDisposition, isImeVisible, showImeSwitcher); if (flags == mNavbarFlags) { diff --git a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java index 694b525e7bc1..ea2bf6a44884 100644 --- a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java +++ b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java @@ -48,7 +48,7 @@ public class SystemProcessCondition extends Condition { } @Override - protected int getStartStrategy() { + public int getStartStrategy() { return START_EAGERLY; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index 9aedf0d1ee31..f1f5b267f9c1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -743,7 +743,13 @@ constructor( BrightnessSliderContainer( viewModel = containerViewModel.brightnessSliderViewModel, modifier = - Modifier.systemGestureExclusionInShade().fillMaxWidth(), + Modifier.systemGestureExclusionInShade( + enabled = { + layoutState.transitionState is + TransitionState.Idle + } + ) + .fillMaxWidth(), ) } val TileGrid = diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt index 2eba36a25ae7..6865e0ee79f6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt @@ -81,7 +81,9 @@ object QsDetailedView { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** Returns a developer-readable string that describes the current requirement list. */ @JvmStatic diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index feb4d41e49b6..865ae9a37d87 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.res.integerResource import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.padding +import com.android.systemui.common.ui.compose.PagerDots import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.development.ui.compose.BuildNumber import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel diff --git a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java index 7a6426c741a6..4be35f147c2f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java @@ -830,7 +830,8 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis private void notifySystemUiStateFlags(@SystemUiStateFlags long flags, int displayId) { if (SysUiState.DEBUG) { Log.d(TAG_OPS, "Notifying sysui state change to launcher service: proxy=" - + mLauncherProxy + " flags=" + flags + " displayId=" + displayId); + + mLauncherProxy + " display=" + displayId + " flags=" + + QuickStepContract.getSystemUiStateString(flags) + " displayId=" + displayId); } try { if (mLauncherProxy != null) { diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt index b0fb6193ee45..634323c1fe2e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt @@ -83,7 +83,9 @@ object SceneContainerFlag { * testing. */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, DESCRIPTION) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, DESCRIPTION) /** Returns a developer-readable string that describes the current requirement list. */ @JvmStatic diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 17fb50aa6890..24e7976011f4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -140,6 +140,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository; import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; import com.android.systemui.shade.data.repository.FlingInfo; +import com.android.systemui.shade.data.repository.ShadeDisplaysRepository; import com.android.systemui.shade.data.repository.ShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; @@ -204,6 +205,7 @@ import dalvik.annotation.optimization.NeverCompile; import com.google.android.msdl.data.model.MSDLToken; import com.google.android.msdl.domain.MSDLPlayer; +import dagger.Lazy; import kotlin.Unit; import kotlinx.coroutines.CoroutineDispatcher; @@ -394,7 +396,7 @@ public final class NotificationPanelViewController implements /** Whether the notifications are displayed full width (no margins on the side). */ private boolean mIsFullWidth; private boolean mBlockingExpansionForCurrentTouch; - // Following variables maintain state of events when input focus transfer may occur. + // Following variables maintain state of events when input focus transfer may occur. private boolean mExpectingSynthesizedDown; private boolean mLastEventSynthesizedDown; @@ -456,6 +458,7 @@ public final class NotificationPanelViewController implements @Deprecated // Use SysUIStateInteractor instead private final SysUiState mSysUiState; private final SysUIStateDisplaysInteractor mSysUIStateDisplaysInteractor; + private final Lazy<ShadeDisplaysRepository> mShadeDisplaysRepository; private final NotificationShadeDepthController mDepthController; private final NavigationBarController mNavigationBarController; private final int mDisplayId; @@ -637,7 +640,8 @@ public final class NotificationPanelViewController implements KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm, MSDLPlayer msdlPlayer, BrightnessMirrorShowingRepository brightnessMirrorShowingRepository, - BlurConfig blurConfig) { + BlurConfig blurConfig, + Lazy<ShadeDisplaysRepository> shadeDisplaysRepository) { mBlurConfig = blurConfig; SceneContainerFlag.assertInLegacyMode(); keyguardStateController.addCallback(new KeyguardStateController.Callback() { @@ -745,6 +749,7 @@ public final class NotificationPanelViewController implements mTapAgainViewController = tapAgainViewController; mSysUiState = sysUiState; mSysUIStateDisplaysInteractor = sysUIStateDisplaysInteractor; + mShadeDisplaysRepository = shadeDisplaysRepository; mKeyguardBypassController = bypassController; mUpdateMonitor = keyguardUpdateMonitor; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; @@ -2716,8 +2721,17 @@ public final class NotificationPanelViewController implements } private int getShadeDisplayId() { - if (mView != null && mView.getDisplay() != null) return mView.getDisplay().getDisplayId(); - return Display.DEFAULT_DISPLAY; + if (ShadeWindowGoesAround.isEnabled()) { + var pendingDisplayId = + mShadeDisplaysRepository.get().getPendingDisplayId().getValue(); + // Use the pendingDisplayId from the repository, *not* the Shade's context. + // This ensures correct UI state updates also if this method is called just *before* + // the Shade window moves to another display. + // The pendingDisplayId is guaranteed to be updated before this method is called. + return pendingDisplayId; + } else { + return Display.DEFAULT_DISPLAY; + } } private void setPerDisplaySysUIStateFlags() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt index 79e63330e3a5..63b618f72921 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt @@ -50,7 +50,9 @@ object ShadeExpandsOnStatusBarLongPress { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt index 016f50f471de..81824f69eddd 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt @@ -67,7 +67,9 @@ object ShadeWindowGoesAround { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 3db004848d22..bdfdb8191356 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -60,6 +60,8 @@ import java.util.Optional import javax.inject.Inject import kotlin.math.max import kotlin.math.sign +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch /** * Responsible for blurring the notification shade window, and applying a zoom effect to the @@ -92,6 +94,7 @@ constructor( private const val MIN_VELOCITY = -MAX_VELOCITY private const val INTERACTION_BLUR_FRACTION = 0.8f private const val ANIMATION_BLUR_FRACTION = 1f - INTERACTION_BLUR_FRACTION + private const val TRANSITION_THRESHOLD = 0.98f private const val TAG = "DepthController" } @@ -156,6 +159,9 @@ constructor( /** * When launching an app from the shade, the animations progress should affect how blurry the * shade is, overriding the expansion amount. + * + * TODO(b/399617511): remove this once [Flags.notificationShadeBlur] is launched and the Shade + * closing is actually instantaneous. */ var blursDisabledForAppLaunch: Boolean = false set(value) { @@ -185,8 +191,12 @@ constructor( return } - shadeAnimation.animateTo(0) - shadeAnimation.finishIfRunning() + if (Flags.notificationShadeBlur()) { + shadeAnimation.skipTo(0) + } else { + shadeAnimation.animateTo(0) + shadeAnimation.finishIfRunning() + } } @Deprecated( message = @@ -487,6 +497,22 @@ constructor( scheduleUpdate() } + fun onTransitionAnimationProgress(progress: Float) { + if (!Flags.notificationShadeBlur() || !Flags.moveTransitionAnimationLayer()) return + // Because the Shade takes a few frames to actually trigger the unblur after a transition + // has ended, we need to disable it manually, or the opening window itself will be blurred + // for a few frames due to relative ordering. We do this towards the end, so that the + // window is already covering the background and the unblur is not visible. + if (progress >= TRANSITION_THRESHOLD && shadeAnimation.radius > 0) { + blursDisabledForAppLaunch = true + } + } + + fun onTransitionAnimationEnd() { + if (!Flags.notificationShadeBlur() || !Flags.moveTransitionAnimationLayer()) return + blursDisabledForAppLaunch = false + } + private fun updateShadeAnimationBlur( expansion: Float, tracking: Boolean, @@ -631,6 +657,20 @@ constructor( springAnimation.addEndListener { _, _, _, _ -> pendingRadius = -1 } } + /** + * Starts an animation to [newRadius], or updates the current one if already ongoing. + * IMPORTANT: do NOT use this method + [finishIfRunning] to instantaneously change the value + * of the animation. The change will NOT be instantaneous. Use [skipTo] instead. + * + * Explanation: + * 1. If idle, [SpringAnimation.animateToFinalPosition] requests a start to the animation. + * 2. On the first frame after an idle animation is requested to start, the animation simply + * acquires the starting value and does nothing else. + * 3. [SpringAnimation.skipToEnd] requests a fast-forward to the end value, but this happens + * during calculation of the next animation value. Because on the first frame no such + * calculation happens (point #2), there is one lagging frame where we still see the old + * value. + */ fun animateTo(newRadius: Int) { if (pendingRadius == newRadius) { return @@ -639,6 +679,19 @@ constructor( springAnimation.animateToFinalPosition(newRadius.toFloat()) } + /** + * Instantaneously set a new blur radius to this animation. Always use this instead of + * [animateTo] and [finishIfRunning] to make sure that the change takes effect in the next + * frame. See the doc for [animateTo] for an explanation. + */ + fun skipTo(newRadius: Int) { + if (pendingRadius == newRadius) return + pendingRadius = newRadius + springAnimation.cancel() + springAnimation.setStartValue(newRadius.toFloat()) + springAnimation.animateToFinalPosition(newRadius.toFloat()) + } + fun finishIfRunning() { if (springAnimation.isRunning) { springAnimation.skipToEnd() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt index d8c3e2546a8f..a0de879845d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt @@ -56,7 +56,7 @@ constructor( private val logger = Logger(logBuffer, "Notif".pad()) // [StatusBarChipLogTag] recommends a max tag length of 20, so [extraLogTag] should NOT be the // top-level tag. It should instead be provided as the first string in each log message. - private val extraLogTag = "SingleChipInteractor[key=$key]" + private val extraLogTag = "SingleNotifChipInteractor[key=$key][id=${hashCode()}]" init { if (startingModel.promotedContent == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt index 35e622ba8e44..9380dfe32bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt @@ -38,6 +38,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -45,6 +46,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn /** An interactor for the notification chips shown in the status bar. */ @SysUISingleton @@ -147,28 +149,42 @@ constructor( */ val allNotificationChips: Flow<List<NotificationChipModel>> = if (StatusBarNotifChips.isEnabled) { - // For all our current interactors... - // TODO(b/364653005): When a promoted notification is added or removed, each individual - // interactor's [notificationChip] flow becomes un-collected then re-collected, which - // can cause some flows to remove then add callbacks when they don't need to. Is there a - // better structure for this? Maybe Channels or a StateFlow with a short timeout? - promotedNotificationInteractors.flatMapLatest { interactors -> - if (interactors.isNotEmpty()) { - // Combine each interactor's [notificationChip] flow... - val allNotificationChips: List<Flow<NotificationChipModel?>> = - interactors.map { interactor -> interactor.notificationChip } - combine(allNotificationChips) { + // For all our current interactors... + promotedNotificationInteractors.flatMapLatest { interactors -> + if (interactors.isNotEmpty()) { + // Combine each interactor's [notificationChip] flow... + val allNotificationChips: List<Flow<NotificationChipModel?>> = + interactors.map { interactor -> interactor.notificationChip } + combine(allNotificationChips) { // ... and emit just the non-null & sorted chips it.filterNotNull().sortedWith(chipComparator) } - .logSort() - } else { - flowOf(emptyList()) + } else { + flowOf(emptyList()) + } } + } else { + flowOf(emptyList()) } - } else { - flowOf(emptyList()) - } + .distinctUntilChanged() + .logSort() + .stateIn( + backgroundScope, + SharingStarted.WhileSubscribed( + // When a promoted notification is added or removed, the `.flatMapLatest` above + // will stop collection and then re-start collection on each individual + // interactor's flow. (It will happen even for a chip that didn't change.) We + // don't want the individual interactor flows to stop then re-start because they + // may be maintaining values that would get thrown away when collection stops + // (like an app's last visible time). + // stopTimeoutMillis ensures we maintain those values even during the brief + // moment (1-2ms) when `.flatMapLatest` causes collect to stop then immediately + // restart. + // Note: Channels could also work to solve this. + stopTimeoutMillis = 1000 + ), + initialValue = emptyList(), + ) /** Emits the notifications that should actually be *shown* as chips in the status bar. */ val shownNotificationChips: Flow<List<NotificationChipModel>> = @@ -199,14 +215,14 @@ constructor( } private fun Flow<List<NotificationChipModel>>.logSort(): Flow<List<NotificationChipModel>> { - return this.distinctUntilChanged().onEach { chips -> + return this.onEach { chips -> val logString = chips.joinToString { "{key=${it.key}. " + "lastVisibleAppTime=${it.lastAppVisibleTime}. " + "creationTime=${it.creationTime}}" } - logger.d({ "Sorted chips: $str1" }) { str1 = logString } + logger.d({ "Sorted notif chips: $str1" }) { str1 = logString } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt index 947e93c8a432..6431f303089f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt @@ -50,7 +50,9 @@ object StatusBarNotifChips { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt index e8ab39692558..d81ea07cae2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt @@ -75,7 +75,12 @@ fun OngoingActivityChip( } } is OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification -> { - ChipBody(model, iconViewStore, onClick = { clickBehavior.onClick() }) + ChipBody( + model, + iconViewStore, + onClick = { clickBehavior.onClick() }, + modifier = modifier, + ) } is OngoingActivityChipModel.ClickBehavior.None -> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt index 3b8c0f48e40e..7080c3402b08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.res.R import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder @@ -47,7 +48,13 @@ fun OngoingActivityChips( chips.active .filter { !it.isHidden } .forEach { - key(it.key) { OngoingActivityChip(model = it, iconViewStore = iconViewStore) } + key(it.key) { + OngoingActivityChip( + model = it, + iconViewStore = iconViewStore, + modifier = Modifier.sysuiResTag(it.key), + ) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt index 9e1686ae135f..03316d3e326c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt @@ -49,7 +49,9 @@ object NewStatusBarIcons { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt index cb1827d57c2a..a58e00c5ee0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt @@ -50,7 +50,9 @@ object StatusBarConnectedDisplays { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt index 4ee49d82b2fd..25dace32261e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt @@ -52,7 +52,9 @@ object StatusBarRootModernization { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt index 5e7e1793ab35..9db2f4b46dae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt @@ -20,9 +20,9 @@ import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositor import com.android.systemui.statusbar.data.repository.LightBarControllerStoreModule import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule -import com.android.systemui.statusbar.data.repository.StatusBarConfigurationStateModule import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule +import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayConfigurationStateModule import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStoreModule import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule import dagger.Module @@ -35,7 +35,7 @@ import dagger.Module LightBarControllerStoreModule::class, RemoteInputRepositoryModule::class, StatusBarConfigurationControllerModule::class, - StatusBarConfigurationStateModule::class, + StatusBarPerDisplayConfigurationStateModule::class, StatusBarContentInsetsProviderStoreModule::class, StatusBarModeRepositoryModule::class, StatusBarPhoneDataLayerModule::class, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationStateStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationStateStore.kt deleted file mode 100644 index 4e6c5ddc3066..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationStateStore.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2025 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.data.repository - -import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR -import com.android.systemui.CoreStartable -import com.android.systemui.common.ui.ConfigurationState -import com.android.systemui.common.ui.ConfigurationStateImpl -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.display.data.repository.DisplayRepository -import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository -import com.android.systemui.display.data.repository.PerDisplayStore -import com.android.systemui.display.data.repository.PerDisplayStoreImpl -import com.android.systemui.display.data.repository.SingleDisplayStore -import com.android.systemui.statusbar.core.StatusBarConnectedDisplays -import dagger.Lazy -import dagger.Module -import dagger.Provides -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope - -/** Status bar specific interface to disambiguate from the global [ConfigurationState]. */ -interface StatusBarConfigurationState : ConfigurationState - -/** Provides per display instances of [ConfigurationState], specifically for the Status Bar. */ -interface StatusBarConfigurationStateStore : PerDisplayStore<StatusBarConfigurationState> - -@SysUISingleton -class MultiDisplayStatusBarConfigurationStateStore -@Inject -constructor( - @Background backgroundApplicationScope: CoroutineScope, - displayRepository: DisplayRepository, - private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository, - private val statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore, - private val factory: ConfigurationStateImpl.Factory, -) : - StatusBarConfigurationStateStore, - PerDisplayStoreImpl<StatusBarConfigurationState>( - backgroundApplicationScope, - displayRepository, - ) { - - init { - StatusBarConnectedDisplays.unsafeAssertInNewMode() - } - - override fun createInstanceForDisplay(displayId: Int): StatusBarConfigurationState? { - val displayWindowProperties = - displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null - val configController = - statusBarConfigurationControllerStore.forDisplay(displayId) ?: return null - return factory.create(displayWindowProperties.context, configController) - } - - override val instanceClass = StatusBarConfigurationState::class.java -} - -@SysUISingleton -class SingleDisplayStatusBarConfigurationStateStore -@Inject -constructor(@Main globalConfigState: ConfigurationState) : - StatusBarConfigurationStateStore, - PerDisplayStore<StatusBarConfigurationState> by SingleDisplayStore( - globalConfigState as StatusBarConfigurationState - ) { - - init { - StatusBarConnectedDisplays.assertInLegacyMode() - } -} - -@Module -object StatusBarConfigurationStateModule { - - @Provides - @SysUISingleton - fun store( - singleDisplayLazy: Lazy<SingleDisplayStatusBarConfigurationStateStore>, - multiDisplayLazy: Lazy<MultiDisplayStatusBarConfigurationStateStore>, - ): StatusBarConfigurationStateStore { - return if (StatusBarConnectedDisplays.isEnabled) { - multiDisplayLazy.get() - } else { - singleDisplayLazy.get() - } - } - - @Provides - @SysUISingleton - @IntoMap - @ClassKey(StatusBarConfigurationStateStore::class) - fun storeAsCoreStartable( - multiDisplayLazy: Lazy<MultiDisplayStatusBarConfigurationStateStore> - ): CoreStartable { - return if (StatusBarConnectedDisplays.isEnabled) { - multiDisplayLazy.get() - } else { - CoreStartable.NOP - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt new file mode 100644 index 000000000000..3168a22c56ad --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.data.repository + +import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR +import com.android.systemui.common.ui.ConfigurationState +import com.android.systemui.common.ui.ConfigurationStateImpl +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository +import com.android.systemui.display.data.repository.PerDisplayInstanceProvider +import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl +import com.android.systemui.display.data.repository.PerDisplayRepository +import com.android.systemui.display.data.repository.SingleInstanceRepositoryImpl +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import dagger.Lazy +import dagger.Module +import dagger.Provides +import javax.inject.Inject + +@SysUISingleton +class StatusBarPerDisplayConfigurationStateProvider +@Inject +constructor( + private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository, + private val statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore, + private val factory: ConfigurationStateImpl.Factory, +) : PerDisplayInstanceProvider<ConfigurationState> { + + override fun createInstance(displayId: Int): ConfigurationState? { + val displayWindowProperties = + displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null + val configController = + statusBarConfigurationControllerStore.forDisplay(displayId) ?: return null + return factory.create(displayWindowProperties.context, configController) + } +} + +@Module +object StatusBarPerDisplayConfigurationStateModule { + + @Provides + @SysUISingleton + fun store( + instanceProvider: Lazy<StatusBarPerDisplayConfigurationStateProvider>, + factory: PerDisplayInstanceRepositoryImpl.Factory<ConfigurationState>, + defaultInstance: ConfigurationState, + ): PerDisplayRepository<ConfigurationState> { + val name = "ConfigurationState" + return if (StatusBarConnectedDisplays.isEnabled) { + factory.create(name, instanceProvider.get()) + } else { + SingleInstanceRepositoryImpl(name, defaultInstance) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt index 08a8960f6c96..d1307aa0ee31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt @@ -50,7 +50,9 @@ object StatusBarPopupChips { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt b/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt index 7a985e7fae9a..7b1024ca65dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt @@ -50,7 +50,9 @@ object StatusBarNoHunBehavior { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index 4053d065cb16..8be9e410f8f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -124,8 +124,14 @@ public final class NotificationClicker implements View.OnClickListener { Notification notification = sbn.getNotification(); if (notification.contentIntent != null || notification.fullScreenIntent != null || row.getEntry().isBubble()) { - row.setBubbleClickListener(v -> - mNotificationActivityStarter.onNotificationBubbleIconClicked(row.getEntry())); + if (NotificationBundleUi.isEnabled()) { + row.setBubbleClickListener( + v -> row.getEntryAdapter().onNotificationBubbleIconClicked()); + } else { + row.setBubbleClickListener(v -> + mNotificationActivityStarter.onNotificationBubbleIconClicked( + row.getEntry())); + } row.setOnClickListener(this); row.setOnDragSuccessListener(mOnDragSuccessListener); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java index 6dd44a123538..41353b9921bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java @@ -45,8 +45,6 @@ import kotlinx.coroutines.flow.StateFlowKt; */ public class BundleEntry extends PipelineEntry { - private final BundleEntryAdapter mEntryAdapter; - // TODO(b/394483200): move NotificationEntry's implementation to PipelineEntry? private final MutableStateFlow<Boolean> mSensitive = StateFlowKt.MutableStateFlow(false); @@ -55,7 +53,6 @@ public class BundleEntry extends PipelineEntry { public BundleEntry(String key) { super(key); - mEntryAdapter = new BundleEntryAdapter(); } @Nullable @@ -86,126 +83,9 @@ public class BundleEntry extends PipelineEntry { return false; } - @VisibleForTesting - public BundleEntryAdapter getEntryAdapter() { - return mEntryAdapter; - } - - public class BundleEntryAdapter implements EntryAdapter { - - /** - * TODO (b/394483200): convert to PipelineEntry.ROOT_ENTRY when pipeline is migrated? - */ - @Override - public GroupEntry getParent() { - return GroupEntry.ROOT_ENTRY; - } - - @Override - public boolean isTopLevelEntry() { - return true; - } - - @NonNull - @Override - public String getKey() { - return mKey; - } - - @Override - @Nullable - public ExpandableNotificationRow getRow() { - return mRow; - } - - @Override - public boolean isGroupRoot() { - return true; - } - - @Override - public StateFlow<Boolean> isSensitive() { - return BundleEntry.this.mSensitive; - } - - @Override - public boolean isClearable() { - // TODO(b/394483200): check whether all of the children are clearable, when implemented - return true; - } - - @Override - public int getTargetSdk() { - return Build.VERSION_CODES.CUR_DEVELOPMENT; - } - - @Override - public String getSummarization() { - return null; - } - - @Override - public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) { - return Notification.COLOR_DEFAULT; - } - - @Override - public boolean canPeek() { - return false; - } - - @Override - public long getWhen() { - return 0; - } - - @Override - public IconPack getIcons() { - // TODO(b/396446620): implement bundle icons - return null; - } - - @Override - public boolean isColorized() { - return false; - } - - @Override - @Nullable - public StatusBarNotification getSbn() { - return null; - } - - @Override - public boolean canDragAndDrop() { - return false; - } - - @Override - public boolean isBubbleCapable() { - return false; - } - - @Override - @Nullable - public String getStyle() { - return null; - } - - @Override - public int getSectionBucket() { - return mBucket; - } - - @Override - public boolean isAmbient() { - return false; - } - - @Override - public boolean isFullScreenCapable() { - return false; - } + @Nullable + public ExpandableNotificationRow getRow() { + return mRow; } public static final List<BundleEntry> ROOT_BUNDLES = List.of( @@ -213,4 +93,8 @@ public class BundleEntry extends PipelineEntry { new BundleEntry(SOCIAL_MEDIA_ID), new BundleEntry(NEWS_ID), new BundleEntry(RECS_ID)); + + public MutableStateFlow<Boolean> isSensitive() { + return mSensitive; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt new file mode 100644 index 000000000000..64db9df8270c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection + +import android.app.Notification +import android.content.Context +import android.os.Build +import android.service.notification.StatusBarNotification +import com.android.systemui.statusbar.notification.icon.IconPack +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import kotlinx.coroutines.flow.StateFlow + +class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { + /** TODO (b/394483200): convert to PipelineEntry.ROOT_ENTRY when pipeline is migrated? */ + override fun getParent(): GroupEntry { + return GroupEntry.ROOT_ENTRY + } + + override fun isTopLevelEntry(): Boolean { + return true + } + + override fun getKey(): String { + return entry.key + } + + override fun getRow(): ExpandableNotificationRow? { + return entry.row + } + + override fun isGroupRoot(): Boolean { + return true + } + + override fun isSensitive(): StateFlow<Boolean> { + return entry.isSensitive + } + + override fun isClearable(): Boolean { + // TODO(b/394483200): check whether all of the children are clearable, when implemented + return true + } + + override fun getTargetSdk(): Int { + return Build.VERSION_CODES.CUR_DEVELOPMENT + } + + override fun getSummarization(): String? { + return null + } + + override fun getContrastedColor( + context: Context?, + isLowPriority: Boolean, + backgroundColor: Int, + ): Int { + return Notification.COLOR_DEFAULT + } + + override fun canPeek(): Boolean { + return false + } + + override fun getWhen(): Long { + return 0 + } + + override fun getIcons(): IconPack? { + // TODO(b/396446620): implement bundle icons + return null + } + + override fun isColorized(): Boolean { + return false + } + + override fun getSbn(): StatusBarNotification? { + return null + } + + override fun canDragAndDrop(): Boolean { + return false + } + + override fun isBubbleCapable(): Boolean { + return false + } + + override fun getStyle(): String? { + return null + } + + override fun getSectionBucket(): Int { + return entry.bucket + } + + override fun isAmbient(): Boolean { + return false + } + + override fun isFullScreenCapable(): Boolean { + return false + } + + override fun onNotificationBubbleIconClicked() { + // do nothing. these cannot be a bubble + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java index 307a9573d5d8..0e75b6050678 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java @@ -50,7 +50,7 @@ public interface EntryAdapter { /** * Gets the view that this entry is backing. */ - @NonNull + @Nullable ExpandableNotificationRow getRow(); /** @@ -135,5 +135,10 @@ public interface EntryAdapter { default boolean isFullScreenCapable() { return false; } + + /** + * Process a click on a notification bubble icon + */ + void onNotificationBubbleIconClicked(); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactory.kt index 9b7cd704aa2f..b7a84ddef103 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.dagger.qualifiers -import javax.inject.Qualifier +package com.android.systemui.statusbar.notification.collection -@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Tracing +/** Creates an appropriate EntryAdapter for the entry type given */ +interface EntryAdapterFactory { + fun create(entry: PipelineEntry): EntryAdapter +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt new file mode 100644 index 000000000000..779c25a3b402 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection + +import androidx.annotation.VisibleForTesting +import com.android.internal.logging.MetricsLogger +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider +import javax.inject.Inject + +/** Creates an appropriate EntryAdapter for the entry type given */ +class EntryAdapterFactoryImpl +@Inject +constructor( + private val notificationActivityStarter: NotificationActivityStarter, + private val metricsLogger: MetricsLogger, + private val peopleNotificationIdentifier: PeopleNotificationIdentifier, + private val iconStyleProvider: NotificationIconStyleProvider, + private val visualStabilityCoordinator: VisualStabilityCoordinator, +) : EntryAdapterFactory { + override fun create(entry: PipelineEntry): EntryAdapter { + return if (entry is NotificationEntry) { + NotificationEntryAdapter( + notificationActivityStarter, + metricsLogger, + peopleNotificationIdentifier, + iconStyleProvider, + visualStabilityCoordinator, + entry, + ) + } else { + BundleEntryAdapter((entry as BundleEntry)) + } + } + + @VisibleForTesting + fun getNotificationActivityStarter() : NotificationActivityStarter { + return notificationActivityStarter + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index b19ba3a18826..3d8ba7f335bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -109,7 +109,6 @@ public final class NotificationEntry extends ListEntry { private final String mKey; private StatusBarNotification mSbn; private Ranking mRanking; - private final NotifEntryAdapter mEntryAdapter; /* * Bookkeeping members @@ -270,141 +269,6 @@ public final class NotificationEntry extends ListEntry { mKey = sbn.getKey(); setSbn(sbn); setRanking(ranking); - mEntryAdapter = new NotifEntryAdapter(); - } - - public class NotifEntryAdapter implements EntryAdapter { - @Override - public PipelineEntry getParent() { - return NotificationEntry.this.getParent(); - } - - @Override - public boolean isTopLevelEntry() { - return getParent() != null - && (getParent() == ROOT_ENTRY || ROOT_BUNDLES.contains(getParent())); - } - - @Override - public String getKey() { - return NotificationEntry.this.getKey(); - } - - @Override - public ExpandableNotificationRow getRow() { - return NotificationEntry.this.getRow(); - } - - @Override - public boolean isGroupRoot() { - if (isTopLevelEntry() || getParent() == null) { - return false; - } - if (NotificationEntry.this.getParent() instanceof GroupEntry parentGroupEntry) { - return parentGroupEntry.getSummary() == NotificationEntry.this; - } - return false; - } - - @Override - public StateFlow<Boolean> isSensitive() { - return NotificationEntry.this.isSensitive(); - } - - @Override - public boolean isClearable() { - return NotificationEntry.this.isClearable(); - } - - @Override - public int getTargetSdk() { - return NotificationEntry.this.targetSdk; - } - - @Override - public String getSummarization() { - return getRanking().getSummarization(); - } - - @Override - public void prepareForInflation() { - getSbn().clearPackageContext(); - } - - @Override - public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) { - return NotificationEntry.this.getContrastedColor( - context, isLowPriority, backgroundColor); - } - - @Override - public boolean canPeek() { - return isStickyAndNotDemoted(); - } - - @Override - public long getWhen() { - return getSbn().getNotification().getWhen(); - } - - @Override - public IconPack getIcons() { - return NotificationEntry.this.getIcons(); - } - - @Override - public boolean isColorized() { - return getSbn().getNotification().isColorized(); - } - - @Override - @Nullable - public StatusBarNotification getSbn() { - return NotificationEntry.this.getSbn(); - } - - @Override - public boolean canDragAndDrop() { - boolean canBubble = canBubble(); - Notification notif = getSbn().getNotification(); - PendingIntent dragIntent = notif.contentIntent != null ? notif.contentIntent - : notif.fullScreenIntent; - if (dragIntent != null && dragIntent.isActivity() && !canBubble) { - return true; - } - return false; - } - - @Override - public boolean isBubbleCapable() { - return NotificationEntry.this.isBubble(); - } - - @Override - @Nullable - public String getStyle() { - return getNotificationStyle(); - } - - @Override - public int getSectionBucket() { - return mBucket; - } - - @Override - public boolean isAmbient() { - return mRanking.isAmbient(); - } - - @Override - public boolean isFullScreenCapable() { - return getSbn().getNotification().fullScreenIntent != null; - } - - } - - public EntryAdapter getEntryAdapter() { - return mEntryAdapter; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt new file mode 100644 index 000000000000..0ff2dd7c7f43 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection + +import android.content.Context +import android.service.notification.StatusBarNotification +import com.android.internal.logging.MetricsLogger +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator +import com.android.systemui.statusbar.notification.icon.IconPack +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider +import kotlinx.coroutines.flow.StateFlow + +class NotificationEntryAdapter( + private val notificationActivityStarter: NotificationActivityStarter, + private val metricsLogger: MetricsLogger, + private val peopleNotificationIdentifier: PeopleNotificationIdentifier, + private val iconStyleProvider: NotificationIconStyleProvider, + private val visualStabilityCoordinator: VisualStabilityCoordinator, + private val entry: NotificationEntry, +) : EntryAdapter { + + override fun getParent(): PipelineEntry? { + return entry.parent + } + + override fun isTopLevelEntry(): Boolean { + return parent != null && + (parent === GroupEntry.ROOT_ENTRY || BundleEntry.ROOT_BUNDLES.contains(parent)) + } + + override fun getKey(): String { + return entry.key + } + + override fun getRow(): ExpandableNotificationRow { + return entry.row + } + + override fun isGroupRoot(): Boolean { + if (isTopLevelEntry || parent == null) { + return false + } + return (entry.parent as? GroupEntry)?.summary == entry + } + + override fun isSensitive(): StateFlow<Boolean> { + return entry.isSensitive + } + + override fun isClearable(): Boolean { + return entry.isClearable + } + + override fun getTargetSdk(): Int { + return entry.targetSdk + } + + override fun getSummarization(): String? { + return entry.ranking?.summarization + } + + override fun prepareForInflation() { + entry.sbn.clearPackageContext() + } + + override fun getContrastedColor( + context: Context?, + isLowPriority: Boolean, + backgroundColor: Int, + ): Int { + return entry.getContrastedColor(context, isLowPriority, backgroundColor) + } + + override fun canPeek(): Boolean { + return entry.isStickyAndNotDemoted + } + + override fun getWhen(): Long { + return entry.sbn.notification.getWhen() + } + + override fun getIcons(): IconPack { + return entry.icons + } + + override fun isColorized(): Boolean { + return entry.sbn.notification.isColorized + } + + override fun getSbn(): StatusBarNotification { + return entry.sbn + } + + override fun canDragAndDrop(): Boolean { + val canBubble: Boolean = entry.canBubble() + val notif = entry.sbn.notification + val dragIntent = + if (notif.contentIntent != null) notif.contentIntent else notif.fullScreenIntent + if (dragIntent != null && dragIntent.isActivity && !canBubble) { + return true + } + return false + } + + override fun isBubbleCapable(): Boolean { + return entry.isBubble + } + + override fun getStyle(): String? { + return entry.notificationStyle + } + + override fun getSectionBucket(): Int { + return entry.bucket + } + + override fun isAmbient(): Boolean { + return entry.ranking.isAmbient + } + + override fun isFullScreenCapable(): Boolean { + return entry.sbn.notification.fullScreenIntent != null + } + + override fun onNotificationBubbleIconClicked() { + notificationActivityStarter.onNotificationBubbleIconClicked(entry) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt index 179a87d7f007..cf7881214ddf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt @@ -50,7 +50,9 @@ object StabilizeHeadsUpGroup { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 53d5dbc58677..ef3da9498f70 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -37,6 +37,8 @@ import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactory; +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl; import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl; @@ -340,4 +342,8 @@ public interface NotificationsModule { return MagneticNotificationRowManager.getEmpty(); } } + + /** Provides an instance of {@link EntryAdapterFactory} */ + @Binds + EntryAdapterFactory provideEntryAdapterFactory(EntryAdapterFactoryImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt index d3a44f9b1497..52bf894c796c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt @@ -50,7 +50,9 @@ object ModesEmptyShadeFix { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt index dbe046fdf30b..90faa4d2c714 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt @@ -50,7 +50,9 @@ object NotifRedesignFooter { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt index c53831671bfc..f52d351cfb0e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt @@ -50,7 +50,9 @@ object NotificationsHunSharedAnimationValues { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt index 30aec8dabcaa..147a5afea306 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt @@ -20,8 +20,8 @@ import android.view.Display import androidx.lifecycle.lifecycleScope import com.android.app.tracing.traceSection import com.android.systemui.common.ui.ConfigurationState +import com.android.systemui.display.data.repository.PerDisplayRepository import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.statusbar.data.repository.StatusBarConfigurationStateStore import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel @@ -36,7 +36,7 @@ class NotificationIconContainerStatusBarViewBinder @Inject constructor( private val viewModel: NotificationIconContainerStatusBarViewModel, - private val configurationStore: StatusBarConfigurationStateStore, + private val configurationStateRepository: PerDisplayRepository<ConfigurationState>, private val defaultConfigurationState: ConfigurationState, private val systemBarUtilsState: SystemBarUtilsState, private val failureTracker: StatusBarIconViewBindingFailureTracker, @@ -57,7 +57,7 @@ constructor( } } val configurationState: ConfigurationState = - configurationStore.forDisplay(displayId) ?: defaultConfigurationState + configurationStateRepository[displayId] ?: defaultConfigurationState lifecycleScope.launch { NotificationIconContainerViewBinder.bind( displayId = displayId, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt index 5bf5766a886c..da59a40a1624 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt @@ -50,7 +50,9 @@ object PromotedNotificationUi { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt index 8679975998f2..c6e3da1c5750 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt @@ -49,7 +49,9 @@ object PromotedNotificationUiAod { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt index 287e00257bc5..adeddde8ccc3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt @@ -50,7 +50,9 @@ object PromotedNotificationUiForceExpanded { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 987068df3ee9..76d285007d1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -111,6 +111,8 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryAdapter; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -121,6 +123,7 @@ import com.android.systemui.statusbar.notification.people.PeopleNotificationIden import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction; +import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModelImpl; import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss; @@ -428,7 +431,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView onExpansionChanged(true /* userAction */, wasExpanded); } } else if (mEnableNonGroupedNotificationExpand) { - if (v.isAccessibilityFocused()) { + if (v != null && v.isAccessibilityFocused()) { mPrivateLayout.setFocusOnVisibilityChange(); } boolean nowExpanded; @@ -1818,6 +1821,22 @@ public class ExpandableNotificationRow extends ActivatableNotificationView ); } + /** + * Init the bundle header view. The ComposeView is initialized within with the passed viewModel. + * This can only be init once and not in conjunction with any other header view. + */ + public void initBundleHeader(@NonNull BundleHeaderViewModelImpl bundleHeaderViewModel) { + if (NotificationBundleUi.isUnexpectedlyInLegacyMode()) return; + NotificationChildrenContainer childrenContainer = getChildrenContainerNonNull(); + bundleHeaderViewModel.setOnExpandClickListener(mExpandClickListener); + + childrenContainer.initBundleHeader(bundleHeaderViewModel); + + if (TransparentHeaderFix.isEnabled()) { + updateBackgroundForGroupState(); + } + } + public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { boolean wasAboveShelf = isAboveShelf(); boolean changed = headsUpAnimatingAway != mHeadsupDisappearRunning; @@ -2124,7 +2143,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * Initialize row. */ public void initialize( - NotificationEntry entry, + EntryAdapter entryAdapter, + PipelineEntry entry, RemoteInputViewSubcomponent.Factory rivSubcomponentFactory, String appName, @NonNull String notificationKey, @@ -2152,11 +2172,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView NotificationRebindingTracker notificationRebindingTracker) { if (NotificationBundleUi.isEnabled()) { + mEntryAdapter = entryAdapter; // TODO (b/395857098): remove when all usages are migrated - mEntryAdapter = entry.getEntryAdapter(); - mEntry = entry; + mEntry = (NotificationEntry) entry; } else { - mEntry = entry; + mEntry = (NotificationEntry) entry; } mAppName = appName; mRebindingTracker = notificationRebindingTracker; @@ -3048,7 +3068,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mUserLocked = userLocked; mPrivateLayout.setUserExpanding(userLocked); - mPublicLayout.setUserExpanding(userLocked); // This is intentionally not guarded with mIsSummaryWithChildren since we might have had // children but not anymore. if (mChildrenContainer != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 02e8f4917da4..ac55930f5c11 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -45,7 +45,11 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.FeedbackIcon; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.NotificationActivityStarter; +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactory; +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl; +import com.android.systemui.statusbar.notification.collection.PipelineEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -56,6 +60,7 @@ import com.android.systemui.statusbar.notification.people.PeopleNotificationIden import com.android.systemui.statusbar.notification.row.dagger.AppName; import com.android.systemui.statusbar.notification.row.dagger.NotificationKey; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; +import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -118,8 +123,8 @@ public class ExpandableNotificationRowController implements NotifViewController private final IStatusBarService mStatusBarService; private final UiEventLogger mUiEventLogger; private final MSDLPlayer mMSDLPlayer; - private final NotificationSettingsController mSettingsController; + private final EntryAdapterFactory mEntryAdapterFactory; @VisibleForTesting final NotificationSettingsController.Listener mSettingsListener = @@ -285,7 +290,8 @@ public class ExpandableNotificationRowController implements NotifViewController IStatusBarService statusBarService, UiEventLogger uiEventLogger, MSDLPlayer msdlPlayer, - NotificationRebindingTracker notificationRebindingTracker) { + NotificationRebindingTracker notificationRebindingTracker, + EntryAdapterFactory entryAdapterFactory) { mView = view; mListContainer = listContainer; mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory; @@ -322,14 +328,16 @@ public class ExpandableNotificationRowController implements NotifViewController mStatusBarService = statusBarService; mUiEventLogger = uiEventLogger; mMSDLPlayer = msdlPlayer; + mEntryAdapterFactory = entryAdapterFactory; } /** * Initialize the controller. */ - public void init(NotificationEntry entry) { + public void init(PipelineEntry entry) { mActivatableNotificationViewController.init(); mView.initialize( + mEntryAdapterFactory.create(entry), entry, mRemoteInputViewSubcomponentFactory, mAppName, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt index 77135802eced..d02f636728fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt @@ -42,14 +42,6 @@ class MagicActionBackgroundDrawable( private val buttonShape = Path() // Color and style - private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - val bgColor = - context.getColor( - com.android.internal.R.color.materialColorPrimaryContainer - ) - color = bgColor - style = Paint.Style.FILL - } private val outlinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { val outlineColor = context.getColor( @@ -99,7 +91,6 @@ class MagicActionBackgroundDrawable( canvas.save() // Draw background canvas.clipPath(buttonShape) - canvas.drawPath(buttonShape, bgPaint) // Apply gradient to outline canvas.drawPath(buttonShape, outlinePaint) updateGradient(boundsF) @@ -128,13 +119,11 @@ class MagicActionBackgroundDrawable( } override fun setAlpha(alpha: Int) { - bgPaint.alpha = alpha outlinePaint.alpha = alpha invalidateSelf() } override fun setColorFilter(colorFilter: ColorFilter?) { - bgPaint.colorFilter = colorFilter outlinePaint.colorFilter = colorFilter invalidateSelf() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt index 11c942c9bcc0..37212a387fad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt @@ -49,7 +49,9 @@ object NotificationBundleUi { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if @@ -57,4 +59,4 @@ object NotificationBundleUi { */ @JvmStatic inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index e830d18b7d73..315d37e55bc3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -39,9 +39,11 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.compose.ui.platform.ComposeView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.NotificationExpandButton; +import com.android.systemui.notifications.ui.composable.row.BundleHeaderKt; import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.CrossFadeHelper; @@ -58,6 +60,7 @@ import com.android.systemui.statusbar.notification.row.HybridGroupManager; import com.android.systemui.statusbar.notification.row.HybridNotificationView; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation; +import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModelImpl; import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; @@ -119,6 +122,13 @@ public class NotificationChildrenContainer extends ViewGroup */ private boolean mEnableShadowOnChildNotifications; + /** + * This view is only set when this NCC is a bundle. If this view is set, all other header + * view variants have to be null. + */ + private ComposeView mBundleHeaderView; + private BundleHeaderViewModelImpl mBundleHeaderViewModel; + private NotificationHeaderView mGroupHeader; private NotificationHeaderViewWrapper mGroupHeaderWrapper; private NotificationHeaderView mMinimizedGroupHeader; @@ -189,6 +199,9 @@ public class NotificationChildrenContainer extends ViewGroup R.dimen.notification_children_container_top_padding); mHeaderHeight = mCollapsedHeaderMargin + mAdditionalExpandedHeaderMargin; } + if (mBundleHeaderView != null) { + initBundleDimens(); + } mCollapsedBottomPadding = res.getDimensionPixelOffset( R.dimen.notification_children_collapsed_bottom_padding); mEnableShadowOnChildNotifications = @@ -244,6 +257,10 @@ public class NotificationChildrenContainer extends ViewGroup mMinimizedGroupHeader.getMeasuredWidth(), mMinimizedGroupHeader.getMeasuredHeight()); } + if (mBundleHeaderView != null) { + mBundleHeaderView.layout(0, 0, mBundleHeaderView.getMeasuredWidth(), + mBundleHeaderView.getMeasuredHeight()); + } } @Override @@ -295,6 +312,9 @@ public class NotificationChildrenContainer extends ViewGroup if (mMinimizedGroupHeader != null) { mMinimizedGroupHeader.measure(widthMeasureSpec, headerHeightSpec); } + if (mBundleHeaderView != null) { + mBundleHeaderView.measure(widthMeasureSpec, headerHeightSpec); + } setMeasuredDimension(width, height); Trace.endSection(); @@ -489,6 +509,28 @@ public class NotificationChildrenContainer extends ViewGroup } /** + * Init the bundle header view. The ComposeView is initialized within with the passed viewModel. + * This can only be init once and not in conjunction with any other header view. + */ + public void initBundleHeader(@NonNull BundleHeaderViewModelImpl viewModel) { + if (NotificationBundleUi.isUnexpectedlyInLegacyMode()) return; + if (mBundleHeaderView != null) return; + initBundleDimens(); + + mBundleHeaderViewModel = viewModel; + mBundleHeaderView = BundleHeaderKt.createComposeView(mBundleHeaderViewModel, getContext()); + addView(mBundleHeaderView); + invalidate(); + } + + private void initBundleDimens() { + NotificationBundleUi.unsafeAssertInNewMode(); + mCollapsedHeaderMargin = mHeaderHeight; + mAdditionalExpandedHeaderMargin = 0; + mCollapsedBottomPadding = 0; + } + + /** * Set the group header view * @param headerView view to set * @param onClickListener OnClickListener of the header view @@ -1311,6 +1353,17 @@ public class NotificationChildrenContainer extends ViewGroup mGroupHeader.setHeaderBackgroundDrawable(null); } } + if (mBundleHeaderView != null) { + if (expanded) { + ColorDrawable cd = new ColorDrawable(); + cd.setColor(mContainingNotification.calculateBgColor()); + // TODO(b/389839492): The backgroundDrawable needs an outline like in the original: + // setOutlineProvider(mProvider); + mBundleHeaderViewModel.setBackgroundDrawable(cd); + } else { + mBundleHeaderViewModel.setBackgroundDrawable(null); + } + } } public int getMaxContentHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index a277597e23df..c1aa5f12aa99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -266,7 +266,7 @@ constructor( combine(shadeModeInteractor.shadeMode, shadeInteractor.qsExpansion) { shadeMode, qsExpansion -> when (shadeMode) { - is ShadeMode.Dual -> false + is ShadeMode.Dual, is ShadeMode.Split -> true is ShadeMode.Single -> qsExpansion < 0.5f } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index face1c7512ce..fc721bfae369 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -109,6 +109,7 @@ import com.android.systemui.AutoReinflateContainer; import com.android.systemui.CoreStartable; import com.android.systemui.DejankUtils; import com.android.systemui.EventLogTags; +import com.android.systemui.Flags; import com.android.systemui.InitController; import com.android.systemui.Prefs; import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; @@ -3183,12 +3184,27 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { new ActivityTransitionAnimator.Listener() { @Override public void onTransitionAnimationStart() { - mKeyguardViewMediator.setBlursDisabledForAppLaunch(true); + if (!Flags.notificationShadeBlur() || !Flags.moveTransitionAnimationLayer()) { + mKeyguardViewMediator.setBlursDisabledForAppLaunch(true); + } + } + + @Override + public void onTransitionAnimationProgress(float linearProgress) { + if (Flags.notificationShadeBlur() && Flags.moveTransitionAnimationLayer()) { + mNotificationShadeDepthControllerLazy.get() + .onTransitionAnimationProgress(linearProgress); + } } @Override public void onTransitionAnimationEnd() { - mKeyguardViewMediator.setBlursDisabledForAppLaunch(false); + if (Flags.notificationShadeBlur() && Flags.moveTransitionAnimationLayer()) { + mNotificationShadeDepthControllerLazy.get() + .onTransitionAnimationEnd(); + } else { + mKeyguardViewMediator.setBlursDisabledForAppLaunch(false); + } } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt index 6afcd8a4fad8..0ac87178086f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt @@ -52,7 +52,9 @@ object StatusBarChipsModernization { * the flag is not enabled to ensure that the refactor author catches issues in testing. */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt index 1d1cfd6cdf68..b2d314c3590b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt @@ -50,7 +50,9 @@ object StatusBarSignalPolicyRefactorEthernet { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt index 16f24f1d5821..5d7ce91b332c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt @@ -34,7 +34,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.Flags -import com.android.systemui.qs.panels.ui.compose.PagerDots +import com.android.systemui.common.ui.compose.PagerDots import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel @Composable diff --git a/packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt b/packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt index 59c05c2493c3..a94d51a21af3 100644 --- a/packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt +++ b/packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt @@ -50,7 +50,9 @@ object DeprecateDpmSupervisionApis { * Caution!! Using this check incorrectly will cause crashes in nextfood builds! */ @JvmStatic - inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) + @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return")) + inline fun unsafeAssertInNewMode() = + RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt index bb8fe46be5b0..3ac6c7bc0c6b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt @@ -28,7 +28,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.volume.Events -import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent +import com.android.systemui.volume.dialog.dagger.factory.VolumeDialogComponentFactory import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor import javax.inject.Inject import kotlinx.coroutines.awaitCancellation @@ -37,7 +37,7 @@ class VolumeDialog @Inject constructor( @Application context: Context, - private val componentFactory: VolumeDialogComponent.Factory, + private val componentFactory: VolumeDialogComponentFactory, private val visibilityInteractor: VolumeDialogVisibilityInteractor, ) : ComponentDialog(context, R.style.Theme_SystemUI_Dialog_Volume) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt index 434f6b567048..36b2b48ece21 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt @@ -16,6 +16,7 @@ package com.android.systemui.volume.dialog.dagger +import com.android.systemui.volume.dialog.dagger.factory.VolumeDialogComponentFactory import com.android.systemui.volume.dialog.dagger.module.VolumeDialogModule import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope @@ -38,9 +39,9 @@ interface VolumeDialogComponent { fun sliderComponentFactory(): VolumeDialogSliderComponent.Factory @Subcomponent.Factory - interface Factory { + interface Factory : VolumeDialogComponentFactory { - fun create( + override fun create( /** * Provides a coroutine scope to use inside [VolumeDialogScope]. * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/factory/VolumeDialogComponentFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/factory/VolumeDialogComponentFactory.kt new file mode 100644 index 000000000000..d909f26dd1dc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/factory/VolumeDialogComponentFactory.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.dagger.factory + +import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent +import kotlinx.coroutines.CoroutineScope + +/** Common interface for all dagger Subcomponent.Factory providing [VolumeDialogComponent]. */ +interface VolumeDialogComponentFactory { + + fun create(scope: CoroutineScope): VolumeDialogComponent +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt index 7b08317d5265..fcf4d110f269 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt @@ -16,10 +16,12 @@ package com.android.systemui.volume.dialog.dagger.module +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepository import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepositoryImpl import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder +import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBinder import com.android.systemui.volume.dialog.ui.binder.ViewBinder import dagger.Binds @@ -27,7 +29,7 @@ import dagger.Module import dagger.Provides /** Dagger module for volume dialog code in the volume package */ -@Module +@Module(subcomponents = [VolumeDialogSliderComponent::class]) interface VolumeDialogModule { @Binds @@ -38,6 +40,7 @@ interface VolumeDialogModule { companion object { @Provides + @VolumeDialog fun provideViewBinders( slidersViewBinder: VolumeDialogSlidersViewBinder, ringerViewBinder: VolumeDialogRingerViewBinder, diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt index 547c51d1cefd..35752ae08260 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.dialog.dagger.module import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent +import com.android.systemui.volume.dialog.dagger.factory.VolumeDialogComponentFactory import com.android.systemui.volume.dialog.shared.model.CsdWarningConfigModel import com.android.systemui.volume.dialog.utils.VolumeTracer import com.android.systemui.volume.dialog.utils.VolumeTracerImpl @@ -27,6 +28,11 @@ import dagger.Provides @Module(subcomponents = [VolumeDialogComponent::class]) interface VolumeDialogPluginModule { + @Binds + fun bindVolumeDialogComponentFactory( + factory: VolumeDialogComponent.Factory + ): VolumeDialogComponentFactory + @Binds fun bindVolumeTracer(volumeTracer: VolumeTracerImpl): VolumeTracer companion object { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt index 577e47bb3b83..d0ed24d0b86d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.dialog.sliders.dagger import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel import dagger.BindsInstance import dagger.Subcomponent @@ -34,6 +35,8 @@ interface VolumeDialogSliderComponent { fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder + fun sliderViewModel(): VolumeDialogSliderViewModel + @Subcomponent.Factory interface Factory { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt index 5de8fe54505f..11d9df4294c0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt @@ -37,6 +37,7 @@ import com.android.systemui.common.ui.view.onApplyWindowInsets import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.res.R import com.android.systemui.util.kotlin.awaitCancellationThenDispose +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory @@ -72,7 +73,7 @@ constructor( private val viewModel: VolumeDialogViewModel, private val jankListenerFactory: JankListenerFactory, private val tracer: VolumeTracer, - private val viewBinders: List<@JvmSuppressWildcards ViewBinder>, + @VolumeDialog private val viewBinders: List<@JvmSuppressWildcards ViewBinder>, ) { private val halfOpenedOffsetPx: Float = diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index 562481567536..5c893da45b8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -28,7 +28,6 @@ import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBO import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_EXPANDED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED; -import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -60,7 +59,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastSender; -import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.screenshot.TimeoutHandler; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.util.concurrency.FakeExecutor; @@ -102,7 +100,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @Mock private UiEventLogger mUiEventLogger; private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); - private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private Animator mAnimator; @@ -152,8 +149,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mSampleClipData = new ClipData("Test", new String[]{"text/plain"}, new ClipData.Item("Test Item")); - - mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests } /** @@ -170,13 +165,13 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { getFakeBroadcastDispatcher(), mBroadcastSender, mTimeoutHandler, - mFeatureFlags, mClipboardUtils, mExecutor, mClipboardImageLoader, mClipboardTransitionExecutor, mClipboardIndicationProvider, - mUiEventLogger); + mUiEventLogger, + new ActionIntentCreator()); verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture()); mCallbacks = mOverlayCallbacksCaptor.getValue(); } @@ -193,7 +188,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { ClipData clipData = new ClipData("", new String[]{"image/png"}, new ClipData.Item(Uri.parse(""))); - mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false); mOverlayController.setClipData(clipData, ""); @@ -208,7 +202,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { ClipData clipData = new ClipData("", new String[]{"resource/png"}, new ClipData.Item(Uri.parse(""))); - mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false); mOverlayController.setClipData(clipData, ""); @@ -219,7 +212,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @Test public void test_setClipData_textData_legacy() { - mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false); initController(); mOverlayController.setClipData(mSampleClipData, "abc"); @@ -232,7 +224,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @Test public void test_setClipData_sensitiveTextData_legacy() { - mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false); initController(); ClipDescription description = mSampleClipData.getDescription(); @@ -250,7 +241,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @Test public void test_setClipData_repeatedCalls_legacy() { when(mAnimator.isRunning()).thenReturn(true); - mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false); initController(); mOverlayController.setClipData(mSampleClipData, ""); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt index 8418598c256b..116a2caa6dda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt @@ -19,9 +19,9 @@ package com.android.systemui.shared.condition import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.shared.condition.Condition.START_EAGERLY -import com.android.systemui.shared.condition.Condition.START_LAZILY -import com.android.systemui.shared.condition.Condition.START_WHEN_NEEDED +import com.android.systemui.shared.condition.Condition.Companion.START_EAGERLY +import com.android.systemui.shared.condition.Condition.Companion.START_LAZILY +import com.android.systemui.shared.condition.Condition.Companion.START_WHEN_NEEDED import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -40,7 +40,7 @@ class CombinedConditionTest : SysuiTestCase() { scope: CoroutineScope, initialValue: Boolean?, overriding: Boolean = false, - @StartStrategy private val startStrategy: Int = START_WHEN_NEEDED, + @StartStrategy override val startStrategy: Int = START_WHEN_NEEDED, ) : Condition(scope, initialValue, overriding) { private var _started = false val started: Boolean @@ -54,10 +54,6 @@ class CombinedConditionTest : SysuiTestCase() { _started = false } - override fun getStartStrategy(): Int { - return startStrategy - } - fun setValue(value: Boolean?) { value?.also { updateCondition(value) } ?: clearCondition() } @@ -75,13 +71,8 @@ class CombinedConditionTest : SysuiTestCase() { val combinedCondition = CombinedCondition( scope = this, - conditions = - listOf( - eagerCondition, - lazyCondition, - startWhenNeededCondition, - ), - operand = Evaluator.OP_OR + conditions = listOf(eagerCondition, lazyCondition, startWhenNeededCondition), + operand = Evaluator.OP_OR, ) val callback = Condition.Callback {} @@ -124,13 +115,8 @@ class CombinedConditionTest : SysuiTestCase() { val combinedCondition = CombinedCondition( scope = this, - conditions = - listOf( - startWhenNeededCondition, - lazyCondition, - eagerCondition, - ), - operand = Evaluator.OP_AND + conditions = listOf(startWhenNeededCondition, lazyCondition, eagerCondition), + operand = Evaluator.OP_AND, ) val callback = Condition.Callback {} @@ -175,7 +161,7 @@ class CombinedConditionTest : SysuiTestCase() { FakeCondition( scope = this, initialValue = false, - startStrategy = START_WHEN_NEEDED + startStrategy = START_WHEN_NEEDED, ) } .toList() @@ -214,7 +200,7 @@ class CombinedConditionTest : SysuiTestCase() { FakeCondition( scope = this, initialValue = false, - startStrategy = START_WHEN_NEEDED + startStrategy = START_WHEN_NEEDED, ) } .toList() @@ -262,7 +248,7 @@ class CombinedConditionTest : SysuiTestCase() { FakeCondition( scope = this, initialValue = false, - startStrategy = START_WHEN_NEEDED + startStrategy = START_WHEN_NEEDED, ) } .toList() @@ -300,9 +286,9 @@ class CombinedConditionTest : SysuiTestCase() { overridingCondition1, lazyCondition, startWhenNeededCondition, - overridingCondition2 + overridingCondition2, ), - operand = Evaluator.OP_OR + operand = Evaluator.OP_OR, ) val callback = Condition.Callback {} @@ -414,11 +400,7 @@ class CombinedConditionTest : SysuiTestCase() { fun testEmptyConditions() = runSelfCancelingTest { for (operand in intArrayOf(Evaluator.OP_OR, Evaluator.OP_AND)) { val combinedCondition = - CombinedCondition( - scope = this, - conditions = emptyList(), - operand = operand, - ) + CombinedCondition(scope = this, conditions = emptyList(), operand = operand) val callback = Condition.Callback {} combinedCondition.addCallback(callback) @@ -435,9 +417,7 @@ class CombinedConditionTest : SysuiTestCase() { * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which * is then automatically canceled and cleaned-up. */ - private fun runSelfCancelingTest( - block: suspend CoroutineScope.() -> Unit, - ) = + private fun runSelfCancelingTest(block: suspend CoroutineScope.() -> Unit) = runBlocking(IMMEDIATE) { val scope = CoroutineScope(coroutineContext + Job()) block(scope) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index af89403c5397..623989ec5809 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -67,6 +67,7 @@ import com.android.systemui.scene.shared.model.sceneDataSource import com.android.systemui.scene.ui.view.mockWindowRootViewProvider import com.android.systemui.settings.brightness.data.repository.brightnessMirrorShowingRepository import com.android.systemui.settings.displayTracker +import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.domain.interactor.shadeLayoutParams @@ -203,5 +204,6 @@ class KosmosJavaAdapter() { val windowRootViewBlurInteractor by lazy { kosmos.windowRootViewBlurInteractor } val sysuiState by lazy { kosmos.sysUiState } val displayTracker by lazy { kosmos.displayTracker } + val fakeShadeDisplaysRepository by lazy { kosmos.fakeShadeDisplaysRepository } val sysUIStateDispatcher by lazy { kosmos.sysUIStateDispatcher } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt index c337ac201b3d..8e98fe31a56a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt @@ -18,6 +18,10 @@ package com.android.systemui.statusbar.notification import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.phone.statusBarNotificationActivityStarter +import org.mockito.kotlin.mock var Kosmos.notificationActivityStarter: NotificationActivityStarter by Kosmos.Fixture { statusBarNotificationActivityStarter } + +var Kosmos.mockNotificationActivityStarter: NotificationActivityStarter by +Kosmos.Fixture { mock<NotificationActivityStarter>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerKosmos.kt new file mode 100644 index 000000000000..f3cdabb5813e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.render + +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +var Kosmos.groupMembershipManager by Kosmos.Fixture { mock<GroupMembershipManager>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractorKosmos.kt new file mode 100644 index 000000000000..c263c5d96610 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractorKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.people + +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +val Kosmos.notificationPersonExtractor by Kosmos.Fixture { mock<NotificationPersonExtractor>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifierKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifierKosmos.kt new file mode 100644 index 000000000000..20982eb43797 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifierKosmos.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.people + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.collection.render.groupMembershipManager + +val Kosmos.peopleNotificationIdentifier by + Kosmos.Fixture { + PeopleNotificationIdentifierImpl(notificationPersonExtractor, groupMembershipManager) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt new file mode 100644 index 000000000000..e99f61e7cd13 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import com.android.internal.logging.metricsLogger +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl +import com.android.systemui.statusbar.notification.collection.coordinator.visualStabilityCoordinator +import com.android.systemui.statusbar.notification.mockNotificationActivityStarter +import com.android.systemui.statusbar.notification.people.peopleNotificationIdentifier +import com.android.systemui.statusbar.notification.row.icon.notificationIconStyleProvider + +val Kosmos.entryAdapterFactory by +Kosmos.Fixture { + EntryAdapterFactoryImpl( + mockNotificationActivityStarter, + metricsLogger, + peopleNotificationIdentifier, + notificationIconStyleProvider, + visualStabilityCoordinator, + ) +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt index 771e1a5dccc3..ff4fbf9f0e94 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt @@ -53,8 +53,11 @@ import com.android.systemui.statusbar.SmartReplyController import com.android.systemui.statusbar.notification.ColorUpdateLogger import com.android.systemui.statusbar.notification.ConversationNotificationManager import com.android.systemui.statusbar.notification.ConversationNotificationProcessor +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider @@ -76,6 +79,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag import com.android.systemui.statusbar.notification.row.icon.AppIconProviderImpl +import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProviderImpl import com.android.systemui.statusbar.notification.row.icon.NotificationRowIconViewInflaterFactory import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor @@ -364,11 +368,22 @@ class ExpandableNotificationRowBuilder( ) val row = rowInflaterTask.inflateSynchronously(context, null, entry) + val entryAdapter = + EntryAdapterFactoryImpl( + Mockito.mock(NotificationActivityStarter::class.java), + Mockito.mock(MetricsLogger::class.java), + Mockito.mock(PeopleNotificationIdentifier::class.java), + Mockito.mock(NotificationIconStyleProvider::class.java), + Mockito.mock(VisualStabilityCoordinator::class.java), + ) + .create(entry) + entry.row = row mIconManager.createIcons(entry) mBindPipelineEntryListener.onEntryInit(entry) mBindPipeline.manageRow(entry, row) row.initialize( + entryAdapter, entry, Mockito.mock(RemoteInputViewSubcomponent.Factory::class.java, STUB_ONLY), APP_NAME, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt index 4ca044d60f3f..34218646b818 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt @@ -33,6 +33,8 @@ import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSlide import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderViewModel import com.android.systemui.volume.dialog.sliders.ui.volumeDialogOverscrollViewBinder import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderViewBinder import com.android.systemui.volume.mediaControllerRepository @@ -60,6 +62,9 @@ fun Kosmos.volumeDialogSliderComponent(type: VolumeDialogSliderType): VolumeDial } } + override fun sliderViewModel(): VolumeDialogSliderViewModel = + localKosmos.volumeDialogSliderViewModel + override fun sliderViewBinder(): VolumeDialogSliderViewBinder = localKosmos.volumeDialogSliderViewBinder diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 296f7cfe93ba..f54027ec0272 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -514,6 +514,7 @@ final class UiModeManagerService extends SystemService { mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR; registerVrStateListener(); // register listeners + // LINT.IfChange(fi_cb) context.getContentResolver() .registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE), false, mDarkThemeObserver, 0); @@ -523,6 +524,7 @@ final class UiModeManagerService extends SystemService { Secure.getUriFor(ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED), false, mForceInvertStateObserver, UserHandle.USER_ALL); } + // LINT.ThenChange(/core/java/android/view/ViewRootImpl.java:fi_cb) context.getContentResolver().registerContentObserver( Secure.getUriFor(Secure.CONTRAST_LEVEL), false, mContrastObserver, UserHandle.USER_ALL); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 1503d889c298..eea667ef2f39 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1443,17 +1443,32 @@ class ProcessRecord implements WindowProcessListener { void onProcessFrozen() { mProfile.onProcessFrozen(); - if (mThread != null) mThread.onProcessPaused(); + final ApplicationThreadDeferred t; + synchronized (mService) { + t = mThread; + } + // Release the lock before calling the notifier, in case that calls back into AM. + if (t != null) t.onProcessPaused(); } void onProcessUnfrozen() { - if (mThread != null) mThread.onProcessUnpaused(); + final ApplicationThreadDeferred t; + synchronized (mService) { + t = mThread; + } + // Release the lock before calling the notifier, in case that calls back into AM. + if (t != null) t.onProcessUnpaused(); mProfile.onProcessUnfrozen(); mServices.onProcessUnfrozen(); } void onProcessFrozenCancelled() { - if (mThread != null) mThread.onProcessPausedCancelled(); + final ApplicationThreadDeferred t; + synchronized (mService) { + t = mThread; + } + // Release the lock before calling the notifier, in case that calls back into AM. + if (t != null) t.onProcessPausedCancelled(); mServices.onProcessFrozenCancelled(); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index af726bd28718..68ad8f7e9433 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2902,12 +2902,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final long ident = Binder.clearCallingIdentity(); try { if (windowPerceptible != null && !windowPerceptible) { - if ((vis & InputMethodService.IME_VISIBLE) != 0) { - vis &= ~InputMethodService.IME_VISIBLE; - vis |= InputMethodService.IME_VISIBLE_IMPERCEPTIBLE; - } - } else { - vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE; + vis &= ~InputMethodService.IME_VISIBLE; } final var curId = bindingController.getCurId(); // TODO(b/305849394): Make mMenuController multi-user aware. diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 6ab30595e46b..098f113dc11a 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -897,7 +897,7 @@ final class DefaultPermissionGrantPolicy { SearchManager.INTENT_ACTION_GLOBAL_SEARCH, userId); grantPermissionsToSystemPackage(pm, voiceSearchPackage, userId, PHONE_PERMISSIONS, CALENDAR_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS, - COARSE_BACKGROUND_LOCATION_PERMISSIONS); + COARSE_BACKGROUND_LOCATION_PERMISSIONS, CONTACTS_PERMISSIONS); revokeRuntimePermissions(pm, voiceSearchPackage, FINE_LOCATION_PERMISSIONS, false, userId); } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index d620e98d3437..bac732637d8d 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1173,12 +1173,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } - final ComponentName wpService = mWallpaper.getComponent(); // The broadcast of package update could be delayed after service disconnected. Try // to re-bind the service for 10 seconds. mWallpaper.mBindSource = BindSource.CONNECTION_TRY_TO_REBIND; - if (bindWallpaperComponentLocked( - wpService, true, false, mWallpaper, null)) { + boolean success; + if (liveWallpaperContentHandling()) { + success = bindWallpaperDescriptionLocked( + mWallpaper.getDescription(), /* force= */ true, + /* fromUser= */ false, mWallpaper, /* reply= */ null); + } else { + success = bindWallpaperComponentLocked(mWallpaper.getComponent(), /* force= */ + true, /* fromUser= */false, mWallpaper, /* reply= */ null); + } + if (success) { mWallpaper.connection.scheduleTimeoutLocked(); } else if (SystemClock.uptimeMillis() - mWallpaper.lastDiedTime < WALLPAPER_RECONNECT_TIMEOUT_MS) { @@ -1189,7 +1196,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // Timeout Slog.w(TAG, "Reverting to built-in wallpaper!"); clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null); - final String flattened = wpService.flattenToString(); + final String flattened = mWallpaper.getComponent().flattenToString(); EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED, flattened.substring(0, Math.min(flattened.length(), MAX_WALLPAPER_COMPONENT_LOG_LENGTH))); diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 36686fc086f1..2287a687700c 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -288,9 +288,10 @@ public class BackgroundActivityStartController { } private boolean isHomeApp(int uid, @Nullable String packageName) { - if (getService().mHomeProcess != null) { - // Fast check - return uid == getService().mHomeProcess.mUid; + WindowProcessController homeProcess = getService().mHomeProcess; + if (homeProcess != null && (homeProcess.mUid != SYSTEM_UID || packageName == null)) { + // Fast check (skip if sharing system UID and we have a package name) + return uid == homeProcess.mUid; } if (packageName == null) { return false; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 65efa310658f..353ccd5836c8 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3284,6 +3284,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } /** + * Whether the display is allowed to switch the content mode between extended and mirroring. + * If the content mode is extended, the display will start home activity and show system + * decorations, such as wallpapaer, status bar and navigation bar. + * If the content mode is mirroring, the display will not show home activity or system + * decorations. + * The content mode is switched when {@link Display#canHostTasks()} changes. + * * Note that we only allow displays that are able to show system decorations to use the content * mode switch; however, not all displays that are able to show system decorations are allowed * to use the content mode switch. diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index d7f91e009c92..2093ccc86653 100644 --- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -156,19 +156,6 @@ class SeamlessAppRotationTest(flicker: LegacyFlickerTest) : RotationTransition(f flicker.assertLayers { isVisible(testApp) } } - /** Checks that [testApp] layer covers the entire screen during the whole transition */ - @Presubmit - @Test - fun appLayerRotates() { - flicker.assertLayers { - this.invoke("entireScreenCovered") { entry -> - entry.entry.displays.map { display -> - entry.visibleRegion(testApp).coversExactly(display.layerStackSpace) - } - } - } - } - /** {@inheritDoc} */ @Test @Ignore("Not applicable to this CUJ. App is full screen") @@ -225,7 +212,6 @@ class SeamlessAppRotationTest(flicker: LegacyFlickerTest) : RotationTransition(f visibleLayersShownMoreThanOneConsecutiveEntry() visibleWindowsShownMoreThanOneConsecutiveEntry() - runAndIgnoreAssumptionViolation { appLayerRotates() } runAndIgnoreAssumptionViolation { appLayerAlwaysVisible() } runAndIgnoreAssumptionViolation { navBarLayerIsVisibleAtStartAndEnd() } runAndIgnoreAssumptionViolation { navBarWindowIsAlwaysVisible() } diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 7c24a4adca3d..98af8b2f53b5 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -39,7 +39,6 @@ android:value="true" /> <activity android:name=".SimpleActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity" - android:theme="@style/CutoutShortEdges" android:label="SimpleActivity" android:exported="true"> <intent-filter> @@ -49,7 +48,6 @@ </activity> <activity android:name=".ImeActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity" - android:theme="@style/CutoutShortEdges" android:label="ImeActivity" android:exported="true"> <intent-filter> @@ -58,7 +56,6 @@ </intent-filter> </activity> <activity android:name=".ImeActivityAutoFocus" - android:theme="@style/CutoutShortEdges" android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus" android:windowSoftInputMode="stateVisible" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" @@ -81,7 +78,6 @@ </activity> <activity android:name=".SeamlessRotationActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity" - android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" android:showWhenLocked="true" android:label="SeamlessActivity" @@ -92,7 +88,6 @@ </intent-filter> </activity> <activity android:name=".NonResizeableActivity" - android:theme="@style/CutoutShortEdges" android:resizeableActivity="false" android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity" android:label="NonResizeableActivity" @@ -174,7 +169,6 @@ </activity> <activity android:name=".LaunchNewActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity" - android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize" android:label="LaunchNewActivity" android:exported="true"> @@ -185,7 +179,6 @@ </activity> <activity android:name=".LaunchNewTaskActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewTaskActivity" - android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize" android:label="LaunchNewTaskActivity" android:exported="true"> @@ -207,7 +200,6 @@ </activity> <activity android:name=".PortraitOnlyActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity" - android:theme="@style/CutoutShortEdges" android:screenOrientation="portrait" android:configChanges="orientation|screenSize" android:exported="true"> @@ -219,7 +211,6 @@ <activity android:name=".ImeEditorPopupDialogActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity" android:configChanges="orientation|screenSize" - android:theme="@style/CutoutShortEdges" android:label="ImeEditorPopupDialogActivity" android:exported="true"> <intent-filter> @@ -229,7 +220,6 @@ </activity> <activity android:name=".ShowWhenLockedActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity" - android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize" android:label="ShowWhenLockedActivity" android:showWhenLocked="true" @@ -241,7 +231,6 @@ </activity> <activity android:name=".NotificationActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity" - android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize" android:label="NotificationActivity" android:exported="true"> @@ -254,7 +243,6 @@ android:name=".ActivityEmbeddingMainActivity" android:label="ActivityEmbedding Main" android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" - android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:exported="true"> <intent-filter> @@ -266,7 +254,6 @@ android:name=".ActivityEmbeddingTrampolineActivity" android:label="ActivityEmbedding Trampoline" android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" - android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:exported="false"> </activity> @@ -274,7 +261,6 @@ android:name=".ActivityEmbeddingSecondaryActivity" android:label="ActivityEmbedding Secondary" android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" - android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:supportsPictureInPicture="true" android:exported="false"/> @@ -282,21 +268,18 @@ android:name=".ActivityEmbeddingThirdActivity" android:label="ActivityEmbedding Third" android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" - android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:exported="false"/> <activity android:name=".ActivityEmbeddingAlwaysExpandActivity" android:label="ActivityEmbedding AlwaysExpand" android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" - android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:exported="false"/> <activity android:name=".ActivityEmbeddingPlaceholderPrimaryActivity" android:label="ActivityEmbedding Placeholder Primary" android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" - android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:exported="false"> </activity> @@ -304,7 +287,6 @@ android:name=".ActivityEmbeddingPlaceholderSecondaryActivity" android:label="ActivityEmbedding Placeholder Secondary" android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" - android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:exported="false"/> <activity android:name=".MailActivity" @@ -334,7 +316,6 @@ android:supportsPictureInPicture="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:taskAffinity="com.android.server.wm.flicker.testapp.PipActivity" - android:theme="@style/CutoutShortEdges" android:launchMode="singleTop" android:label="PipActivity" android:exported="true"> @@ -350,7 +331,6 @@ <activity android:name=".BottomHalfPipLaunchingActivity" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity" - android:theme="@style/CutoutShortEdges" android:label="BottomHalfPipLaunchingActivity" android:exported="true"> <intent-filter> @@ -371,7 +351,6 @@ <activity android:name=".SplitScreenActivity" android:resizeableActivity="true" android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity" - android:theme="@style/CutoutShortEdges" android:label="SplitScreenPrimaryActivity" android:exported="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"> @@ -383,7 +362,6 @@ <activity android:name=".SplitScreenSecondaryActivity" android:resizeableActivity="true" android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity" - android:theme="@style/CutoutShortEdges" android:label="SplitScreenSecondaryActivity" android:exported="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"> @@ -396,7 +374,6 @@ </activity> <activity android:name=".SendNotificationActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.SendNotificationActivity" - android:theme="@style/CutoutShortEdges" android:label="SendNotificationActivity" android:exported="true"> <intent-filter> @@ -408,7 +385,6 @@ android:name=".LaunchBubbleActivity" android:label="LaunchBubbleActivity" android:exported="true" - android:theme="@style/CutoutShortEdges" android:launchMode="singleTop"> <intent-filter> <action android:name="android.intent.action.MAIN"/> @@ -420,7 +396,6 @@ android:name=".BubbleActivity" android:label="BubbleActivity" android:exported="false" - android:theme="@style/CutoutShortEdges" android:resizeableActivity="true"/> <activity android:name=".TransferSplashscreenActivity" @@ -468,4 +443,4 @@ </service> </application> <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/> -</manifest> +</manifest>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bottom_half_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bottom_half_pip.xml index 2f9c3aa82057..15e2a798b2cf 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bottom_half_pip.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bottom_half_pip.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:orientation="vertical" android:background="@android:color/holo_blue_bright"> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml index 7c7b2cacaefb..4bc70996eba8 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml @@ -16,6 +16,7 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:fitsSystemWindows="true" android:layout_width="match_parent" android:layout_height="match_parent"> <Button diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml index f0dfdfce035f..e98ffd045d3d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml @@ -19,5 +19,6 @@ android:id="@+id/root_activity_layout" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:orientation="vertical"> </LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml index 939ba81a47ea..16c906d9ee42 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:background="@android:color/holo_orange_light"> <LinearLayout diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml index 6d4de995bd73..eedb910b2b4e 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml @@ -19,6 +19,7 @@ android:id="@+id/secondary_activity_layout" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:orientation="vertical"> <Button @@ -49,4 +50,4 @@ android:layout_height="48dp" android:text="Enter pip" /> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml index 507c1b622613..5e5203cb0545 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml @@ -17,6 +17,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/holo_green_light" + android:fitsSystemWindows="true" android:focusableInTouchMode="true" android:orientation="vertical"> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml index fe7bced690f9..2ab5fe7b9d6c 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:background="@android:color/holo_orange_light"> <Button android:id="@+id/launch_second_activity" diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml index 8a97433ede04..fcef791afe2e 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml @@ -19,6 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" + android:fitsSystemWindows="true" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml index 6d5a9dd29248..aeb8423680a3 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:orientation="vertical" android:background="@android:color/holo_orange_light"> @@ -29,4 +30,4 @@ android:text="NonResizeableActivity" android:textAppearance="?android:attr/textAppearanceLarge"/> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml index 365a0ea017f6..d66b3d74e114 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:orientation="vertical" android:background="@android:color/holo_blue_bright"> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_simple.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_simple.xml index 5d94e5177dcc..42213d6812da 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_simple.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_simple.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:background="@android:color/holo_orange_light"> </LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml index 79e88e49b99e..16c3bc07d55c 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:orientation="vertical" android:background="@android:color/holo_green_light"> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml index ed9feaf1eade..7643cf5de216 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:orientation="vertical" android:background="@android:color/holo_blue_light"> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml index c34d2003ef42..79e7bb5de96e 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:gravity="center" android:orientation="vertical" android:background="@android:color/holo_orange_light"> @@ -39,4 +40,4 @@ android:gravity="center_vertical|center_horizontal" android:text="Start Media Projection with extra intent" android:textAppearance="?android:attr/textAppearanceLarge"/> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_surfaceview.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_surfaceview.xml index 0b4693dec6e1..4d12168ca8db 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_surfaceview.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_surfaceview.xml @@ -19,6 +19,7 @@ android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:background="@android:color/holo_orange_light"> <SurfaceView diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml index 0730ded66ce4..32df5f015414 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml @@ -15,6 +15,7 @@ ~ limitations under the License. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:fitsSystemWindows="true" android:layout_width="match_parent" android:layout_height="match_parent"> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml index ff4ead95f16e..a0c87fd17fc3 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml @@ -17,6 +17,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:orientation="vertical" android:background="@android:color/black"> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml index 78072006f681..da58b3f43c73 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml @@ -18,10 +18,12 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:background="@android:color/holo_orange_light"> <Button android:id="@+id/post_notification" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_gravity="center_horizontal|center_vertical" android:text="Post Notification" /> </LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml index 8f75d175d00a..ec3135c0a42d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:background="@android:color/holo_orange_light"> <Button android:id="@+id/launch_new_task" diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml index 837d050b73ff..ca3244820893 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -36,10 +36,6 @@ <item name="android:windowLayoutInDisplayCutoutMode">default</item> </style> - <style name="CutoutShortEdges" parent="@style/DefaultTheme"> - <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> - </style> - <style name="CutoutNever" parent="@style/DefaultTheme"> <item name="android:windowLayoutInDisplayCutoutMode">never</item> </style> @@ -78,4 +74,4 @@ <!-- Here we want to match the duration of our AVD --> <item name="android:windowSplashScreenAnimationDuration">900</item> </style> -</resources> +</resources>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java index 4418b5a5ff82..30bf616cfe74 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java @@ -30,7 +30,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.util.Log; -import android.view.WindowManager; public class ImeActivity extends Activity { @@ -64,10 +63,6 @@ public class ImeActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - WindowManager.LayoutParams p = getWindow().getAttributes(); - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(p); setContentView(R.layout.activity_ime); final var filter = new IntentFilter(); diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java index 95f933f97bb2..887a15c9ea90 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java @@ -30,8 +30,6 @@ public class ImeEditorPopupDialogActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); WindowManager.LayoutParams p = getWindow().getAttributes(); - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; p.softInputMode = SOFT_INPUT_STATE_ALWAYS_HIDDEN; getWindow().setAttributes(p); LinearLayout layout = new LinearLayout(this); diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java index e5710c850f07..97d7a64d5ebf 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java @@ -19,17 +19,12 @@ package com.android.server.wm.flicker.testapp; import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import android.view.WindowManager; import android.widget.Button; public class LaunchNewActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - WindowManager.LayoutParams p = getWindow().getAttributes(); - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(p); setContentView(R.layout.activity_launch_new); Button button = findViewById(R.id.launch_second_activity); diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java index 1809781b33e6..402a393e7028 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java @@ -21,17 +21,12 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import android.view.WindowManager; import android.widget.Button; public class LaunchNewTaskActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - WindowManager.LayoutParams p = getWindow().getAttributes(); - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(p); setContentView(R.layout.task_button); Button button = findViewById(R.id.launch_new_task); diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java index d6427abcc65a..61254385e980 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java @@ -45,14 +45,10 @@ public class NotificationActivity extends Activity { requestPermissions(new String[] { POST_NOTIFICATIONS }, 0); } - WindowManager.LayoutParams p = getWindow().getAttributes(); - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(p); setContentView(R.layout.notification_button); - Button button = findViewById(R.id.post_notification); - button.setOnClickListener(v -> postNotification()); + ((Button) findViewById(R.id.post_notification)) + .setOnClickListener(v -> postNotification()); createNotificationChannel(); } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java index ee25ab2fb66c..e030dcf0db9b 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java @@ -47,8 +47,6 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.Rational; import android.view.View; -import android.view.Window; -import android.view.WindowManager; import android.widget.CheckBox; import android.widget.RadioButton; @@ -145,12 +143,6 @@ public class PipActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final Window window = getWindow(); - final WindowManager.LayoutParams layoutParams = window.getAttributes(); - layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - window.setAttributes(layoutParams); - setContentView(R.layout.activity_pip); findViewById(R.id.media_session_start) diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java index b1876b5e5511..552d843676bb 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java @@ -18,16 +18,11 @@ package com.android.server.wm.flicker.testapp; import android.app.Activity; import android.os.Bundle; -import android.view.WindowManager; public class PortraitOnlyActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - WindowManager.LayoutParams p = getWindow().getAttributes(); - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(p); setContentView(R.layout.activity_simple); } } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java index ce7a0059fa2d..e98c34db0cd9 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java @@ -61,8 +61,6 @@ public class SeamlessRotationActivity extends Activity { private void enableSeamlessRotation() { WindowManager.LayoutParams p = getWindow().getAttributes(); p.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java index 6f94b74ccf41..a533c9052574 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java @@ -18,16 +18,11 @@ package com.android.server.wm.flicker.testapp; import android.app.Activity; import android.os.Bundle; -import android.view.WindowManager; public class ShowWhenLockedActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - WindowManager.LayoutParams p = getWindow().getAttributes(); - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(p); setContentView(R.layout.activity_simple); } } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SimpleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SimpleActivity.java index 699abf87d341..c56eefe32189 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SimpleActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SimpleActivity.java @@ -18,16 +18,11 @@ package com.android.server.wm.flicker.testapp; import android.app.Activity; import android.os.Bundle; -import android.view.WindowManager; public class SimpleActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - WindowManager.LayoutParams p = getWindow().getAttributes(); - p.layoutInDisplayCutoutMode = WindowManager.LayoutParams - .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(p); setContentView(R.layout.activity_simple); } } |