diff options
280 files changed, 6366 insertions, 1699 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 302168d845fa..834398e5c2c2 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -824,6 +824,11 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +cc_aconfig_library { + name: "android.media.tv.flags-aconfig-cc", + aconfig_declarations: "android.media.tv.flags-aconfig", +} + // Permissions aconfig_declarations { name: "android.permission.flags-aconfig", diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 787fdee6ee16..3314b4aa0d71 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -648,7 +648,7 @@ java_api_library { java_api_library { name: "android-non-updatable.stubs.module_lib.from-text", - api_surface: "module_lib", + api_surface: "module-lib", api_contributions: [ "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", @@ -668,7 +668,7 @@ java_api_library { // generated from this module, as this module is strictly used for hiddenapi only. java_api_library { name: "android-non-updatable.stubs.test_module_lib", - api_surface: "module_lib", + api_surface: "module-lib", api_contributions: [ "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", @@ -689,7 +689,7 @@ java_api_library { java_api_library { name: "android-non-updatable.stubs.system_server.from-text", - api_surface: "system_server", + api_surface: "system-server", api_contributions: [ "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", diff --git a/core/api/current.txt b/core/api/current.txt index 6964866db7f3..3883a9667355 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -452,7 +452,7 @@ package android { field public static final int activityCloseExitAnimation = 16842939; // 0x10100bb field public static final int activityOpenEnterAnimation = 16842936; // 0x10100b8 field public static final int activityOpenExitAnimation = 16842937; // 0x10100b9 - field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final int adServiceTypes; + field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final int adServiceTypes = 16844452; // 0x10106a4 field public static final int addPrintersActivity = 16843750; // 0x10103e6 field public static final int addStatesFromChildren = 16842992; // 0x10100f0 field public static final int adjustViewBounds = 16843038; // 0x101011e @@ -1017,7 +1017,7 @@ package android { field public static final int insetRight = 16843192; // 0x10101b8 field public static final int insetTop = 16843193; // 0x10101b9 field public static final int installLocation = 16843447; // 0x10102b7 - field @FlaggedApi("android.security.enable_intent_matching_flags") public static final int intentMatchingFlags; + field @FlaggedApi("android.security.enable_intent_matching_flags") public static final int intentMatchingFlags = 16844457; // 0x10106a9 field public static final int interactiveUiTimeout = 16844181; // 0x1010595 field public static final int interpolator = 16843073; // 0x1010141 field public static final int intro = 16844395; // 0x101066b @@ -1072,7 +1072,7 @@ package android { field public static final int label = 16842753; // 0x1010001 field public static final int labelFor = 16843718; // 0x10103c6 field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235 - field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int languageSettingsActivity; + field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int languageSettingsActivity = 16844453; // 0x10106a5 field public static final int languageTag = 16844040; // 0x1010508 field public static final int largeHeap = 16843610; // 0x101035a field public static final int largeScreens = 16843398; // 0x1010286 @@ -1085,7 +1085,7 @@ package android { field public static final int layout = 16842994; // 0x10100f2 field public static final int layoutAnimation = 16842988; // 0x10100ec field public static final int layoutDirection = 16843698; // 0x10103b2 - field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int layoutLabel; + field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int layoutLabel = 16844458; // 0x10106aa field public static final int layoutMode = 16843738; // 0x10103da field public static final int layout_above = 16843140; // 0x1010184 field public static final int layout_alignBaseline = 16843142; // 0x1010186 @@ -1207,7 +1207,7 @@ package android { field public static final int minResizeHeight = 16843670; // 0x1010396 field public static final int minResizeWidth = 16843669; // 0x1010395 field public static final int minSdkVersion = 16843276; // 0x101020c - field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int minSdkVersionFull; + field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int minSdkVersionFull = 16844461; // 0x10106ad field public static final int minWidth = 16843071; // 0x101013f field public static final int minimumHorizontalAngle = 16843901; // 0x101047d field public static final int minimumVerticalAngle = 16843902; // 0x101047e @@ -1282,7 +1282,7 @@ package android { field public static final int paddingStart = 16843699; // 0x10103b3 field public static final int paddingTop = 16842967; // 0x10100d7 field public static final int paddingVertical = 16844094; // 0x101053e - field @FlaggedApi("android.content.pm.app_compat_option_16kb") public static final int pageSizeCompat; + field @FlaggedApi("android.content.pm.app_compat_option_16kb") public static final int pageSizeCompat = 16844459; // 0x10106ab field public static final int panelBackground = 16842846; // 0x101005e field public static final int panelColorBackground = 16842849; // 0x1010061 field public static final int panelColorForeground = 16842848; // 0x1010060 @@ -1624,7 +1624,7 @@ package android { field public static final int summaryColumn = 16843426; // 0x10102a2 field public static final int summaryOff = 16843248; // 0x10101f0 field public static final int summaryOn = 16843247; // 0x10101ef - field @FlaggedApi("android.view.accessibility.supplemental_description") public static final int supplementalDescription; + field @FlaggedApi("android.view.accessibility.supplemental_description") public static final int supplementalDescription = 16844456; // 0x10106a8 field public static final int supportedTypes = 16844369; // 0x1010651 field public static final int supportsAssist = 16844016; // 0x10104f0 field public static final int supportsBatteryGameMode = 16844374; // 0x1010656 @@ -1871,7 +1871,7 @@ package android { field public static final int wallpaperIntraOpenExitAnimation = 16843416; // 0x1010298 field public static final int wallpaperOpenEnterAnimation = 16843411; // 0x1010293 field public static final int wallpaperOpenExitAnimation = 16843412; // 0x1010294 - field @FlaggedApi("android.nfc.nfc_associated_role_services") public static final int wantsRoleHolderPriority; + field @FlaggedApi("android.nfc.nfc_associated_role_services") public static final int wantsRoleHolderPriority = 16844460; // 0x10106ac field public static final int webTextViewStyle = 16843449; // 0x10102b9 field public static final int webViewStyle = 16842885; // 0x1010085 field public static final int weekDayTextAppearance = 16843592; // 0x1010348 @@ -42267,6 +42267,7 @@ package android.service.notification { method public int getRank(); method @NonNull public java.util.List<android.app.Notification.Action> getSmartActions(); method @NonNull public java.util.List<java.lang.CharSequence> getSmartReplies(); + method @FlaggedApi("android.app.nm_summarization") @Nullable public String getSummarization(); method public int getSuppressedVisualEffects(); method public int getUserSentiment(); method public boolean isAmbient(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 937a9ffaf210..8a5276c1ce08 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -464,7 +464,7 @@ package android { public static final class R.attr { field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600 - field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final int backgroundPermission; + field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final int backgroundPermission = 16844455; // 0x10106a7 field @FlaggedApi("android.content.res.manifest_flagging") public static final int featureFlag = 16844428; // 0x101068c field public static final int gameSessionService = 16844373; // 0x1010655 field public static final int hotwordDetectionService = 16844326; // 0x1010626 @@ -543,7 +543,7 @@ package android { field public static final int config_systemCallStreaming = 17039431; // 0x1040047 field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039 field public static final int config_systemContacts = 17039403; // 0x104002b - field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final int config_systemDependencyInstaller; + field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final int config_systemDependencyInstaller = 17039434; // 0x104004a field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046 field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035 @@ -555,7 +555,7 @@ package android { field public static final int config_systemTextIntelligence = 17039414; // 0x1040036 field public static final int config_systemUi = 17039418; // 0x104003a field public static final int config_systemUiIntelligence = 17039410; // 0x1040032 - field @FlaggedApi("android.permission.flags.system_vendor_intelligence_role_enabled") public static final int config_systemVendorIntelligence; + field @FlaggedApi("android.permission.flags.system_vendor_intelligence_role_enabled") public static final int config_systemVendorIntelligence = 17039435; // 0x104004b field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037 field public static final int config_systemWearHealthService = 17039428; // 0x1040044 field public static final int config_systemWellbeing = 17039408; // 0x1040030 @@ -13446,6 +13446,7 @@ package android.service.notification { field public static final String KEY_RANKING_SCORE = "key_ranking_score"; field public static final String KEY_SENSITIVE_CONTENT = "key_sensitive_content"; field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; + field @FlaggedApi("android.app.nm_summarization") public static final String KEY_SUMMARIZATION = "key_summarization"; field public static final String KEY_TEXT_REPLIES = "key_text_replies"; field @FlaggedApi("android.service.notification.notification_classification") public static final String KEY_TYPE = "key_type"; field public static final String KEY_USER_SENTIMENT = "key_user_sentiment"; diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 16dcf2ad7e45..8d20e46c7df8 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -25,6 +25,7 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -483,6 +484,19 @@ public class ActivityTaskManager { } /** + * @return Whether the app could be universal resizeable (assuming it's on a large screen and + * ignoring possible overrides) + * @hide + */ + public boolean canBeUniversalResizeable(@NonNull ApplicationInfo appInfo) { + try { + return getService().canBeUniversalResizeable(appInfo); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Detaches the navigation bar from the app it was attached to during a transition. * @hide */ diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index c6f62a21641d..4b1afa517122 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -158,6 +158,12 @@ interface IActivityTaskManager { void reportAssistContextExtras(in IBinder assistToken, in Bundle extras, in AssistStructure structure, in AssistContent content, in Uri referrer); + /** + * @return whether the app could be universal resizeable (assuming it's on a large screen and + * ignoring possible overrides) + */ + boolean canBeUniversalResizeable(in ApplicationInfo appInfo); + void setFocusedRootTask(int taskId); ActivityTaskManager.RootTaskInfo getFocusedRootTaskInfo(); Rect getTaskBounds(int taskId); diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index b7285c38290c..7454c44997d6 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -60,6 +60,7 @@ import com.android.internal.statusbar.NotificationVisibility; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -195,12 +196,40 @@ public class StatusBarManager { */ private static final int DEFAULT_SIM_LOCKED_DISABLED_FLAGS = DISABLE_EXPAND; - /** @hide */ - public static final int NAVIGATION_HINT_BACK_ALT = 1 << 0; - /** @hide */ - public static final int NAVIGATION_HINT_IME_SHOWN = 1 << 1; - /** @hide */ - public static final int NAVIGATION_HINT_IME_SWITCHER_SHOWN = 1 << 2; + /** + * The back button is visually adjusted to indicate that it will dismiss the IME when pressed. + * This only takes effect while the IME is visible. By default, it is set while the IME is + * visible, but may be overridden by the + * {@link android.inputmethodservice.InputMethodService.BackDispositionMode backDispositionMode} + * set by the IME. + * + * @hide + */ + public static final int NAVIGATION_HINT_BACK_ALT = 1 << 0; + /** + * The IME is visible. + * + * @hide + */ + public static final int NAVIGATION_HINT_IME_SHOWN = 1 << 1; + /** + * The IME Switcher button is visible. This only takes effect while the IME is visible. + * + * @hide + */ + public static final int NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN = 1 << 2; + /** + * Navigation bar flags related to the IME state. + * + * @hide + */ + @IntDef(flag = true, prefix = { "NAVIGATION_HINT_" }, value = { + NAVIGATION_HINT_BACK_ALT, + NAVIGATION_HINT_IME_SHOWN, + NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NavigationHint {} /** @hide */ public static final int WINDOW_STATUS_BAR = 1; @@ -1325,6 +1354,22 @@ public class StatusBarManager { } /** @hide */ + @NonNull + public static String navigationHintsToString(@NavigationHint int hints) { + final var hintStrings = new ArrayList<String>(); + if ((hints & NAVIGATION_HINT_BACK_ALT) != 0) { + hintStrings.add("NAVIGATION_HINT_BACK_ALT"); + } + if ((hints & NAVIGATION_HINT_IME_SHOWN) != 0) { + hintStrings.add("NAVIGATION_HINT_IME_SHOWN"); + } + if ((hints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0) { + hintStrings.add("NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN"); + } + return String.join(" | ", hintStrings); + } + + /** @hide */ public static String windowStateToString(int state) { if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING"; if (state == WINDOW_STATE_HIDDEN) return "WINDOW_STATE_HIDDEN"; diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 6f8e335cff80..1035d8b93881 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -222,7 +222,8 @@ public final class UiAutomation { private OnAccessibilityEventListener mOnAccessibilityEventListener; - private boolean mWaitingForEventDelivery; + // Count the nested clients waiting for data delivery + private int mCurrentEventWatchersCount = 0; private long mLastEventTimeMillis; @@ -1132,73 +1133,70 @@ public final class UiAutomation { */ public AccessibilityEvent executeAndWaitForEvent(Runnable command, AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException { + int watchersDepth; + // Track events added after the index for this command, it is to support nested calls. + // This doesn't support concurrent calls correctly. + int eventQueueStartIndex; + final long executionStartTimeMillis; + // Acquire the lock and prepare for receiving events. synchronized (mLock) { throwIfNotConnectedLocked(); - mEventQueue.clear(); - // Prepare to wait for an event. - mWaitingForEventDelivery = true; + watchersDepth = ++mCurrentEventWatchersCount; + executionStartTimeMillis = SystemClock.uptimeMillis(); + eventQueueStartIndex = mEventQueue.size(); + } + if (DEBUG) { + Log.d(LOG_TAG, "executeAndWaitForEvent: watchersCount=" + watchersDepth + + ", eventQueueStartIndex=" + eventQueueStartIndex); } - // Note: We have to release the lock since calling out with this lock held - // can bite. We will correctly filter out events from other interactions, - // so starting to collect events before running the action is just fine. - - // We will ignore events from previous interactions. - final long executionStartTimeMillis = SystemClock.uptimeMillis(); - // Execute the command *without* the lock being held. - command.run(); - - List<AccessibilityEvent> receivedEvents = new ArrayList<>(); - - // Acquire the lock and wait for the event. try { - // Wait for the event. - final long startTimeMillis = SystemClock.uptimeMillis(); - while (true) { - List<AccessibilityEvent> localEvents = new ArrayList<>(); - synchronized (mLock) { - localEvents.addAll(mEventQueue); - mEventQueue.clear(); - } - // Drain the event queue - while (!localEvents.isEmpty()) { - AccessibilityEvent event = localEvents.remove(0); - // Ignore events from previous interactions. - if (event.getEventTime() < executionStartTimeMillis) { - continue; - } - if (filter.accept(event)) { - return event; - } - receivedEvents.add(event); - } - // Check if timed out and if not wait. - final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; - final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; - if (remainingTimeMillis <= 0) { - throw new TimeoutException("Expected event not received within: " - + timeoutMillis + " ms among: " + receivedEvents); + // Execute the command *without* the lock being held. + command.run(); + synchronized (mLock) { + if (watchersDepth != mCurrentEventWatchersCount) { + throw new IllegalStateException("Unexpected event watchers count, expected: " + + watchersDepth + ", actual: " + mCurrentEventWatchersCount); } + } + final long startTimeMillis = SystemClock.uptimeMillis(); + List<AccessibilityEvent> receivedEvents = new ArrayList<>(); + long elapsedTimeMillis = 0; + int currentQueueSize = 0; + while (timeoutMillis > elapsedTimeMillis) { + AccessibilityEvent event = null; synchronized (mLock) { - if (mEventQueue.isEmpty()) { + currentQueueSize = mEventQueue.size(); + if (eventQueueStartIndex < currentQueueSize) { + event = mEventQueue.get(eventQueueStartIndex++); + } else { try { - mLock.wait(remainingTimeMillis); + mLock.wait(timeoutMillis - elapsedTimeMillis); } catch (InterruptedException ie) { /* ignore */ } } } + elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + if (event == null || event.getEventTime() < executionStartTimeMillis) { + continue; + } + if (filter.accept(event)) { + return event; + } + receivedEvents.add(event); } - } finally { - int size = receivedEvents.size(); - for (int i = 0; i < size; i++) { - receivedEvents.get(i).recycle(); + if (eventQueueStartIndex < currentQueueSize) { + Log.w(LOG_TAG, "Timed out before reading all events from the queue"); } - + throw new TimeoutException("Expected event not received before timeout, events: " + + receivedEvents); + } finally { synchronized (mLock) { - mWaitingForEventDelivery = false; - mEventQueue.clear(); + if (--mCurrentEventWatchersCount == 0) { + mEventQueue.clear(); + } mLock.notifyAll(); } } @@ -1957,7 +1955,7 @@ public final class UiAutomation { // It is not guaranteed that the accessibility framework sends events by the // order of event timestamp. mLastEventTimeMillis = Math.max(mLastEventTimeMillis, event.getEventTime()); - if (mWaitingForEventDelivery) { + if (mCurrentEventWatchersCount > 0) { mEventQueue.add(AccessibilityEvent.obtain(event)); } mLock.notifyAll(); diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 914ca73f1ce4..a4a6a55fc9ab 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -310,3 +310,17 @@ flag { description: "removes sbnholder from NLS" bug: "362981561" } + +flag { + name: "nm_summarization" + namespace: "systemui" + description: "Allows the NAS to summarize notifications" + bug: "390417189" +} + +flag { + name: "nm_summarization_ui" + namespace: "systemui" + description: "Shows summarized notifications in the UI" + bug: "390217880" +} diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig index 18182b804627..232883cbfe00 100644 --- a/core/java/android/app/supervision/flags.aconfig +++ b/core/java/android/app/supervision/flags.aconfig @@ -48,3 +48,19 @@ flag { description: "Flag that enables the Supervision settings screen with top-level Android settings entry point" bug: "383404606" } + +flag { + name: "enable_app_approval" + is_exported: true + namespace: "supervision" + description: "Flag to enable the App Approval settings in Android settings UI" + bug: "390185393" +} + +flag { + name: "enable_supervision_pin_recovery_screen" + is_exported: true + namespace: "supervision" + description: "Flag that enables the Supervision pin recovery screen with Supervision settings entry point" + bug: "390500290" +} diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 8a3a3ad56a7b..582a1a9442ce 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -183,6 +183,12 @@ public class UserInfo implements Parcelable { * * <p>This is not necessarily the system user. For example, it will not be the system user on * devices for which {@link UserManager#isHeadlessSystemUserMode()} returns true. + * + * <p>NB: Features should ideally not limit functionality to the main user. Ideally, they + * should either work for all users or for all admin users. If a feature should only work for + * select users, its determination of which user should be done intelligently or be + * customizable. Not all devices support a main user, and the idea of singling out one user as + * special is contrary to overall multiuser goals. */ public static final int FLAG_MAIN = 0x00004000; diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS index 37604bc2eb65..1de8a242acfc 100644 --- a/core/java/android/hardware/usb/OWNERS +++ b/core/java/android/hardware/usb/OWNERS @@ -1,7 +1,7 @@ # Bug component: 175220 -anothermark@google.com +vmartensson@google.com +nkapron@google.com febinthattil@google.com -aprasath@google.com +shubhankarm@google.com badhri@google.com -kumarashishg@google.com
\ No newline at end of file diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index 019ba0045916..edc5cb7cdf6a 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -18,7 +18,7 @@ package android.inputmethodservice; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; @@ -245,7 +245,7 @@ final class NavigationBarController { // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary. final int hints = NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN | (mShouldShowImeSwitcherWhenImeIsShown - ? NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0); + ? NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN : 0); navigationBarView.setNavigationIconHints(hints); navigationBarView.prepareNavButtons(this); } @@ -518,7 +518,7 @@ final class NavigationBarController { // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary. final int hints = NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN | (mShouldShowImeSwitcherWhenImeIsShown - ? NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0); + ? NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN : 0); navigationBarView.setNavigationIconHints(hints); } } else { diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java index e7e46a9482c8..622d5d1b1c5a 100644 --- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java @@ -16,6 +16,8 @@ package android.inputmethodservice.navigationbar; +import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE; import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE; import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET; @@ -28,6 +30,7 @@ import android.annotation.DrawableRes; import android.annotation.FloatRange; import android.annotation.NonNull; import android.app.StatusBarManager; +import android.app.StatusBarManager.NavigationHint; import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; @@ -63,7 +66,8 @@ public final class NavigationBarView extends FrameLayout { private int mCurrentRotation = -1; int mDisabledFlags = 0; - int mNavigationIconHints = StatusBarManager.NAVIGATION_HINT_BACK_ALT; + @NavigationHint + private int mNavigationIconHints = 0; private final int mNavBarMode = NAV_BAR_MODE_GESTURAL; private KeyButtonDrawable mBackIcon; @@ -241,8 +245,7 @@ public final class NavigationBarView extends FrameLayout { } private void orientBackButton(KeyButtonDrawable drawable) { - final boolean useAltBack = - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + final boolean useAltBack = (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0; final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; float degrees = useAltBack ? (isRtl ? 90 : -90) : 0; if (drawable.getRotation() == degrees) { @@ -284,8 +287,10 @@ public final class NavigationBarView extends FrameLayout { * * @param hints bit flags defined in {@link StatusBarManager}. */ - public void setNavigationIconHints(int hints) { - if (hints == mNavigationIconHints) return; + public void setNavigationIconHints(@NavigationHint int hints) { + if (hints == mNavigationIconHints) { + return; + } final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; final boolean oldBackAlt = (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; @@ -312,9 +317,10 @@ public final class NavigationBarView extends FrameLayout { getImeSwitchButton().setImageDrawable(mImeSwitcherIcon); // Update IME button visibility, a11y and rotate button always overrides the appearance - final boolean imeSwitcherVisible = - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0; - getImeSwitchButton().setVisibility(imeSwitcherVisible ? View.VISIBLE : View.INVISIBLE); + final boolean isImeSwitcherButtonVisible = + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0; + getImeSwitchButton() + .setVisibility(isImeSwitcherButtonVisible ? View.VISIBLE : View.INVISIBLE); getBackButton().setVisibility(View.VISIBLE); getHomeHandle().setVisibility(View.INVISIBLE); diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index ee62dea7f9e5..6b1e918a3c47 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -149,6 +149,11 @@ public class Binder implements IBinder { private static volatile boolean sStackTrackingEnabled = false; /** + * The extension binder object + */ + private IBinder mExtension = null; + + /** * Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to * {@link TransactionTracker}. * @@ -1237,7 +1242,9 @@ public class Binder implements IBinder { /** @hide */ @Override - public final native @Nullable IBinder getExtension(); + public final @Nullable IBinder getExtension() { + return mExtension; + } /** * Set the binder extension. @@ -1245,7 +1252,12 @@ public class Binder implements IBinder { * * @hide */ - public final native void setExtension(@Nullable IBinder extension); + public final void setExtension(@Nullable IBinder extension) { + mExtension = extension; + setExtensionNative(extension); + } + + private final native void setExtensionNative(@Nullable IBinder extension); /** * Default implementation rewinds the parcels and calls onTransact. On diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 50b621c46778..1e663342522b 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -39,7 +39,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.ravenwood.RavenwoodEnvironment; import dalvik.annotation.optimization.NeverCompile; -import dalvik.annotation.optimization.NeverInline; import java.io.FileDescriptor; import java.lang.annotation.Retention; @@ -238,7 +237,6 @@ public final class MessageQueue { private final MatchDeliverableMessages mMatchDeliverableMessages = new MatchDeliverableMessages(); - @NeverInline private boolean isIdleConcurrent() { final long now = SystemClock.uptimeMillis(); @@ -269,7 +267,6 @@ public final class MessageQueue { return true; } - @NeverInline private boolean isIdleLegacy() { synchronized (this) { final long now = SystemClock.uptimeMillis(); @@ -292,14 +289,12 @@ public final class MessageQueue { } } - @NeverInline private void addIdleHandlerConcurrent(@NonNull IdleHandler handler) { synchronized (mIdleHandlersLock) { mIdleHandlers.add(handler); } } - @NeverInline private void addIdleHandlerLegacy(@NonNull IdleHandler handler) { synchronized (this) { mIdleHandlers.add(handler); @@ -326,15 +321,11 @@ public final class MessageQueue { addIdleHandlerLegacy(handler); } } - - @NeverInline private void removeIdleHandlerConcurrent(@NonNull IdleHandler handler) { synchronized (mIdleHandlersLock) { mIdleHandlers.remove(handler); } } - - @NeverInline private void removeIdleHandlerLegacy(@NonNull IdleHandler handler) { synchronized (this) { mIdleHandlers.remove(handler); @@ -358,14 +349,12 @@ public final class MessageQueue { } } - @NeverInline private boolean isPollingConcurrent() { // If the loop is quitting then it must not be idling. // We can assume mPtr != 0 when sQuitting is false. return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr); } - @NeverInline private boolean isPollingLegacy() { synchronized (this) { return isPollingLocked(); @@ -396,7 +385,6 @@ public final class MessageQueue { // We can assume mPtr != 0 when mQuitting is false. return !mQuitting && nativeIsPolling(mPtr); } - @NeverInline private void addOnFileDescriptorEventListenerConcurrent(@NonNull FileDescriptor fd, @OnFileDescriptorEventListener.Events int events, @NonNull OnFileDescriptorEventListener listener) { @@ -405,7 +393,6 @@ public final class MessageQueue { } } - @NeverInline private void addOnFileDescriptorEventListenerLegacy(@NonNull FileDescriptor fd, @OnFileDescriptorEventListener.Events int events, @NonNull OnFileDescriptorEventListener listener) { @@ -455,14 +442,12 @@ public final class MessageQueue { } } - @NeverInline private void removeOnFileDescriptorEventListenerConcurrent(@NonNull FileDescriptor fd) { synchronized (mFileDescriptorRecordsLock) { updateOnFileDescriptorEventListenerLocked(fd, 0, null); } } - @NeverInline private void removeOnFileDescriptorEventListenerLegacy(@NonNull FileDescriptor fd) { synchronized (this) { updateOnFileDescriptorEventListenerLocked(fd, 0, null); @@ -796,7 +781,6 @@ public final class MessageQueue { } } - @NeverInline private Message nextConcurrent() { final long ptr = mPtr; if (ptr == 0) { @@ -871,7 +855,6 @@ public final class MessageQueue { } } - @NeverInline private Message nextLegacy() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit @@ -1036,13 +1019,11 @@ public final class MessageQueue { } } - @NeverInline private int postSyncBarrierConcurrent() { return postSyncBarrier(SystemClock.uptimeMillis()); } - @NeverInline private int postSyncBarrierLegacy() { return postSyncBarrier(SystemClock.uptimeMillis()); } @@ -1162,7 +1143,6 @@ public final class MessageQueue { } } - @NeverInline private void removeSyncBarrierConcurrent(int token) { boolean removed; MessageNode first; @@ -1189,7 +1169,6 @@ public final class MessageQueue { } } - @NeverInline private void removeSyncBarrierLegacy(int token) { synchronized (this) { Message prev = null; @@ -1249,7 +1228,6 @@ public final class MessageQueue { } - @NeverInline private boolean enqueueMessageConcurrent(Message msg, long when) { if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); @@ -1258,7 +1236,6 @@ public final class MessageQueue { return enqueueMessageUnchecked(msg, when); } - @NeverInline private boolean enqueueMessageLegacy(Message msg, long when) { synchronized (this) { if (msg.isInUse()) { @@ -1495,13 +1472,11 @@ public final class MessageQueue { private final MatchHandlerWhatAndObject mMatchHandlerWhatAndObject = new MatchHandlerWhatAndObject(); - @NeverInline private boolean hasMessagesConcurrent(Handler h, int what, Object object) { return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, false); } - @NeverInline private boolean hasMessagesLegacy(Handler h, int what, Object object) { synchronized (this) { Message p = mMessages; @@ -1540,13 +1515,11 @@ public final class MessageQueue { private final MatchHandlerWhatAndObjectEquals mMatchHandlerWhatAndObjectEquals = new MatchHandlerWhatAndObjectEquals(); - @NeverInline private boolean hasEqualMessagesConcurrent(Handler h, int what, Object object) { return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, false); } - @NeverInline private boolean hasEqualMessagesLegacy(Handler h, int what, Object object) { synchronized (this) { Message p = mMessages; @@ -1585,13 +1558,11 @@ public final class MessageQueue { private final MatchHandlerRunnableAndObject mMatchHandlerRunnableAndObject = new MatchHandlerRunnableAndObject(); - @NeverInline private boolean hasMessagesConcurrent(Handler h, Runnable r, Object object) { return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, false); } - @NeverInline private boolean hasMessagesLegacy(Handler h, Runnable r, Object object) { synchronized (this) { Message p = mMessages; @@ -1626,12 +1597,10 @@ public final class MessageQueue { } private final MatchHandler mMatchHandler = new MatchHandler(); - @NeverInline private boolean hasMessagesConcurrent(Handler h) { return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false); } - @NeverInline private boolean hasMessagesLegacy(Handler h) { synchronized (this) { Message p = mMessages; @@ -1656,12 +1625,10 @@ public final class MessageQueue { } } - @NeverInline private void removeMessagesConcurrent(Handler h, int what, Object object) { findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true); } - @NeverInline private void removeMessagesLegacy(Handler h, int what, Object object) { synchronized (this) { Message p = mMessages; @@ -1716,12 +1683,10 @@ public final class MessageQueue { } } - @NeverInline private void removeEqualMessagesConcurrent(Handler h, int what, Object object) { findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true); } - @NeverInline private void removeEqualMessagesLegacy(Handler h, int what, Object object) { synchronized (this) { Message p = mMessages; @@ -1777,12 +1742,10 @@ public final class MessageQueue { } } - @NeverInline private void removeMessagesConcurrent(Handler h, Runnable r, Object object) { findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true); } - @NeverInline private void removeMessagesLegacy(Handler h, Runnable r, Object object) { synchronized (this) { Message p = mMessages; @@ -1852,12 +1815,10 @@ public final class MessageQueue { private final MatchHandlerRunnableAndObjectEquals mMatchHandlerRunnableAndObjectEquals = new MatchHandlerRunnableAndObjectEquals(); - @NeverInline private void removeEqualMessagesConcurrent(Handler h, Runnable r, Object object) { findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true); } - @NeverInline private void removeEqualMessagesLegacy(Handler h, Runnable r, Object object) { synchronized (this) { Message p = mMessages; @@ -1926,12 +1887,10 @@ public final class MessageQueue { } private final MatchHandlerAndObject mMatchHandlerAndObject = new MatchHandlerAndObject(); - @NeverInline private void removeCallbacksAndMessagesConcurrent(Handler h, Object object) { findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true); } - @NeverInline private void removeCallbacksAndMessagesLegacy(Handler h, Object object) { synchronized (this) { Message p = mMessages; @@ -2000,12 +1959,10 @@ public final class MessageQueue { private final MatchHandlerAndObjectEquals mMatchHandlerAndObjectEquals = new MatchHandlerAndObjectEquals(); - @NeverInline void removeCallbacksAndEqualMessagesConcurrent(Handler h, Object object) { findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true); } - @NeverInline void removeCallbacksAndEqualMessagesLegacy(Handler h, Object object) { synchronized (this) { Message p = mMessages; diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java index 976bfe41ba45..62d5015af914 100644 --- a/core/java/android/os/UidBatteryConsumer.java +++ b/core/java/android/os/UidBatteryConsumer.java @@ -147,7 +147,7 @@ public final class UidBatteryConsumer extends BatteryConsumer { for (int screenState = 0; screenState < SCREEN_STATE_COUNT; screenState++) { if (mData.layout.screenStateDataIncluded - && screenState == POWER_STATE_UNSPECIFIED) { + && screenState == SCREEN_STATE_UNSPECIFIED) { continue; } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 967f55ce7a88..6c21dbf126bb 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2910,10 +2910,18 @@ public class UserManager { * <p>Currently, on most form factors the first human user on the device will be the main user; * in the future, the concept may be transferable, so a different user (or even no user at all) * may be designated the main user instead. On other form factors there might not be a main + * user. In the future, the concept may be removed, i.e. typical future devices may have no main * user. * * <p>Note that this will not be the system user on devices for which * {@link #isHeadlessSystemUserMode()} returns true. + * + * <p>NB: Features should ideally not limit functionality to the main user. Ideally, they + * should either work for all users or for all admin users. If a feature should only work for + * select users, its determination of which user should be done intelligently or be + * customizable. Not all devices support a main user, and the idea of singling out one user as + * special is contrary to overall multiuser goals. + * * @hide */ @SystemApi @@ -2930,6 +2938,12 @@ public class UserManager { /** * Returns the designated "main user" of the device, or {@code null} if there is no main user. * + * <p>NB: Features should ideally not limit functionality to the main user. Ideally, they + * should either work for all users or for all admin users. If a feature should only work for + * select users, its determination of which user should be done intelligently or be + * customizable. Not all devices support a main user, and the idea of singling out one user as + * special is contrary to overall multiuser goals. + * * @see #isMainUser() * @hide */ diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java index 073f512a85fb..09ebc9f030c2 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Notification; import android.os.Build; import android.os.Bundle; @@ -223,6 +224,14 @@ public final class Adjustment implements Parcelable { public static final int TYPE_CONTENT_RECOMMENDATION = 4; /** + * Data type: String, the classification type of this notification. The OS may display + * notifications differently depending on the type, and may change the alerting level of the + * notification. + */ + @FlaggedApi(android.app.Flags.FLAG_NM_SUMMARIZATION) + public static final String KEY_SUMMARIZATION = "key_summarization"; + + /** * Create a notification adjustment. * * @param pkg The package of the notification. diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 72569075c2ed..f23006584621 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.annotation.UiThread; import android.app.ActivityManager; import android.app.INotificationManager; @@ -56,6 +57,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.widget.RemoteViews; @@ -1824,6 +1826,7 @@ public abstract class NotificationListenerService extends Service { private int mProposedImportance; // Sensitive info detected by the notification assistant private boolean mSensitiveContent; + private String mSummarization; private static final int PARCEL_VERSION = 2; @@ -1864,6 +1867,7 @@ public abstract class NotificationListenerService extends Service { out.writeBoolean(mIsBubble); out.writeInt(mProposedImportance); out.writeBoolean(mSensitiveContent); + out.writeString(mSummarization); } /** @hide */ @@ -1904,6 +1908,7 @@ public abstract class NotificationListenerService extends Service { mIsBubble = in.readBoolean(); mProposedImportance = in.readInt(); mSensitiveContent = in.readBoolean(); + mSummarization = in.readString(); } @@ -2180,6 +2185,16 @@ public abstract class NotificationListenerService extends Service { } /** + * Returns a summary of the content in the notification, or potentially of the current + * notification and related notifications (for example, if this is provided for a group + * summary notification it may be summarizing all the child notifications). + */ + @FlaggedApi(android.app.Flags.FLAG_NM_SUMMARIZATION) + public @Nullable String getSummarization() { + return mSummarization; + } + + /** * Returns the intended transition to ranking passed by {@link NotificationAssistantService} * @hide */ @@ -2201,7 +2216,7 @@ public abstract class NotificationListenerService extends Service { ArrayList<CharSequence> smartReplies, boolean canBubble, boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo, int rankingAdjustment, boolean isBubble, int proposedImportance, - boolean sensitiveContent) { + boolean sensitiveContent, String summarization) { mKey = key; mRank = rank; mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW; @@ -2229,6 +2244,7 @@ public abstract class NotificationListenerService extends Service { mIsBubble = isBubble; mProposedImportance = proposedImportance; mSensitiveContent = sensitiveContent; + mSummarization = TextUtils.nullIfEmpty(summarization); } /** @@ -2271,7 +2287,8 @@ public abstract class NotificationListenerService extends Service { other.mRankingAdjustment, other.mIsBubble, other.mProposedImportance, - other.mSensitiveContent); + other.mSensitiveContent, + other.mSummarization); } /** @@ -2332,7 +2349,8 @@ public abstract class NotificationListenerService extends Service { && Objects.equals(mRankingAdjustment, other.mRankingAdjustment) && Objects.equals(mIsBubble, other.mIsBubble) && Objects.equals(mProposedImportance, other.mProposedImportance) - && Objects.equals(mSensitiveContent, other.mSensitiveContent); + && Objects.equals(mSensitiveContent, other.mSensitiveContent) + && Objects.equals(mSummarization, other.mSummarization); } } diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index 105fa3ffd4cd..79957f411597 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -18,6 +18,8 @@ package android.service.notification; import static android.text.TextUtils.formatSimple; +import static com.android.window.flags.Flags.enablePerDisplayPackageContextCacheInStatusbarNotif; + import android.annotation.NonNull; import android.app.Notification; import android.app.NotificationManager; @@ -37,9 +39,9 @@ import android.util.ArrayMap; import com.android.internal.logging.InstanceId; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import static com.android.window.flags.Flags.enablePerDisplayPackageContextCacheInStatusbarNotif; import java.util.ArrayList; +import java.util.Collections; import java.util.Map; /** @@ -81,7 +83,8 @@ public class StatusBarNotification implements Parcelable { @Deprecated private Context mContext; // used for inflation & icon expansion // Maps display id to context used for remote view content inflation and status bar icon. - private final Map<Integer, Context> mContextForDisplayId = new ArrayMap<>(); + private final Map<Integer, Context> mContextForDisplayId = + Collections.synchronizedMap(new ArrayMap<>()); /** @hide */ public StatusBarNotification(String pkg, String opPkg, int id, diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index cb498503f201..a5d52957c40e 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -121,6 +121,7 @@ public class StaticLayout extends Layout { b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; b.mLineBreakConfig = LineBreakConfig.NONE; + b.mUseBoundsForWidth = false; b.mMinimumFontMetrics = null; return b; } diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java new file mode 100644 index 000000000000..0d1bb77ae8a2 --- /dev/null +++ b/core/java/android/window/DesktopExperienceFlags.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement; + +import android.annotation.Nullable; +import android.os.SystemProperties; +import android.util.Log; + +import com.android.window.flags.Flags; + +import java.util.function.BooleanSupplier; + +/** + * Checks Desktop Experience flag state. + * + * <p>This enum provides a centralized way to control the behavior of flags related to desktop + * experience features which are aiming for developer preview before their release. It allows + * developer option to override the default behavior of these flags. + * + * <p>The flags here will be controlled by the {@code + * persist.wm.debug.desktop_experience_devopts} system property. + * + * <p>NOTE: Flags should only be added to this enum when they have received Product and UX alignment + * that the feature is ready for developer preview, otherwise just do a flag check. + * + * @hide + */ +public enum DesktopExperienceFlags { + ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT(() -> enableDisplayContentModeManagement(), true); + + /** + * Flag class, to be used in case the enum cannot be used because the flag is not accessible. + * + * <p>This class will still use the process-wide cache. + */ + public static class DesktopExperienceFlag { + // Function called to obtain aconfig flag value. + private final BooleanSupplier mFlagFunction; + // Whether the flag state should be affected by developer option. + private final boolean mShouldOverrideByDevOption; + + public DesktopExperienceFlag(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { + this.mFlagFunction = flagFunction; + this.mShouldOverrideByDevOption = shouldOverrideByDevOption; + } + + /** + * Determines state of flag based on the actual flag and desktop experience developer option + * overrides. + */ + public boolean isTrue() { + return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); + } + } + + private static final String TAG = "DesktopExperienceFlags"; + // Function called to obtain aconfig flag value. + private final BooleanSupplier mFlagFunction; + // Whether the flag state should be affected by developer option. + private final boolean mShouldOverrideByDevOption; + + // Local cache for toggle override, which is initialized once on its first access. It needs to + // be refreshed only on reboots as overridden state is expected to take effect on reboots. + @Nullable private static Boolean sCachedToggleOverride; + + public static final String SYSTEM_PROPERTY_NAME = "persist.wm.debug.desktop_experience_devopts"; + + DesktopExperienceFlags(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { + this.mFlagFunction = flagFunction; + this.mShouldOverrideByDevOption = shouldOverrideByDevOption; + } + + /** + * Determines state of flag based on the actual flag and desktop experience developer option + * overrides. + */ + public boolean isTrue() { + return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); + } + + private static boolean isFlagTrue( + BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { + if (shouldOverrideByDevOption + && Flags.showDesktopExperienceDevOption() + && getToggleOverride()) { + return true; + } + return flagFunction.getAsBoolean(); + } + + private static boolean getToggleOverride() { + // If cached, return it + if (sCachedToggleOverride != null) { + return sCachedToggleOverride; + } + + // Otherwise, fetch and cache it + boolean override = getToggleOverrideFromSystem(); + sCachedToggleOverride = override; + Log.d(TAG, "Toggle override initialized to: " + override); + return override; + } + + /** Returns the {@link ToggleOverride} from the system property.. */ + private static boolean getToggleOverrideFromSystem() { + return SystemProperties.getBoolean(SYSTEM_PROPERTY_NAME, false); + } +} diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index d44b941082b5..e51ef4f6d04c 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -35,6 +35,10 @@ import java.util.function.BooleanSupplier; * windowing features which are aiming for developer preview before their release. It allows * developer option to override the default behavior of these flags. * + * <p> The flags here will be controlled by either {@link + * Settings.Global#DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES} or the {@code + * persyst.wm.debug.desktop_experience_devopts} system property. + * * <p>NOTE: Flags should only be added to this enum when they have received Product and UX * alignment that the feature is ready for developer preview, otherwise just do a flag check. * @@ -89,7 +93,8 @@ public enum DesktopModeFlags { ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX( Flags::enableDesktopAppLaunchTransitionsBugfix, false), INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC( - Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true); + Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true), + ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true); /** * Flag class, to be used in case the enum cannot be used because the flag is not accessible. @@ -109,7 +114,7 @@ public enum DesktopModeFlags { /** * Determines state of flag based on the actual flag and desktop mode developer option - * overrides. + * or desktop experience developer option overrides. */ public boolean isTrue() { return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); diff --git a/core/java/android/window/OWNERS b/core/java/android/window/OWNERS index 77c99b98cf4a..82d37244dc70 100644 --- a/core/java/android/window/OWNERS +++ b/core/java/android/window/OWNERS @@ -3,3 +3,4 @@ set noparent include /services/core/java/com/android/server/wm/OWNERS per-file DesktopModeFlags.java = file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS +per-file DesktopExperienceFlags.java = file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 73ebcdd8a07b..e35c3b80a58b 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -563,3 +563,23 @@ flag { description: "Replace the freeform windowing dev options with a desktop experience one." bug: "389092752" } + +flag { + name: "enable_quickswitch_desktop_split_bugfix" + namespace: "lse_desktop_experience" + description: "Enables splitting QuickSwitch between fullscreen apps and Desktop workspaces." + bug: "345296916" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_desktop_windowing_exit_by_minimize_transition_bugfix" + namespace: "lse_desktop_experience" + description: "Enables exit desktop windowing by minimize transition & motion polish changes" + bug: "390161102" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index ec3975205542..72cb9d1a20ac 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -51,6 +51,14 @@ oneway interface IStatusBar void showWirelessChargingAnimation(int batteryLevel); + /** + * Sets the new IME window status. + * + * @param displayId The id of the display to which the IME is bound. + * @param vis The IME window visibility. + * @param backDisposition The IME back disposition mode. + * @param showImeSwitcher Whether the IME Switcher button should be shown. + */ void setImeWindowStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher); void setWindowState(int display, int window, int state); diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index ec0954d5590a..1fa1e0bbc69a 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -61,6 +61,14 @@ interface IStatusBarService void setIconVisibility(String slot, boolean visible); @UnsupportedAppUsage void removeIcon(String slot); + /** + * Sets the new IME window status. + * + * @param displayId The id of the display to which the IME is bound. + * @param vis The IME window visibility. + * @param backDisposition The IME back disposition mode. + * @param showImeSwitcher Whether the IME Switcher button should be shown. + */ void setImeWindowStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher); void expandSettingsPanel(String subPanel); diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 639f5bff7614..91b25c2bda06 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -74,6 +74,7 @@ static struct bindernative_offsets_t jmethodID mExecTransact; jmethodID mGetInterfaceDescriptor; jmethodID mTransactionCallback; + jmethodID mGetExtension; // Object state. jfieldID mObject; @@ -489,8 +490,12 @@ public: if (mVintf) { ::android::internal::Stability::markVintf(b.get()); } - if (mExtension != nullptr) { - b.get()->setExtension(mExtension); + if (mSetExtensionCalled) { + jobject javaIBinderObject = env->CallObjectMethod(obj, gBinderOffsets.mGetExtension); + sp<IBinder> extensionFromJava = ibinderForJavaObject(env, javaIBinderObject); + if (extensionFromJava != nullptr) { + b.get()->setExtension(extensionFromJava); + } } mBinder = b; ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n", @@ -516,21 +521,12 @@ public: mVintf = false; } - sp<IBinder> getExtension() { - AutoMutex _l(mLock); - sp<JavaBBinder> b = mBinder.promote(); - if (b != nullptr) { - return b.get()->getExtension(); - } - return mExtension; - } - void setExtension(const sp<IBinder>& extension) { AutoMutex _l(mLock); - mExtension = extension; + mSetExtensionCalled = true; sp<JavaBBinder> b = mBinder.promote(); if (b != nullptr) { - b.get()->setExtension(mExtension); + b.get()->setExtension(extension); } } @@ -542,8 +538,7 @@ private: // is too much binder state here, we can think about making JavaBBinder an // sp here (avoid recreating it) bool mVintf = false; - - sp<IBinder> mExtension; + bool mSetExtensionCalled = false; }; // ---------------------------------------------------------------------------- @@ -1249,10 +1244,6 @@ static void android_os_Binder_blockUntilThreadAvailable(JNIEnv* env, jobject cla return IPCThreadState::self()->blockUntilThreadAvailable(); } -static jobject android_os_Binder_getExtension(JNIEnv* env, jobject obj) { - JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject); - return javaObjectForIBinder(env, jbh->getExtension()); -} static void android_os_Binder_setExtension(JNIEnv* env, jobject obj, jobject extensionObject) { JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject); @@ -1295,8 +1286,7 @@ static const JNINativeMethod gBinderMethods[] = { { "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder }, { "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer }, { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }, - { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension }, - { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension }, + { "setExtensionNative", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension }, }; // clang-format on @@ -1313,6 +1303,8 @@ static int int_register_android_os_Binder(JNIEnv* env) gBinderOffsets.mTransactionCallback = GetStaticMethodIDOrDie(env, clazz, "transactionCallback", "(IIII)V"); gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J"); + gBinderOffsets.mGetExtension = GetMethodIDOrDie(env, clazz, "getExtension", + "()Landroid/os/IBinder;"); return RegisterMethodsOrDie( env, kBinderPathName, diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b894d3a6888f..586cafdd2b57 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2009,6 +2009,10 @@ <!-- Component name of the built in wallpaper used to display bitmap wallpapers. This must not be null. --> <string name="image_wallpaper_component" translatable="false">com.android.systemui/com.android.systemui.wallpapers.ImageWallpaper</string> + <!-- Component name of the built in wallpaper that is used when the user-selected wallpaper is + incompatible with the display's resolution or aspect ratio. --> + <string name="fallback_wallpaper_component" translatable="false">com.android.systemui/com.android.systemui.wallpapers.GradientColorWallpaper</string> + <!-- True if WallpaperService is enabled --> <bool name="config_enableWallpaperService">true</bool> diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml index d421944917ea..d8e89318a134 100644 --- a/core/res/res/values/public-final.xml +++ b/core/res/res/values/public-final.xml @@ -3922,4 +3922,86 @@ <public type="color" name="system_error_900" id="0x010600d0" /> <public type="color" name="system_error_1000" id="0x010600d1" /> + <!-- =============================================================== + Resources added in version NEXT of the platform + + NOTE: After this version of the platform is forked, changes cannot be made to the root + branch's groups for that release. Only merge changes to the forked platform branch. + =============================================================== --> + <eat-comment/> + + <staging-public-group-final type="attr" first-id="0x01b70000"> + <public name="removed_" /> + <!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") --> + <public name="adServiceTypes" /> + <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") --> + <public name="languageSettingsActivity"/> + <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) --> + <public name="dreamCategory"/> + <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") + @hide @SystemApi --> + <public name="backgroundPermission"/> + <!-- @FlaggedApi(android.view.accessibility.supplemental_description) --> + <public name="supplementalDescription"/> + <!-- @FlaggedApi("android.security.enable_intent_matching_flags") --> + <public name="intentMatchingFlags"/> + <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) --> + <public name="layoutLabel"/> + <public name="removed_" /> + <public name="removed_" /> + <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) --> + <public name="pageSizeCompat" /> + <!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) --> + <public name="wantsRoleHolderPriority"/> + <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) --> + <public name="minSdkVersionFull"/> + <public name="removed_" /> + <public name="removed_" /> + <public name="removed_" /> + <public name="removed_" /> + </staging-public-group-final> + + <!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") --> + <public type="attr" name="adServiceTypes" id="0x010106a4" /> + <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") --> + <public type="attr" name="languageSettingsActivity" id="0x010106a5" /> + <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) --> + <public type="attr" name="dreamCategory" id="0x010106a6" /> + <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") + @hide @SystemApi --> + <public type="attr" name="backgroundPermission" id="0x010106a7" /> + <!-- @FlaggedApi(android.view.accessibility.supplemental_description) --> + <public type="attr" name="supplementalDescription" id="0x010106a8" /> + <!-- @FlaggedApi("android.security.enable_intent_matching_flags") --> + <public type="attr" name="intentMatchingFlags" id="0x010106a9" /> + <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) --> + <public type="attr" name="layoutLabel" id="0x010106aa" /> + <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) --> + <public type="attr" name="pageSizeCompat" id="0x010106ab" /> + <!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) --> + <public type="attr" name="wantsRoleHolderPriority" id="0x010106ac" /> + <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) --> + <public type="attr" name="minSdkVersionFull" id="0x010106ad" /> + + <staging-public-group-final type="string" first-id="0x01b40000"> + <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + @hide @SystemApi --> + <public name="config_systemDependencyInstaller" /> + <!-- @hide @SystemApi --> + <public name="removed_config_defaultReservedForTestingProfileGroupExclusivity" /> + <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED) + @hide @SystemApi --> + <public name="config_systemVendorIntelligence" /> + <public name="removed_" /> + <public name="removed_" /> + <public name="removed_" /> + </staging-public-group-final> + + <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + @hide @SystemApi --> + <public type="string" name="config_systemDependencyInstaller" id="0x0104004a" /> + <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED) + @hide @SystemApi --> + <public type="string" name="config_systemVendorIntelligence" id="0x0104004b" /> + </resources> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index a0c4c13a8702..2d411d0268b3 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -109,34 +109,13 @@ =============================================================== --> <eat-comment/> - <staging-public-group type="attr" first-id="0x01b70000"> + <staging-public-group type="attr" first-id="0x01b30000"> <!-- @FlaggedApi("android.content.pm.sdk_lib_independence") --> <public name="optional"/> - <!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") --> - <public name="adServiceTypes" /> - <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") --> - <public name="languageSettingsActivity"/> - <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) --> - <public name="dreamCategory"/> - <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") - @hide @SystemApi --> - <public name="backgroundPermission"/> - <!-- @FlaggedApi(android.view.accessibility.supplemental_description) --> - <public name="supplementalDescription"/> - <!-- @FlaggedApi("android.security.enable_intent_matching_flags") --> - <public name="intentMatchingFlags"/> - <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) --> - <public name="layoutLabel"/> <!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) --> <public name="alternateLauncherIcons"/> <!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) --> <public name="alternateLauncherLabels"/> - <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) --> - <public name="pageSizeCompat" /> - <!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) --> - <public name="wantsRoleHolderPriority"/> - <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) --> - <public name="minSdkVersionFull"/> <!-- @hide Only for device overlay to use this. --> <public name="pointerIconVectorFill"/> <!-- @hide Only for device overlay to use this. --> @@ -147,39 +126,27 @@ <public name="pointerIconVectorStrokeInverse"/> </staging-public-group> - <staging-public-group type="id" first-id="0x01b60000"> + <staging-public-group type="id" first-id="0x01b20000"> <!-- @FlaggedApi(android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS) --> <public name="remoteViewsMetricsId"/> </staging-public-group> - <staging-public-group type="style" first-id="0x01b50000"> + <staging-public-group type="style" first-id="0x01b10000"> </staging-public-group> - <staging-public-group type="string" first-id="0x01b40000"> - <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER) - @hide @SystemApi --> - <public name="config_systemDependencyInstaller" /> - <!-- @hide @SystemApi --> - <public name="removed_config_defaultReservedForTestingProfileGroupExclusivity" /> - <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED) - @hide @SystemApi --> - <public name="config_systemVendorIntelligence" /> - + <staging-public-group type="string" first-id="0x01b00000"> <!-- @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE) @hide @SystemApi --> <public name="config_defaultOnDeviceIntelligenceService" /> - <!-- @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE) @hide @SystemApi --> <public name="config_defaultOnDeviceSandboxedInferenceService" /> - <!-- @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE) @hide @SystemApi --> <public name="config_defaultOnDeviceIntelligenceDeviceConfigNamespace" /> - </staging-public-group> - <staging-public-group type="dimen" first-id="0x01b30000"> + <staging-public-group type="dimen" first-id="0x01af0000"> <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)--> <public name="config_motionStandardFastSpatialDamping"/> <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)--> @@ -216,7 +183,7 @@ <public name="config_shapeCornerRadiusXlarge"/> </staging-public-group> - <staging-public-group type="color" first-id="0x01b20000"> + <staging-public-group type="color" first-id="0x01ae0000"> <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_COLORS_10_2024)--> <public name="system_inverse_on_surface_light"/> <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_COLORS_10_2024)--> @@ -243,28 +210,28 @@ <public name="system_surface_tint_dark"/> </staging-public-group> - <staging-public-group type="array" first-id="0x01b10000"> + <staging-public-group type="array" first-id="0x01ad0000"> </staging-public-group> - <staging-public-group type="drawable" first-id="0x01b00000"> + <staging-public-group type="drawable" first-id="0x01ac0000"> </staging-public-group> - <staging-public-group type="layout" first-id="0x01af0000"> + <staging-public-group type="layout" first-id="0x01ab0000"> </staging-public-group> - <staging-public-group type="anim" first-id="0x01ae0000"> + <staging-public-group type="anim" first-id="0x01aa0000"> </staging-public-group> - <staging-public-group type="animator" first-id="0x01ad0000"> + <staging-public-group type="animator" first-id="0x01a90000"> </staging-public-group> - <staging-public-group type="interpolator" first-id="0x01ac0000"> + <staging-public-group type="interpolator" first-id="0x01a80000"> </staging-public-group> - <staging-public-group type="mipmap" first-id="0x01ab0000"> + <staging-public-group type="mipmap" first-id="0x01a70000"> </staging-public-group> - <staging-public-group type="integer" first-id="0x01aa0000"> + <staging-public-group type="integer" first-id="0x01a60000"> <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)--> <public name="config_motionStandardFastSpatialStiffness"/> <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)--> @@ -291,16 +258,16 @@ <public name="config_motionExpressiveSlowEffectStiffness"/> </staging-public-group> - <staging-public-group type="transition" first-id="0x01a90000"> + <staging-public-group type="transition" first-id="0x01a50000"> </staging-public-group> - <staging-public-group type="raw" first-id="0x01a80000"> + <staging-public-group type="raw" first-id="0x01a40000"> </staging-public-group> - <staging-public-group type="bool" first-id="0x01a70000"> + <staging-public-group type="bool" first-id="0x01a30000"> </staging-public-group> - <staging-public-group type="fraction" first-id="0x01a60000"> + <staging-public-group type="fraction" first-id="0x01a20000"> </staging-public-group> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8315e3cf1a11..772a7413a4a7 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2270,6 +2270,7 @@ <java-symbol type="string" name="heavy_weight_notification" /> <java-symbol type="string" name="heavy_weight_notification_detail" /> <java-symbol type="string" name="image_wallpaper_component" /> + <java-symbol type="string" name="fallback_wallpaper_component" /> <java-symbol type="string" name="input_method_binding_label" /> <java-symbol type="string" name="input_method_ime_switch_long_click_action_desc" /> <java-symbol type="string" name="launch_warning_original" /> diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java index 1bdb006c3465..9f1580cb8b57 100644 --- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java +++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java @@ -124,7 +124,8 @@ public class NotificationRankingUpdateTest { getRankingAdjustment(i), isBubble(i), getProposedImportance(i), - hasSensitiveContent(i) + hasSensitiveContent(i), + getSummarization(i) ); rankings[i] = ranking; } @@ -334,6 +335,17 @@ public class NotificationRankingUpdateTest { } /** + * Produces a String that can be used to represent getSummarization, based on the provided + * index. + */ + public static String getSummarization(int index) { + if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())) { + return "summary " + index; + } + return null; + } + + /** * Produces a boolean that can be used to represent isBubble, based on the provided index. */ public static boolean isBubble(int index) { @@ -461,7 +473,8 @@ public class NotificationRankingUpdateTest { /* rankingAdjustment= */ 0, /* isBubble= */ false, /* proposedImportance= */ 0, - /* sensitiveContent= */ false + /* sensitiveContent= */ false, + /* summarization = */ null ); return ranking; } @@ -550,7 +563,8 @@ public class NotificationRankingUpdateTest { tweak.getRankingAdjustment(), tweak.isBubble(), tweak.getProposedImportance(), - tweak.hasSensitiveContent() + tweak.hasSensitiveContent(), + tweak.getSummarization() ); assertNotEquals(nru, nru2); } diff --git a/core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java b/core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java new file mode 100644 index 000000000000..cc06f3d21332 --- /dev/null +++ b/core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assume.assumeTrue; +import static org.junit.Assume.assumeFalse; + +import android.content.Context; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.FlagsParameterization; +import android.platform.test.flag.junit.SetFlagsRule; +import android.support.test.uiautomator.UiDevice; +import android.window.DesktopExperienceFlags.DesktopExperienceFlag; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.window.flags.Flags; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * Test class for {@link android.window.DesktopExperienceFlags} + * + * <p>Build/Install/Run: atest FrameworksCoreTests:DesktopExperienceFlagsTest + */ +@SmallTest +@Presubmit +@RunWith(ParameterizedAndroidJunit4.class) +public class DesktopExperienceFlagsTest { + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf(FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION); + } + + @Rule public SetFlagsRule mSetFlagsRule; + + private UiDevice mUiDevice; + private Context mContext; + private boolean mLocalFlagValue = false; + private final DesktopExperienceFlag mOverriddenLocalFlag = + new DesktopExperienceFlag(() -> mLocalFlagValue, true); + private final DesktopExperienceFlag mNotOverriddenLocalFlag = + new DesktopExperienceFlag(() -> mLocalFlagValue, false); + + private static final String OVERRIDE_OFF_SETTING = "0"; + private static final String OVERRIDE_ON_SETTING = "1"; + private static final String OVERRIDE_INVALID_SETTING = "garbage"; + + public DesktopExperienceFlagsTest(FlagsParameterization flags) { + mSetFlagsRule = new SetFlagsRule(flags); + } + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + setSysProp(null); + } + + @After + public void tearDown() throws Exception { + resetCache(); + setSysProp(null); + } + + @Test + public void isTrue_overrideOff_featureFlagOn_returnsTrue() throws Exception { + mLocalFlagValue = true; + setSysProp(OVERRIDE_OFF_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); + } + + @Test + public void isTrue_overrideOn_featureFlagOn_returnsTrue() throws Exception { + mLocalFlagValue = true; + setSysProp(OVERRIDE_ON_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); + } + + @Test + public void isTrue_overrideOff_featureFlagOff_returnsFalse() throws Exception { + mLocalFlagValue = false; + setSysProp(OVERRIDE_OFF_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); + } + + @Test + public void isTrue_devOptionEnabled_overrideOn_featureFlagOff() throws Exception { + assumeTrue(Flags.showDesktopExperienceDevOption()); + mLocalFlagValue = false; + setSysProp(OVERRIDE_ON_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); + } + + @Test + public void isTrue_devOptionDisabled_overrideOn_featureFlagOff_returnsFalse() throws Exception { + assumeFalse(Flags.showDesktopExperienceDevOption()); + mLocalFlagValue = false; + setSysProp(OVERRIDE_ON_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); + } + + private void setSysProp(String value) throws Exception { + if (value == null) { + resetSysProp(); + } else { + mUiDevice.executeShellCommand( + "setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " " + value); + } + } + + private void resetSysProp() throws Exception { + mUiDevice.executeShellCommand("setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " ''"); + } + + private void resetCache() throws Exception { + Field cachedToggleOverride = + DesktopExperienceFlags.class.getDeclaredField("sCachedToggleOverride"); + cachedToggleOverride.setAccessible(true); + cachedToggleOverride.set(null, null); + } +} diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt index dd387b382dc6..09a93d501f8e 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt @@ -296,5 +296,9 @@ class BubbleControllerBubbleBarTest { override fun onBubbleStateChange(update: BubbleBarUpdate?) {} override fun animateBubbleBarLocation(location: BubbleBarLocation?) {} + + override fun onDragItemOverBubbleBarDragZone(location: BubbleBarLocation) {} + + override fun onItemDraggedOutsideBubbleBarDropZone() {} } } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java index 2ca011bfe000..0a1e3b9495a0 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java @@ -111,7 +111,7 @@ public class GroupedTaskInfo implements Parcelable { * Create new for a pair of tasks in split screen */ public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1, - @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) { + @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) { return new GroupedTaskInfo(List.of(task1, task2), splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */); } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java index 840de2c86f92..4d00c74155a8 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java @@ -127,6 +127,46 @@ public class TransitionUtil { } /** + * Check if all changes in this transition are only ordering changes. If so, we won't animate. + */ + public static boolean isAllOrderOnly(TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + if (!isOrderOnly(info.getChanges().get(i))) return false; + } + return true; + } + + /** + * Look through a transition and see if all non-closing changes are no-animation. If so, no + * animation should play. + */ + public static boolean isAllNoAnimation(TransitionInfo info) { + if (isClosingType(info.getType())) { + // no-animation is only relevant for launching (open) activities. + return false; + } + boolean hasNoAnimation = false; + final int changeSize = info.getChanges().size(); + for (int i = changeSize - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (isClosingType(change.getMode())) { + // ignore closing apps since they are a side-effect of the transition and don't + // animate. + continue; + } + if (change.hasFlags(TransitionInfo.FLAG_NO_ANIMATION)) { + hasNoAnimation = true; + } else if (!isOrderOnly(change) && !change.hasFlags(TransitionInfo.FLAG_IS_OCCLUDED)) { + // Ignore the order only or occluded changes since they shouldn't be visible during + // animation. For anything else, we need to animate if at-least one relevant + // participant *is* animated, + return false; + } + } + return hasNoAnimation; + } + + /** * Filter that selects leaf-tasks only. THIS IS ORDER-DEPENDENT! For it to work properly, you * MUST call `test` in the same order that the changes appear in the TransitionInfo. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index e8e25e20d8d8..e68a98fb7f21 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -28,6 +28,7 @@ import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; import android.app.Person; +import android.app.TaskInfo; import android.content.Context; import android.content.Intent; import android.content.LocusId; @@ -57,6 +58,7 @@ import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.bubbles.BubbleInfo; import com.android.wm.shell.shared.bubbles.ParcelableFlyoutMessage; +import com.android.wm.shell.taskview.TaskView; import java.io.PrintWriter; import java.util.List; @@ -204,6 +206,13 @@ public class Bubble implements BubbleViewProvider { private Intent mAppIntent; /** + * Set while preparing a transition for animation. Several steps are needed before animation + * starts, so this is used to detect and route associated events to the coordinating transition. + */ + @Nullable + private BubbleTransitions.BubbleTransition mPreparingTransition; + + /** * Create a bubble with limited information based on given {@link ShortcutInfo}. * Note: Currently this is only being used when the bubble is persisted to disk. */ @@ -280,6 +289,30 @@ public class Bubble implements BubbleViewProvider { mShortcutInfo = info; } + private Bubble( + TaskInfo task, + UserHandle user, + @Nullable Icon icon, + String key, + @ShellMainThread Executor mainExecutor, + @ShellBackgroundThread Executor bgExecutor) { + mGroupKey = null; + mLocusId = null; + mFlags = 0; + mUser = user; + mIcon = icon; + mIsAppBubble = true; + mKey = key; + mShowBubbleUpdateDot = false; + mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; + mTaskId = task.taskId; + mAppIntent = null; + mDesiredHeight = Integer.MAX_VALUE; + mPackageName = task.baseActivity.getPackageName(); + } + + /** Creates an app bubble. */ public static Bubble createAppBubble(Intent intent, UserHandle user, @Nullable Icon icon, @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { @@ -291,6 +324,16 @@ public class Bubble implements BubbleViewProvider { mainExecutor, bgExecutor); } + /** Creates a task bubble. */ + public static Bubble createTaskBubble(TaskInfo info, UserHandle user, @Nullable Icon icon, + @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { + return new Bubble(info, + user, + icon, + getAppBubbleKeyForTask(info), + mainExecutor, bgExecutor); + } + /** Creates a shortcut bubble. */ public static Bubble createShortcutBubble( ShortcutInfo info, @@ -316,6 +359,15 @@ public class Bubble implements BubbleViewProvider { return info.getPackage() + ":" + info.getUserId() + ":" + info.getId(); } + /** + * Returns the key for an app bubble from an app with package name, {@code packageName} on an + * Android user, {@code user}. + */ + public static String getAppBubbleKeyForTask(TaskInfo taskInfo) { + Objects.requireNonNull(taskInfo); + return KEY_APP_BUBBLE + ":" + taskInfo.taskId; + } + @VisibleForTesting(visibility = PRIVATE) public Bubble(@NonNull final BubbleEntry entry, final Bubbles.BubbleMetadataFlagListener listener, @@ -469,6 +521,10 @@ public class Bubble implements BubbleViewProvider { return mBubbleTaskView; } + public TaskView getTaskView() { + return mBubbleTaskView.getTaskView(); + } + /** * @return the ShortcutInfo id if it exists, or the metadata shortcut id otherwise. */ @@ -486,6 +542,10 @@ public class Bubble implements BubbleViewProvider { return (mMetadataShortcutId != null && !mMetadataShortcutId.isEmpty()); } + BubbleTransitions.BubbleTransition getPreparingTransition() { + return mPreparingTransition; + } + /** * Call this to clean up the task for the bubble. Ensure this is always called when done with * the bubble. @@ -556,6 +616,13 @@ public class Bubble implements BubbleViewProvider { } /** + * Sets the current bubble-transition that is coordinating a change in this bubble. + */ + void setPreparingTransition(BubbleTransitions.BubbleTransition transit) { + mPreparingTransition = transit; + } + + /** * Sets whether this bubble is considered text changed. This method is purely for * testing. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 4f9028e8aaf3..1f463295233f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -110,6 +110,7 @@ import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; import com.android.wm.shell.shared.bubbles.BubbleBarLocation; import com.android.wm.shell.shared.bubbles.BubbleBarUpdate; import com.android.wm.shell.sysui.ConfigurationChangeListener; @@ -287,6 +288,8 @@ public class BubbleController implements ConfigurationChangeListener, /** Used to send updates to the views from {@link #mBubbleDataListener}. */ private BubbleViewCallback mBubbleViewCallback; + private final BubbleTransitions mBubbleTransitions; + public BubbleController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, @@ -350,12 +353,16 @@ public class BubbleController implements ConfigurationChangeListener, context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.importance_ring_stroke_width)); mDisplayController = displayController; + final TaskViewTransitions tvTransitions; if (TaskViewTransitions.useRepo()) { - mTaskViewController = new TaskViewTransitions(transitions, taskViewRepository, - organizer, syncQueue); + tvTransitions = new TaskViewTransitions(transitions, taskViewRepository, organizer, + syncQueue); } else { - mTaskViewController = taskViewTransitions; + tvTransitions = taskViewTransitions; } + mTaskViewController = tvTransitions; + mBubbleTransitions = new BubbleTransitions(transitions, organizer, taskViewRepository, data, + tvTransitions, context); mTransitions = transitions; mOneHandedOptional = oneHandedOptional; mDragAndDropController = dragAndDropController; @@ -1456,7 +1463,19 @@ public class BubbleController implements ConfigurationChangeListener, * @param taskInfo the task. */ public void expandStackAndSelectBubble(ActivityManager.RunningTaskInfo taskInfo) { - // TODO(384976265): Not implemented yet + if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return; + Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow + ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", taskInfo.taskId); + if (b.isInflated()) { + mBubbleData.setSelectedBubbleAndExpandStack(b); + } else { + b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + // Lazy init stack view when a bubble is created + ensureBubbleViewsAndWindowCreated(); + mBubbleTransitions.startConvertToBubble(b, taskInfo, mExpandedViewManager, + mBubbleTaskViewFactory, mBubblePositioner, mLogger, mStackView, mLayerView, + mBubbleIconFactory, mInflateSynchronously); + } } /** @@ -2261,9 +2280,16 @@ public class BubbleController implements ConfigurationChangeListener, private void showExpandedViewForBubbleBar() { BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble(); - if (selectedBubble != null && mLayerView != null) { - mLayerView.showExpandedView(selectedBubble); + if (selectedBubble == null) return; + if (selectedBubble instanceof Bubble) { + final Bubble bubble = (Bubble) selectedBubble; + if (bubble.getPreparingTransition() != null) { + bubble.getPreparingTransition().continueExpand(); + return; + } } + if (mLayerView == null) return; + mLayerView.showExpandedView(selectedBubble); } private void collapseExpandedViewForBubbleBar() { @@ -2613,6 +2639,17 @@ public class BubbleController implements ConfigurationChangeListener, public void animateBubbleBarLocation(BubbleBarLocation location) { mListener.call(l -> l.animateBubbleBarLocation(location)); } + + @Override + public void onDragItemOverBubbleBarDragZone( + @NonNull BubbleBarLocation location) { + mListener.call(l -> l.onDragItemOverBubbleBarDragZone(location)); + } + + @Override + public void onItemDraggedOutsideBubbleBarDropZone() { + mListener.call(IBubblesListener::onItemDraggedOutsideBubbleBarDropZone); + } }; IBubblesImpl(BubbleController controller) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 74302094a296..96d0f6d5654e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -22,6 +22,7 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.annotation.NonNull; import android.app.PendingIntent; +import android.app.TaskInfo; import android.content.Context; import android.content.Intent; import android.content.LocusId; @@ -470,6 +471,17 @@ public class BubbleData { return bubbleToReturn; } + Bubble getOrCreateBubble(TaskInfo taskInfo) { + UserHandle user = UserHandle.of(mCurrentUserId); + String bubbleKey = Bubble.getAppBubbleKeyForTask(taskInfo); + Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey); + if (bubbleToReturn == null) { + bubbleToReturn = Bubble.createTaskBubble(taskInfo, user, null, mMainExecutor, + mBgExecutor); + } + return bubbleToReturn; + } + @Nullable private Bubble findAndRemoveBubbleFromOverflow(String key) { Bubble bubbleToReturn = getBubbleInStackWithKey(key); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index 89c038b4a26b..ae84f449c0e4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -109,7 +109,9 @@ public class BubbleTaskViewHelper { MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId() || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything())); - if (mBubble.isAppBubble()) { + if (mBubble.getPreparingTransition() != null) { + mBubble.getPreparingTransition().surfaceCreated(); + } else if (mBubble.isAppBubble()) { Context context = mContext.createContextAsUser( mBubble.getUser(), Context.CONTEXT_RESTRICTED); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java new file mode 100644 index 000000000000..e37844f53b11 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.view.WindowManager.TRANSIT_CHANGE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.TaskInfo; +import android.content.Context; +import android.graphics.Rect; +import android.os.IBinder; +import android.util.Slog; +import android.view.SurfaceControl; +import android.view.SurfaceView; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.launcher3.icons.BubbleIconFactory; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; +import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; +import com.android.wm.shell.taskview.TaskView; +import com.android.wm.shell.taskview.TaskViewRepository; +import com.android.wm.shell.taskview.TaskViewTaskController; +import com.android.wm.shell.taskview.TaskViewTransitions; +import com.android.wm.shell.transition.Transitions; + +import java.util.concurrent.Executor; + +/** + * Implements transition coordination for bubble operations. + */ +public class BubbleTransitions { + private static final String TAG = "BubbleTransitions"; + + @NonNull final Transitions mTransitions; + @NonNull final ShellTaskOrganizer mTaskOrganizer; + @NonNull final TaskViewRepository mRepository; + @NonNull final Executor mMainExecutor; + @NonNull final BubbleData mBubbleData; + @NonNull final TaskViewTransitions mTaskViewTransitions; + @NonNull final Context mContext; + + BubbleTransitions(@NonNull Transitions transitions, @NonNull ShellTaskOrganizer organizer, + @NonNull TaskViewRepository repository, @NonNull BubbleData bubbleData, + @NonNull TaskViewTransitions taskViewTransitions, Context context) { + mTransitions = transitions; + mTaskOrganizer = organizer; + mRepository = repository; + mMainExecutor = transitions.getMainExecutor(); + mBubbleData = bubbleData; + mTaskViewTransitions = taskViewTransitions; + mContext = context; + } + + /** + * Starts a convert-to-bubble transition. + * + * @see ConvertToBubble + */ + public BubbleTransition startConvertToBubble(Bubble bubble, TaskInfo taskInfo, + BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory, + BubblePositioner positioner, BubbleLogger logger, BubbleStackView stackView, + BubbleBarLayerView layerView, BubbleIconFactory iconFactory, + boolean inflateSync) { + ConvertToBubble convert = new ConvertToBubble(bubble, taskInfo, mContext, + expandedViewManager, factory, positioner, logger, stackView, layerView, iconFactory, + inflateSync); + return convert; + } + + /** + * Interface to a bubble-specific transition. Bubble transitions have a multi-step lifecycle + * in order to coordinate with the bubble view logic. These steps are communicated on this + * interface. + */ + interface BubbleTransition { + default void surfaceCreated() {} + default void continueExpand() {} + void skip(); + } + + /** + * BubbleTransition that coordinates the process of a non-bubble task becoming a bubble. The + * steps are as follows: + * + * 1. Start inflating the bubble view + * 2. Once inflated (but not-yet visible), tell WM to do the shell-transition. + * 3. Transition becomes ready, so notify Launcher + * 4. Launcher responds with showExpandedView which calls continueExpand() to make view visible + * 5. Surface is created which kicks off actual animation + * + * So, constructor -> onInflated -> startAnimation -> continueExpand -> surfaceCreated. + * + * continueExpand and surfaceCreated are set-up to happen in either order, though, to support + * UX/timing adjustments. + */ + @VisibleForTesting + class ConvertToBubble implements Transitions.TransitionHandler, BubbleTransition { + final BubbleBarLayerView mLayerView; + Bubble mBubble; + IBinder mTransition; + Transitions.TransitionFinishCallback mFinishCb; + WindowContainerTransaction mFinishWct = null; + final Rect mStartBounds = new Rect(); + SurfaceControl mSnapshot = null; + TaskInfo mTaskInfo; + boolean mFinishedExpand = false; + BubbleViewProvider mPriorBubble = null; + + private SurfaceControl.Transaction mFinishT; + private SurfaceControl mTaskLeash; + + ConvertToBubble(Bubble bubble, TaskInfo taskInfo, Context context, + BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory, + BubblePositioner positioner, BubbleLogger logger, BubbleStackView stackView, + BubbleBarLayerView layerView, BubbleIconFactory iconFactory, boolean inflateSync) { + mBubble = bubble; + mTaskInfo = taskInfo; + mLayerView = layerView; + mBubble.setInflateSynchronously(inflateSync); + mBubble.setPreparingTransition(this); + mBubble.inflate( + this::onInflated, + context, + expandedViewManager, + factory, + positioner, + logger, + stackView, + layerView, + iconFactory, + false /* skipInflation */); + } + + @VisibleForTesting + void onInflated(Bubble b) { + if (b != mBubble) { + throw new IllegalArgumentException("inflate callback doesn't match bubble"); + } + final Rect launchBounds = new Rect(); + mLayerView.getExpandedViewRestBounds(launchBounds); + WindowContainerTransaction wct = new WindowContainerTransaction(); + if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) { + if (mTaskInfo.getParentTaskId() != INVALID_TASK_ID) { + wct.reparent(mTaskInfo.token, null, true); + } + } + + wct.setAlwaysOnTop(mTaskInfo.token, true); + wct.setWindowingMode(mTaskInfo.token, WINDOWING_MODE_MULTI_WINDOW); + wct.setBounds(mTaskInfo.token, launchBounds); + + final TaskView tv = b.getTaskView(); + tv.setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT); + final TaskViewRepository.TaskViewState state = mRepository.byTaskView( + tv.getController()); + if (state != null) { + state.mVisible = true; + } + mTaskViewTransitions.enqueueExternal(tv.getController(), () -> { + mTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this); + return mTransition; + }); + } + + @Override + public void skip() { + mBubble.setPreparingTransition(null); + mFinishCb.onTransitionFinished(mFinishWct); + mFinishCb = null; + } + + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @Nullable TransitionRequestInfo request) { + return null; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + } + + @Override + public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, + @NonNull SurfaceControl.Transaction finishTransaction) { + if (!aborted) return; + mTransition = null; + mTaskViewTransitions.onExternalDone(transition); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mTransition != transition) return false; + boolean found = false; + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change chg = info.getChanges().get(i); + if (chg.getTaskInfo() == null) continue; + if (chg.getMode() != TRANSIT_CHANGE) continue; + if (!mTaskInfo.token.equals(chg.getTaskInfo().token)) continue; + mStartBounds.set(chg.getStartAbsBounds()); + // Converting a task into taskview, so treat as "new" + mFinishWct = new WindowContainerTransaction(); + mTaskInfo = chg.getTaskInfo(); + mFinishT = finishTransaction; + mTaskLeash = chg.getLeash(); + found = true; + mSnapshot = chg.getSnapshot(); + break; + } + if (!found) { + Slog.w(TAG, "Expected a TaskView conversion in this transition but didn't get " + + "one, cleaning up the task view"); + mBubble.getTaskView().getController().setTaskNotFound(); + mTaskViewTransitions.onExternalDone(transition); + return false; + } + mFinishCb = finishCallback; + + // Now update state (and talk to launcher) in parallel with snapshot stuff + mBubbleData.notificationEntryUpdated(mBubble, /* suppressFlyout= */ true, + /* showInShade= */ false); + + startTransaction.show(mSnapshot); + // Move snapshot to root so that it remains visible while task is moved to taskview + startTransaction.reparent(mSnapshot, info.getRoot(0).getLeash()); + startTransaction.setPosition(mSnapshot, + mStartBounds.left - info.getRoot(0).getOffset().x, + mStartBounds.top - info.getRoot(0).getOffset().y); + startTransaction.setLayer(mSnapshot, Integer.MAX_VALUE); + startTransaction.apply(); + + mTaskViewTransitions.onExternalDone(transition); + return true; + } + + @Override + public void continueExpand() { + mFinishedExpand = true; + final boolean animate = mLayerView.canExpandView(mBubble); + if (animate) { + mPriorBubble = mLayerView.prepareConvertedView(mBubble); + } + if (mPriorBubble != null) { + // TODO: an animation. For now though, just remove it. + final BubbleBarExpandedView priorView = mPriorBubble.getBubbleBarExpandedView(); + mLayerView.removeView(priorView); + mPriorBubble = null; + } + if (!animate || mBubble.getTaskView().getSurfaceControl() != null) { + playAnimation(animate); + } + } + + @Override + public void surfaceCreated() { + mMainExecutor.execute(() -> { + final TaskViewTaskController tvc = mBubble.getTaskView().getController(); + final TaskViewRepository.TaskViewState state = mRepository.byTaskView(tvc); + if (state == null) return; + state.mVisible = true; + if (mFinishedExpand) { + playAnimation(true /* animate */); + } + }); + } + + private void playAnimation(boolean animate) { + final TaskViewTaskController tv = mBubble.getTaskView().getController(); + final SurfaceControl.Transaction startT = new SurfaceControl.Transaction(); + mTaskViewTransitions.prepareOpenAnimation(tv, true /* new */, startT, mFinishT, + (ActivityManager.RunningTaskInfo) mTaskInfo, mTaskLeash, mFinishWct); + + if (mFinishWct.isEmpty()) { + mFinishWct = null; + } + + // Preparation is complete. + mBubble.setPreparingTransition(null); + + if (animate) { + mLayerView.animateConvert(startT, mStartBounds, mSnapshot, mTaskLeash, () -> { + mFinishCb.onTransitionFinished(mFinishWct); + mFinishCb = null; + }); + } else { + startT.apply(); + mFinishCb.onTransitionFinished(mFinishWct); + mFinishCb = null; + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 62895fe7c7cc..4297fac0f6a8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -36,6 +36,7 @@ import android.window.ScreenCapture.ScreenshotHardwareBuffer; import android.window.ScreenCapture.SynchronousScreenCaptureListener; import androidx.annotation.IntDef; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.shared.annotations.ExternalThread; @@ -330,6 +331,18 @@ public interface Bubbles { * Does not result in a state change. */ void animateBubbleBarLocation(BubbleBarLocation location); + + /** + * Called when an application icon is being dragged over the Bubble Bar drop zone. + * The location of the Bubble Bar is provided as an argument. + */ + void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation location); + + /** + * Called when an application icon is being dragged outside the Bubble Bar drop zone. + * Always called after {@link #onDragItemOverBubbleBarDragZone(BubbleBarLocation)} + */ + void onItemDraggedOutsideBubbleBarDropZone(); } /** Listener to find out about stack expansion / collapse events. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl index eb907dbb6597..9fc769f562a9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl @@ -33,4 +33,16 @@ oneway interface IBubblesListener { * Does not result in a state change. */ void animateBubbleBarLocation(in BubbleBarLocation location); + + /** + * Called when an application icon is being dragged over the Bubble Bar drop zone. + * The location of the Bubble Bar is provided as an argument. + */ + void onDragItemOverBubbleBarDragZone(in BubbleBarLocation location); + + /** + * Called when an application icon is being dragged outside the Bubble Bar drop zone. + * Always called after {@link #onDragItemOverBubbleBarDragZone(BubbleBarLocation)} + */ + void onItemDraggedOutsideBubbleBarDropZone(); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index de6d1f6c8852..52f20646fb4a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -36,17 +36,21 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.annotation.NonNull; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; import android.util.Log; import android.util.Size; +import android.view.SurfaceControl; import android.widget.FrameLayout; import androidx.annotation.Nullable; import com.android.app.animation.Interpolators; import com.android.wm.shell.R; +import com.android.wm.shell.animation.SizeChangeAnimation; +import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleViewProvider; @@ -571,6 +575,49 @@ public class BubbleBarAnimationHelper { } /** + * Animates converting of a non-bubble task into an expanded bubble view. + */ + public void animateConvert(BubbleViewProvider expandedBubble, + @NonNull SurfaceControl.Transaction startT, + @NonNull Rect origBounds, + @NonNull SurfaceControl snapshot, + @NonNull SurfaceControl taskLeash, + @Nullable Runnable afterAnimation) { + mExpandedBubble = expandedBubble; + final BubbleBarExpandedView bbev = getExpandedView(); + if (bbev == null) { + return; + } + + bbev.setTaskViewAlpha(1f); + SurfaceControl tvSf = ((Bubble) mExpandedBubble).getTaskView().getSurfaceControl(); + + final Size size = getExpandedViewSize(); + Point position = getExpandedViewRestPosition(size); + + final SizeChangeAnimation sca = + new SizeChangeAnimation( + new Rect(origBounds.left - position.x, origBounds.top - position.y, + origBounds.right - position.x, origBounds.bottom - position.y), + new Rect(0, 0, size.getWidth(), size.getHeight())); + sca.initialize(bbev, taskLeash, snapshot, startT); + + Animator a = sca.buildViewAnimator(bbev, tvSf, snapshot, /* onFinish */ (va) -> { + updateExpandedView(bbev); + snapshot.release(); + bbev.setSurfaceZOrderedOnTop(false); + bbev.setAnimating(false); + if (afterAnimation != null) { + afterAnimation.run(); + } + }); + + bbev.setSurfaceZOrderedOnTop(true); + a.setDuration(EXPANDED_VIEW_ANIMATE_TO_REST_DURATION); + a.start(); + } + + /** * Cancel current animations */ public void cancelAnimations() { @@ -627,6 +674,13 @@ public class BubbleBarAnimationHelper { bbev.maybeShowOverflow(); } + void getExpandedViewRestBounds(Rect out) { + final int width = mPositioner.getExpandedViewWidthForBubbleBar(false /* overflow */); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(false /* overflow */); + Point position = getExpandedViewRestPosition(new Size(width, height)); + out.set(position.x, position.y, position.x + width, position.y + height); + } + private Point getExpandedViewRestPosition(Size size) { final int padding = mPositioner.getBubbleBarExpandedViewPadding(); Point point = new Point(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index eaa0bd250fc4..91dcbdf5f117 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -28,6 +28,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.ColorDrawable; import android.view.Gravity; +import android.view.SurfaceControl; import android.view.TouchDelegate; import android.view.View; import android.view.ViewTreeObserver; @@ -174,14 +175,34 @@ public class BubbleBarLayerView extends FrameLayout /** Shows the expanded view of the provided bubble. */ public void showExpandedView(BubbleViewProvider b) { - BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView(); - if (expandedView == null) { - return; - } + if (!canExpandView(b)) return; + animateExpand(prepareExpandedView(b)); + } + + /** + * @return whether it's possible to expand {@param b} right now. This is {@code false} if + * the bubble has no view or if the bubble is already showing. + */ + public boolean canExpandView(BubbleViewProvider b) { + if (b.getBubbleBarExpandedView() == null) return false; if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) { - // Already showing this bubble, skip animating - return; + // Already showing this bubble so can't expand it. + return false; + } + return true; + } + + /** + * Prepares the expanded view of the provided bubble to be shown. This includes removing any + * stale content and cancelling any related animations. + * + * @return previous open bubble if there was one. + */ + private BubbleViewProvider prepareExpandedView(BubbleViewProvider b) { + if (!canExpandView(b)) { + throw new IllegalStateException("Can't prepare expand. Check canExpandView(b) first."); } + BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView(); BubbleViewProvider previousBubble = null; if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) { if (mIsExpanded && mExpandedBubble.getBubbleBarExpandedView() != null) { @@ -251,7 +272,20 @@ public class BubbleBarLayerView extends FrameLayout mIsExpanded = true; mBubbleController.getSysuiProxy().onStackExpandChanged(true); + showScrim(true); + return previousBubble; + } + /** + * Performs an animation to open a bubble with content that is not already visible. + * + * @param previousBubble If non-null, this is a bubble that is already showing before the new + * bubble is expanded. + */ + public void animateExpand(BubbleViewProvider previousBubble) { + if (!mIsExpanded || mExpandedBubble == null) { + throw new IllegalStateException("Can't animateExpand without expnaded state"); + } final Runnable afterAnimation = () -> { if (mExpandedView == null) return; // Touch delegate for the menu @@ -274,8 +308,49 @@ public class BubbleBarLayerView extends FrameLayout } else { mAnimationHelper.animateExpansion(mExpandedBubble, afterAnimation); } + } - showScrim(true); + /** + * Like {@link #prepareExpandedView} but also makes the current expanded bubble visible + * immediately so it gets a surface that can be animated. Since the surface may not be ready + * yet, this keeps the TaskView alpha=0. + */ + public BubbleViewProvider prepareConvertedView(BubbleViewProvider b) { + final BubbleViewProvider prior = prepareExpandedView(b); + + final BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView(); + if (bbev != null) { + updateExpandedView(); + bbev.setAnimating(true); + bbev.setContentVisibility(true); + bbev.setSurfaceZOrderedOnTop(true); + bbev.setTaskViewAlpha(0.f); + bbev.setVisibility(VISIBLE); + } + + return prior; + } + + /** + * Starts and animates a conversion-from transition. + * + * @param startT A transaction with first-frame work. this *will* be applied here! + */ + public void animateConvert(@NonNull SurfaceControl.Transaction startT, + @NonNull Rect startBounds, @NonNull SurfaceControl snapshot, SurfaceControl taskLeash, + Runnable animFinish) { + if (!mIsExpanded || mExpandedBubble == null) { + throw new IllegalStateException("Can't animateExpand without expanded state"); + } + mAnimationHelper.animateConvert(mExpandedBubble, startT, startBounds, snapshot, taskLeash, + animFinish); + } + + /** + * Populates {@param out} with the rest bounds of an expanded bubble. + */ + public void getExpandedViewRestBounds(Rect out) { + mAnimationHelper.getExpandedViewRestBounds(out); } /** Removes the given {@code bubble}. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 2a988c15d49a..f275f4c5f829 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -3010,6 +3010,14 @@ class DesktopTasksController( controller = null } + override fun createDesk(displayId: Int) { + // TODO: b/362720497 - Implement this API. + } + + override fun activateDesk(deskId: Int, remoteTransition: RemoteTransition?) { + // TODO: b/362720497 - Implement this API. + } + override fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition?) { executeRemoteCallWithTaskPermission(controller, "showDesktopApps") { c -> c.showDesktopApps(displayId, remoteTransition) @@ -3037,17 +3045,6 @@ class DesktopTasksController( ) } - override fun getVisibleTaskCount(displayId: Int): Int { - val result = IntArray(1) - executeRemoteCallWithTaskPermission( - controller, - "visibleTaskCount", - { controller -> result[0] = controller.visibleTaskCount(displayId) }, - /* blocking= */ true, - ) - return result[0] - } - override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) { executeRemoteCallWithTaskPermission(controller, "onDesktopSplitSelectAnimComplete") { c -> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index a135e4462150..44f7e16e98c3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -29,6 +29,11 @@ import com.android.wm.shell.desktopmode.IMoveToDesktopCallback; * Interface that is exposed to remote callers to manipulate desktop mode features. */ interface IDesktopMode { + /** If possible, creates a new desk on the display whose ID is `displayId`. */ + oneway void createDesk(int displayId); + + /** Activates the desk whose ID is `deskId` on whatever display it currently exists on. */ + oneway void activateDesk(int deskId, in RemoteTransition remoteTransition); /** Show apps on the desktop on the given display */ void showDesktopApps(int displayId, in RemoteTransition remoteTransition); @@ -48,9 +53,6 @@ interface IDesktopMode { oneway void showDesktopApp(int taskId, in @nullable RemoteTransition remoteTransition, in DesktopTaskToFrontReason toFrontReason); - /** Get count of visible desktop tasks on the given display */ - int getVisibleTaskCount(int displayId); - /** Perform cleanup transactions after the animation to split select is complete */ oneway void onDesktopSplitSelectAnimComplete(in RunningTaskInfo taskInfo); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 2d4d458292ea..4f2e028a1df0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -311,6 +311,9 @@ public class RecentTasksController implements TaskStackListenerCallback, public void onTaskAdded(RunningTaskInfo taskInfo) { notifyRunningTaskAppeared(taskInfo); + if (!enableShellTopTaskTracking()) { + notifyRecentTasksChanged(); + } } public void onTaskRemoved(RunningTaskInfo taskInfo) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index afc6fee2eca3..55133780f517 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -222,7 +222,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, RecentsMixedHandler mixer = null; Consumer<IBinder> setTransitionForMixer = null; for (int i = 0; i < mMixers.size(); ++i) { - setTransitionForMixer = mMixers.get(i).handleRecentsRequest(wct); + setTransitionForMixer = mMixers.get(i).handleRecentsRequest(); if (setTransitionForMixer != null) { mixer = mMixers.get(i); break; @@ -1455,6 +1455,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, } } + // Notify the mixers of the pending finish + for (int i = 0; i < mMixers.size(); ++i) { + mMixers.get(i).handleFinishRecents(returningToApp, wct, t); + } + if (Flags.enableRecentsBookendTransition()) { if (!wct.isEmpty()) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, @@ -1653,15 +1658,22 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, */ public interface RecentsMixedHandler extends Transitions.TransitionHandler { /** - * Called when a recents request comes in. The handler can add operations to outWCT. If - * the handler wants to "accept" the transition, it should return a Consumer accepting the - * IBinder for the transition. If not, it should return `null`. + * Called when a recents request comes in. If the handler wants to "accept" the transition, + * it should return a Consumer accepting the IBinder for the transition. If not, it should + * return `null`. * * If a mixed-handler accepts this recents, it will be the de-facto handler for this * transition and is required to call the associated {@link #startAnimation}, * {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods. */ @Nullable - Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT); + Consumer<IBinder> handleRecentsRequest(); + + /** + * Called when a recents transition has finished, with a WCT and SurfaceControl Transaction + * that can be used to add to any changes needed to restore the state. + */ + void handleFinishRecents(boolean returnToApp, @NonNull WindowContainerTransaction finishWct, + @NonNull SurfaceControl.Transaction finishT); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 511e426cc681..722494c05e32 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -129,6 +129,7 @@ import com.android.internal.logging.InstanceId; import com.android.internal.policy.FoldLockSettingsObserver; import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; @@ -3766,13 +3767,31 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mTaskOrganizer.applyTransaction(wct); } + public void onRecentsInSplitAnimationFinishing(boolean returnToApp, + @NonNull WindowContainerTransaction finishWct, + @NonNull SurfaceControl.Transaction finishT) { + if (!Flags.enableRecentsBookendTransition()) { + // The non-bookend recents transition case will be handled by + // RecentsMixedTransition wrapping the finish callback and calling + // onRecentsInSplitAnimationFinish() + return; + } + + onRecentsInSplitAnimationFinishInner(returnToApp, finishWct, finishT); + } + /** Call this when the recents animation during split-screen finishes. */ - public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct, - SurfaceControl.Transaction finishT) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationFinish"); - mPausingTasks.clear(); + public void onRecentsInSplitAnimationFinish(@NonNull WindowContainerTransaction finishWct, + @NonNull SurfaceControl.Transaction finishT) { + if (Flags.enableRecentsBookendTransition()) { + // The bookend recents transition case will be handled by + // onRecentsInSplitAnimationFinishing above + return; + } + // Check if the recent transition is finished by returning to the current // split, so we can restore the divider bar. + boolean returnToApp = false; for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) { final WindowContainerTransaction.HierarchyOp op = finishWct.getHierarchyOps().get(i); @@ -3787,13 +3806,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() && anyStageContainsContainer) { - updateSurfaceBounds(mSplitLayout, finishT, - false /* applyResizingOffset */); - finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash); - setDividerVisibility(true, finishT); - return; + returnToApp = true; } } + onRecentsInSplitAnimationFinishInner(returnToApp, finishWct, finishT); + } + + /** Call this when the recents animation during split-screen finishes. */ + public void onRecentsInSplitAnimationFinishInner(boolean returnToApp, + @NonNull WindowContainerTransaction finishWct, + @NonNull SurfaceControl.Transaction finishT) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationFinish: returnToApp=%b", + returnToApp); + mPausingTasks.clear(); + if (returnToApp) { + updateSurfaceBounds(mSplitLayout, finishT, + false /* applyResizingOffset */); + finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash); + setDividerVisibility(true, finishT); + return; + } setSplitsVisible(false); finishWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java index 0445add9cba9..13d87eda085b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java @@ -92,6 +92,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, getHolder().addCallback(this); } + public TaskViewTaskController getController() { + return mTaskViewTaskController; + } + /** * Launch a new activity. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java index d19a7eac6ad2..aef75e2dc99e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java @@ -448,7 +448,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { * have the pending info, we'll do it when we receive it in * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}. */ - void setTaskNotFound() { + public void setTaskNotFound() { mTaskNotFound = true; if (mPendingInfo != null) { cleanUpPendingTask(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java index 6c90a9060523..1eaae7ec83d9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -66,7 +67,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV static final String TAG = "TaskViewTransitions"; /** - * Map of {@link TaskViewTaskController} to {@link TaskViewRequestedState}. + * Map of {@link TaskViewTaskController} to {@link TaskViewRepository.TaskViewState}. * <p> * {@link TaskView} keeps a reference to the {@link TaskViewTaskController} instance and * manages its lifecycle. @@ -95,6 +96,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV final @WindowManager.TransitionType int mType; final WindowContainerTransaction mWct; final @NonNull TaskViewTaskController mTaskView; + ExternalTransition mExternalTransition; IBinder mClaimed; /** @@ -182,6 +184,32 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV } /** + * Starts or queues an "external" runnable into the pending queue. This means it will run + * in order relative to the local transitions. + * + * The external operation *must* call {@link #onExternalDone} once it has finished. + * + * In practice, the external is usually another transition on a different handler. + */ + public void enqueueExternal(@NonNull TaskViewTaskController taskView, ExternalTransition ext) { + final PendingTransition pending = new PendingTransition( + TRANSIT_NONE, null /* wct */, taskView, null /* cookie */); + pending.mExternalTransition = ext; + mPending.add(pending); + startNextTransition(); + } + + /** + * An external transition run in this "queue" is required to call this once it becomes ready. + */ + public void onExternalDone(IBinder key) { + final PendingTransition pending = findPending(key); + if (pending == null) return; + mPending.remove(pending); + startNextTransition(); + } + + /** * Looks through the pending transitions for a opening transaction that matches the provided * `taskView`. * @@ -191,6 +219,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV PendingTransition findPendingOpeningTransition(TaskViewTaskController taskView) { for (int i = mPending.size() - 1; i >= 0; --i) { if (mPending.get(i).mTaskView != taskView) continue; + if (mPending.get(i).mExternalTransition != null) continue; if (TransitionUtil.isOpeningType(mPending.get(i).mType)) { return mPending.get(i); } @@ -207,6 +236,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV PendingTransition findPending(TaskViewTaskController taskView, int type) { for (int i = mPending.size() - 1; i >= 0; --i) { if (mPending.get(i).mTaskView != taskView) continue; + if (mPending.get(i).mExternalTransition != null) continue; if (mPending.get(i).mType == type) { return mPending.get(i); } @@ -518,7 +548,11 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV // Wait for this to start animating. return; } - pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this); + if (pending.mExternalTransition != null) { + pending.mClaimed = pending.mExternalTransition.start(); + } else { + pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this); + } } @Override @@ -641,7 +675,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV } @VisibleForTesting - void prepareOpenAnimation(TaskViewTaskController taskView, + public void prepareOpenAnimation(TaskViewTaskController taskView, final boolean newTask, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, @@ -695,4 +729,10 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV taskView.notifyAppeared(newTask); } + + /** Interface for running an external transition in this object's pending queue. */ + public interface ExternalTransition { + /** Starts a transition and returns an identifying key for lookup. */ + IBinder start(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 2177986bccd5..d8e7c2ccb15f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -367,7 +367,7 @@ public class DefaultMixedHandler implements MixedTransitionHandler, } @Override - public Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT) { + public Consumer<IBinder> handleRecentsRequest() { if (mRecentsHandler != null) { if (mSplitHandler.isSplitScreenVisible()) { return this::setRecentsTransitionDuringSplit; @@ -383,6 +383,21 @@ public class DefaultMixedHandler implements MixedTransitionHandler, return null; } + @Override + public void handleFinishRecents(boolean returnToApp, + @NonNull WindowContainerTransaction finishWct, + @NonNull SurfaceControl.Transaction finishT) { + if (mRecentsHandler != null) { + for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { + final MixedTransition mixed = mActiveTransitions.get(i); + if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { + ((RecentsMixedTransition) mixed).onAnimateRecentsDuringSplitFinishing( + returnToApp, finishWct, finishT); + } + } + } + } + private void setRecentsTransitionDuringSplit(IBinder transition) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + "Split-Screen is foreground, so treat it as Mixed."); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java index b0547a2a47b1..29a58d7f75dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -407,8 +407,6 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { mLeftoversHandler.mergeAnimation( transition, info, t, mergeTarget, finishCallback); } - } else { - mPipHandler.end(); } return; case TYPE_KEYGUARD: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 36c3e9711f5c..ac6e4c5cd69e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -306,10 +306,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } // Early check if the transition doesn't warrant an animation. - if (Transitions.isAllNoAnimation(info) || Transitions.isAllOrderOnly(info) + if (TransitionUtil.isAllNoAnimation(info) || TransitionUtil.isAllOrderOnly(info) || (info.getFlags() & WindowManager.TRANSIT_FLAG_INVISIBLE) != 0) { startTransaction.apply(); - finishTransaction.apply(); + // As a contract, finishTransaction should only be applied in Transitions#onFinish finishCallback.onTransitionFinished(null /* wct */); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java index 30ffdac5cbba..357861cc3ca7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java @@ -161,13 +161,10 @@ public class MixedTransitionHelper { pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, finishCB); } - // make a new finishTransaction because pip's startEnterAnimation "consumes" it so - // we need a separate one to send over to launcher. - SurfaceControl.Transaction otherFinishT = new SurfaceControl.Transaction(); // Dispatch the rest of the transition normally. This will most-likely be taken by // recents or default handler. mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse, - otherStartT, otherFinishT, finishCB, mixedHandler); + otherStartT, finishTransaction, finishCB, mixedHandler); } else { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just " + "forward animation to Pip-Handler."); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java index 8cdbe26a2c76..1847af07f275 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java @@ -159,6 +159,8 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { // If pair-to-pair switching, the post-recents clean-up isn't needed. wct = wct != null ? wct : new WindowContainerTransaction(); if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) { + // TODO(b/346588978): Only called if !enableRecentsBookendTransition(), can remove + // once that rolls out mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction); } else { // notify pair-to-pair recents animation finish @@ -177,6 +179,17 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { return handled; } + /** + * Called when the recents animation during split is about to finish. + */ + void onAnimateRecentsDuringSplitFinishing(boolean returnToApp, + @NonNull WindowContainerTransaction finishWct, + @NonNull SurfaceControl.Transaction finishT) { + if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) { + mSplitHandler.onRecentsInSplitAnimationFinishing(returnToApp, finishWct, finishT); + } + } + @Override void mergeAnimation( @NonNull IBinder transition, @NonNull TransitionInfo info, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 8c9407b38d9e..b83b7e2f07a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -672,46 +672,6 @@ public class Transitions implements RemoteCallable<Transitions>, return -1; } - /** - * Look through a transition and see if all non-closing changes are no-animation. If so, no - * animation should play. - */ - static boolean isAllNoAnimation(TransitionInfo info) { - if (isClosingType(info.getType())) { - // no-animation is only relevant for launching (open) activities. - return false; - } - boolean hasNoAnimation = false; - final int changeSize = info.getChanges().size(); - for (int i = changeSize - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - if (isClosingType(change.getMode())) { - // ignore closing apps since they are a side-effect of the transition and don't - // animate. - continue; - } - if (change.hasFlags(FLAG_NO_ANIMATION)) { - hasNoAnimation = true; - } else if (!TransitionUtil.isOrderOnly(change) && !change.hasFlags(FLAG_IS_OCCLUDED)) { - // Ignore the order only or occluded changes since they shouldn't be visible during - // animation. For anything else, we need to animate if at-least one relevant - // participant *is* animated, - return false; - } - } - return hasNoAnimation; - } - - /** - * Check if all changes in this transition are only ordering changes. If so, we won't animate. - */ - static boolean isAllOrderOnly(TransitionInfo info) { - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - if (!TransitionUtil.isOrderOnly(info.getChanges().get(i))) return false; - } - return true; - } - private Track getOrCreateTrack(int trackId) { while (trackId >= mTracks.size()) { mTracks.add(new Track()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index fe9bdb027d18..dd7488e5f2f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -972,7 +972,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final RelayoutParams.OccludingCaptionElement controlsElement = new RelayoutParams.OccludingCaptionElement(); controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end; - if (Flags.enableMinimizeButton()) { + if (DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue()) { controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_with_minimize_button_margin_end; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt index 79c5eff01b4c..9f8ca7740182 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt @@ -47,7 +47,6 @@ import com.android.internal.R.color.materialColorSurfaceContainerHigh import com.android.internal.R.color.materialColorSurfaceContainerLow import com.android.internal.R.color.materialColorSurfaceDim import com.android.window.flags.Flags -import com.android.window.flags.Flags.enableMinimizeButton import com.android.wm.shell.R import android.window.DesktopModeFlags import com.android.wm.shell.windowdecor.MaximizeButtonView @@ -226,7 +225,7 @@ class AppHeaderViewHolder( minimizeWindowButton.background = getDrawable(1) } maximizeButtonView.setAnimationTints(isDarkMode()) - minimizeWindowButton.isGone = !enableMinimizeButton() + minimizeWindowButton.isGone = !DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue() } private fun bindDataWithThemedHeaders( @@ -276,7 +275,7 @@ class AppHeaderViewHolder( drawableInsets = minimizeDrawableInsets ) } - minimizeWindowButton.isGone = !enableMinimizeButton() + minimizeWindowButton.isGone = !DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue() // Maximize button. maximizeButtonView.apply { setAnimationTints( diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsLandscape.kt new file mode 100644 index 000000000000..6b159a4152ac --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.Rotation.ROTATION_90 +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CASCADE_APP +import com.android.wm.shell.scenarios.OpenAppFromAllApps +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromAllAppsLandscape : OpenAppFromAllApps(rotation = ROTATION_90) { + + @ExpectedScenarios(["CASCADE_APP"]) + @Test + override fun openApp() = super.openApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CASCADE_APP) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsPortrait.kt new file mode 100644 index 000000000000..07b439284680 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.Rotation.ROTATION_0 +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CASCADE_APP +import com.android.wm.shell.scenarios.OpenAppFromAllApps +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromAllAppsPortrait : OpenAppFromAllApps(rotation = ROTATION_0) { + + @ExpectedScenarios(["CASCADE_APP"]) + @Test + override fun openApp() = super.openApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CASCADE_APP) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarLandscape.kt new file mode 100644 index 000000000000..caadd3be0b9c --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.Rotation.ROTATION_90 +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CASCADE_APP +import com.android.wm.shell.scenarios.OpenAppFromTaskbar +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromTaskbarLandscape : OpenAppFromTaskbar(rotation = ROTATION_90) { + + @ExpectedScenarios(["CASCADE_APP"]) + @Test + override fun openApp() = super.openApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CASCADE_APP) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarPortrait.kt new file mode 100644 index 000000000000..77f5ab290e20 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.Rotation.ROTATION_0 +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CASCADE_APP +import com.android.wm.shell.scenarios.OpenAppFromTaskbar +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromTaskbarPortrait : OpenAppFromTaskbar(rotation = ROTATION_0) { + + @ExpectedScenarios(["CASCADE_APP"]) + @Test + override fun openApp() = super.openApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CASCADE_APP) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromAllApps.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromAllApps.kt index 36cdd5b26992..348219631245 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromAllApps.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromAllApps.kt @@ -20,12 +20,12 @@ import android.app.Instrumentation import android.tools.NavBar import android.tools.flicker.rules.ChangeDisplayOrientationRule import android.tools.Rotation +import android.tools.device.apphelpers.CalculatorAppHelper import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.helpers.DesktopModeAppHelper -import com.android.server.wm.flicker.helpers.MailAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.window.flags.Flags import com.android.wm.shell.Utils @@ -44,7 +44,7 @@ abstract class OpenAppFromAllApps(val rotation: Rotation = Rotation.ROTATION_0) private val wmHelper = WindowManagerStateHelper(instrumentation) private val device = UiDevice.getInstance(instrumentation) private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) - private val mailApp = MailAppHelper(instrumentation) + private val calculatorApp = CalculatorAppHelper(instrumentation) @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @@ -64,13 +64,13 @@ abstract class OpenAppFromAllApps(val rotation: Rotation = Rotation.ROTATION_0) open fun openApp() { tapl.launchedAppState.taskbar .openAllApps() - .getAppIcon(mailApp.appName) - .launch(mailApp.packageName) + .getAppIcon(calculatorApp.appName) + .launch(calculatorApp.packageName) } @After fun teardown() { - mailApp.exit(wmHelper) + calculatorApp.exit(wmHelper) testApp.exit(wmHelper) } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt index 31d89f92f744..9d501d32fbc7 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt @@ -23,8 +23,8 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.RecentTasksUtils import com.android.wm.shell.Utils -import com.android.wm.shell.flicker.utils.RecentTasksUtils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt index 1af6cac39085..f574f02ac3b3 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt @@ -23,8 +23,8 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.RecentTasksUtils import com.android.wm.shell.Utils -import com.android.wm.shell.flicker.utils.RecentTasksUtils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt index 8ad8c7bd7a7f..60fcce2fbf18 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt @@ -23,8 +23,8 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.RecentTasksUtils import com.android.wm.shell.Utils -import com.android.wm.shell.flicker.utils.RecentTasksUtils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt index da0ace472153..e6a080b2d258 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt @@ -23,8 +23,8 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.RecentTasksUtils import com.android.wm.shell.Utils -import com.android.wm.shell.flicker.utils.RecentTasksUtils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt index f9c60ad14fae..aa893ed65e7c 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt @@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice -import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder @@ -29,7 +28,6 @@ import android.tools.helpers.WindowUtils import android.tools.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.wm.shell.Flags import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.Assume import org.junit.FixMethodOrder @@ -64,7 +62,6 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) open class FromSplitScreenAutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : AutoEnterPipOnGoToHomeTest(flicker) { private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt index 805f4c2fe7f8..8e7cb56c0f07 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt @@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice -import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder @@ -30,7 +29,6 @@ import android.tools.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.EnterPipTransition import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.Assume @@ -66,7 +64,6 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) @FlakyTest(bugId = 386333280) open class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java new file mode 100644 index 000000000000..b4f514acf2dd --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles; + +import static android.view.WindowManager.TRANSIT_CHANGE; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.view.ViewRootImpl; +import android.window.IWindowContainerToken; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; + +import androidx.test.filters.SmallTest; + +import com.android.launcher3.icons.BubbleIconFactory; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestSyncExecutor; +import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; +import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.taskview.TaskView; +import com.android.wm.shell.taskview.TaskViewRepository; +import com.android.wm.shell.taskview.TaskViewTaskController; +import com.android.wm.shell.taskview.TaskViewTransitions; +import com.android.wm.shell.transition.Transitions; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests of {@link BubbleTransitions}. + */ +@SmallTest +public class BubbleTransitionsTest extends ShellTestCase { + @Mock + private BubbleData mBubbleData; + @Mock + private Bubble mBubble; + @Mock + private Transitions mTransitions; + @Mock + private SyncTransactionQueue mSyncQueue; + @Mock + private BubbleExpandedViewManager mExpandedViewManager; + @Mock + private BubblePositioner mBubblePositioner; + @Mock + private BubbleLogger mBubbleLogger; + @Mock + private BubbleStackView mStackView; + @Mock + private BubbleBarLayerView mLayerView; + @Mock + private BubbleIconFactory mIconFactory; + + @Mock private ShellTaskOrganizer mTaskOrganizer; + private TaskViewTransitions mTaskViewTransitions; + private TaskViewRepository mRepository; + private BubbleTransitions mBubbleTransitions; + private BubbleTaskViewFactory mTaskViewFactory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mRepository = new TaskViewRepository(); + ShellExecutor syncExecutor = new TestSyncExecutor(); + + when(mTransitions.getMainExecutor()).thenReturn(syncExecutor); + when(mTransitions.isRegistered()).thenReturn(true); + mTaskViewTransitions = new TaskViewTransitions(mTransitions, mRepository, mTaskOrganizer, + mSyncQueue); + mBubbleTransitions = new BubbleTransitions(mTransitions, mTaskOrganizer, mRepository, + mBubbleData, mTaskViewTransitions, mContext); + mTaskViewFactory = () -> { + TaskViewTaskController taskViewTaskController = new TaskViewTaskController( + mContext, mTaskOrganizer, mTaskViewTransitions, mSyncQueue); + TaskView taskView = new TaskView(mContext, mTaskViewTransitions, + taskViewTaskController); + return new BubbleTaskView(taskView, syncExecutor); + }; + final BubbleBarExpandedView bbev = mock(BubbleBarExpandedView.class); + final ViewRootImpl vri = mock(ViewRootImpl.class); + when(bbev.getViewRootImpl()).thenReturn(vri); + when(mBubble.getBubbleBarExpandedView()).thenReturn(bbev); + } + + private ActivityManager.RunningTaskInfo setupBubble() { + ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + final IWindowContainerToken itoken = mock(IWindowContainerToken.class); + final IBinder asBinder = mock(IBinder.class); + when(itoken.asBinder()).thenReturn(asBinder); + WindowContainerToken token = new WindowContainerToken(itoken); + taskInfo.token = token; + final TaskView tv = mock(TaskView.class); + final TaskViewTaskController tvtc = mock(TaskViewTaskController.class); + when(tvtc.getTaskInfo()).thenReturn(taskInfo); + when(tv.getController()).thenReturn(tvtc); + when(mBubble.getTaskView()).thenReturn(tv); + mRepository.add(tvtc); + return taskInfo; + } + + @Test + public void testConvertToBubble() { + // Basic walk-through of convert-to-bubble transition stages + ActivityManager.RunningTaskInfo taskInfo = setupBubble(); + final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertToBubble( + mBubble, taskInfo, mExpandedViewManager, mTaskViewFactory, mBubblePositioner, + mBubbleLogger, mStackView, mLayerView, mIconFactory, false); + final BubbleTransitions.ConvertToBubble ctb = (BubbleTransitions.ConvertToBubble) bt; + ctb.onInflated(mBubble); + when(mLayerView.canExpandView(any())).thenReturn(true); + verify(mTransitions).startTransition(anyInt(), any(), eq(ctb)); + verify(mBubble).setPreparingTransition(eq(bt)); + // Ensure we are communicating with the taskviewtransitions queue + assertTrue(mTaskViewTransitions.hasPending()); + + final TransitionInfo info = new TransitionInfo(TRANSIT_CHANGE, 0); + final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token, + mock(SurfaceControl.class)); + chg.setTaskInfo(taskInfo); + chg.setMode(TRANSIT_CHANGE); + info.addChange(chg); + info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0)); + SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + final boolean[] finishCalled = new boolean[]{false}; + Transitions.TransitionFinishCallback finishCb = wct -> { + assertFalse(finishCalled[0]); + finishCalled[0] = true; + }; + ctb.startAnimation(ctb.mTransition, info, startT, finishT, finishCb); + assertFalse(mTaskViewTransitions.hasPending()); + + verify(mBubbleData).notificationEntryUpdated(eq(mBubble), anyBoolean(), anyBoolean()); + ctb.continueExpand(); + + clearInvocations(mBubble); + verify(mBubble, never()).setPreparingTransition(any()); + + ctb.surfaceCreated(); + verify(mBubble).setPreparingTransition(isNull()); + ArgumentCaptor<Runnable> animCb = ArgumentCaptor.forClass(Runnable.class); + verify(mLayerView).animateConvert(any(), any(), any(), any(), animCb.capture()); + assertFalse(finishCalled[0]); + animCb.getValue().run(); + assertTrue(finishCalled[0]); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 065fa219e8d0..542289db6cfc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -211,6 +211,7 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testAddRemoveSplitNotifyChange() { + reset(mRecentTasksController); RecentTaskInfo t1 = makeTaskInfo(1); RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); @@ -225,6 +226,7 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testAddSameSplitBoundsInfoSkipNotifyChange() { + reset(mRecentTasksController); RecentTaskInfo t1 = makeTaskInfo(1); RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); @@ -535,6 +537,7 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testTaskWindowingModeChangedNotifiesChange() { + reset(mRecentTasksController); RecentTaskInfo t1 = makeTaskInfo(1); setRawList(t1); @@ -551,7 +554,8 @@ public class RecentTasksControllerTest extends ShellTestCase { WINDOWING_MODE_MULTI_WINDOW); mShellTaskOrganizer.onTaskInfoChanged(rt2MultiWIndow); - verify(mRecentTasksController).notifyRecentTasksChanged(); + // One for onTaskAppeared and one for onTaskInfoChanged + verify(mRecentTasksController, times(2)).notifyRecentTasksChanged(); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 4211e4682810..b9d6a454694d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -67,6 +67,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.Flags; import com.android.wm.shell.MockToken; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; @@ -355,8 +356,13 @@ public class SplitTransitionTests extends ShellTestCase { // Make sure it cleans-up if recents doesn't restore WindowContainerTransaction commitWCT = new WindowContainerTransaction(); - mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT, - mock(SurfaceControl.Transaction.class)); + if (Flags.enableRecentsBookendTransition()) { + mStageCoordinator.onRecentsInSplitAnimationFinishing(false /* returnToApp */, commitWCT, + mock(SurfaceControl.Transaction.class)); + } else { + mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT, + mock(SurfaceControl.Transaction.class)); + } assertFalse(mStageCoordinator.isSplitScreenVisible()); } @@ -420,8 +426,13 @@ public class SplitTransitionTests extends ShellTestCase { // simulate the restoreWCT being applied: mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class)); mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class)); - mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT, - mock(SurfaceControl.Transaction.class)); + if (Flags.enableRecentsBookendTransition()) { + mStageCoordinator.onRecentsInSplitAnimationFinishing(true /* returnToApp */, restoreWCT, + mock(SurfaceControl.Transaction.class)); + } else { + mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT, + mock(SurfaceControl.Transaction.class)); + } assertTrue(mStageCoordinator.isSplitScreenVisible()); } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 677fd86aca9c..53d3b77f1ba2 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -206,6 +206,9 @@ java_sdk_library { visibility: [ "//frameworks/base", // Framework ], + impl_library_visibility: [ + "//frameworks/base/ravenwood", + ], srcs: [ ":framework-graphics-srcs", diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 52eae43f7db9..aa3dbda374be 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -21,6 +21,7 @@ import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.media.audio.Flags.automaticBtDeviceType; +import static android.media.audio.Flags.cacheGetStreamMinMaxVolume; import static android.media.audio.Flags.FLAG_DEPRECATE_STREAM_BT_SCO; import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING; import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API; @@ -58,7 +59,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.media.AudioAttributes.AttributeSystemUsage; -import android.media.AudioDeviceInfo; import android.media.CallbackUtil.ListenerInfo; import android.media.audiopolicy.AudioPolicy; import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener; @@ -75,6 +75,7 @@ import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBinder; +import android.os.IpcDataCache; import android.os.Looper; import android.os.Message; import android.os.RemoteException; @@ -1231,6 +1232,84 @@ public class AudioManager { } /** + * API string for caching the min volume for each stream + * @hide + **/ + public static final String VOLUME_MIN_CACHING_API = "getStreamMinVolume"; + /** + * API string for caching the max volume for each stream + * @hide + **/ + public static final String VOLUME_MAX_CACHING_API = "getStreamMaxVolume"; + private static final int VOLUME_MIN_MAX_CACHING_SIZE = 16; + + private final IpcDataCache.QueryHandler<VolumeCacheQuery, Integer> mVolQuery = + new IpcDataCache.QueryHandler<>() { + @Override + public Integer apply(VolumeCacheQuery query) { + final IAudioService service = getService(); + try { + return switch (query.queryCommand) { + case QUERY_VOL_MIN -> service.getStreamMinVolume(query.stream); + case QUERY_VOL_MAX -> service.getStreamMaxVolume(query.stream); + default -> { + Log.w(TAG, "Not a valid volume cache query: " + query); + yield null; + } + }; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + }; + + private final IpcDataCache<VolumeCacheQuery, Integer> mVolMinCache = + new IpcDataCache<>(VOLUME_MIN_MAX_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, + VOLUME_MIN_CACHING_API, VOLUME_MIN_CACHING_API, mVolQuery); + + private final IpcDataCache<VolumeCacheQuery, Integer> mVolMaxCache = + new IpcDataCache<>(VOLUME_MIN_MAX_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, + VOLUME_MAX_CACHING_API, VOLUME_MAX_CACHING_API, mVolQuery); + + /** + * Used to invalidate the cache for the given API + * @hide + **/ + public static void clearVolumeCache(String api) { + if (cacheGetStreamMinMaxVolume()) { + IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, api); + } + } + + private static final int QUERY_VOL_MIN = 1; + private static final int QUERY_VOL_MAX = 2; + /** @hide */ + @IntDef(prefix = "QUERY_VOL", value = { + QUERY_VOL_MIN, + QUERY_VOL_MAX} + ) + @Retention(RetentionPolicy.SOURCE) + private @interface QueryVolCommand {} + + private record VolumeCacheQuery(int stream, @QueryVolCommand int queryCommand) { + private String queryVolCommandToString() { + return switch (queryCommand) { + case QUERY_VOL_MIN -> "getStreamMinVolume"; + case QUERY_VOL_MAX -> "getStreamMaxVolume"; + default -> "invalid command"; + }; + } + + @NonNull + @Override + public String toString() { + return TextUtils.formatSimple("VolumeCacheQuery(stream=%d, queryCommand=%s)", stream, + queryVolCommandToString()); + } + } + + /** * Returns the maximum volume index for a particular stream. * * @param streamType The stream type whose maximum volume index is returned. @@ -1238,6 +1317,9 @@ public class AudioManager { * @see #getStreamVolume(int) */ public int getStreamMaxVolume(int streamType) { + if (cacheGetStreamMinMaxVolume()) { + return mVolMaxCache.query(new VolumeCacheQuery(streamType, QUERY_VOL_MAX)); + } final IAudioService service = getService(); try { return service.getStreamMaxVolume(streamType); @@ -1271,6 +1353,9 @@ public class AudioManager { */ @TestApi public int getStreamMinVolumeInt(int streamType) { + if (cacheGetStreamMinMaxVolume()) { + return mVolMinCache.query(new VolumeCacheQuery(streamType, QUERY_VOL_MIN)); + } final IAudioService service = getService(); try { return service.getStreamMinVolume(streamType); diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index c9625c405faa..c4886836f451 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -20,6 +20,8 @@ import static android.media.codec.Flags.FLAG_CODEC_AVAILABILITY; import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE; import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST; import static android.media.codec.Flags.FLAG_SUBSESSION_METRICS; +import static android.media.tv.flags.Flags.applyPictureProfiles; +import static android.media.tv.flags.Flags.mediaQualityFw; import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME; @@ -37,6 +39,8 @@ import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.hardware.HardwareBuffer; import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.quality.PictureProfile; +import android.media.quality.PictureProfileHandle; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -5370,6 +5374,9 @@ final public class MediaCodec { * @param params The bundle of parameters to set. * @throws IllegalStateException if in the Released state. */ + + private static final String PARAMETER_KEY_PICTURE_PROFILE_HANDLE = "picture-profile-handle"; + public final void setParameters(@Nullable Bundle params) { if (params == null) { return; @@ -5383,19 +5390,41 @@ final public class MediaCodec { if (key.equals(MediaFormat.KEY_AUDIO_SESSION_ID)) { int sessionId = 0; try { - sessionId = (Integer)params.get(key); + sessionId = (Integer) params.get(key); } catch (Exception e) { throw new IllegalArgumentException("Wrong Session ID Parameter!"); } keys[i] = "audio-hw-sync"; values[i] = AudioSystem.getAudioHwSyncForSession(sessionId); + } else if (applyPictureProfiles() && mediaQualityFw() + && key.equals(MediaFormat.KEY_PICTURE_PROFILE_INSTANCE)) { + PictureProfile pictureProfile = null; + try { + pictureProfile = (PictureProfile) params.get(key); + } catch (ClassCastException e) { + throw new IllegalArgumentException( + "Cannot cast the instance parameter to PictureProfile!"); + } catch (Exception e) { + android.util.Log.getStackTraceString(e); + throw new IllegalArgumentException("Unexpected exception when casting the " + + "instance parameter to PictureProfile!"); + } + if (pictureProfile == null) { + throw new IllegalArgumentException( + "Picture profile instance parameter is null!"); + } + PictureProfileHandle handle = pictureProfile.getHandle(); + if (handle != PictureProfileHandle.NONE) { + keys[i] = PARAMETER_KEY_PICTURE_PROFILE_HANDLE; + values[i] = Long.valueOf(handle.getId()); + } } else { keys[i] = key; Object value = params.get(key); // Bundle's byte array is a byte[], JNI layer only takes ByteBuffer if (value instanceof byte[]) { - values[i] = ByteBuffer.wrap((byte[])value); + values[i] = ByteBuffer.wrap((byte[]) value); } else { values[i] = value; } diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java index d1f63404dbff..8ae72de3214a 100644 --- a/media/java/android/media/quality/MediaQualityContract.java +++ b/media/java/android/media/quality/MediaQualityContract.java @@ -385,6 +385,339 @@ public class MediaQualityContract { public static final String PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED = "auto_super_resolution_enabled"; + + /** + * @hide + * + */ + public static final String PARAMETER_LEVEL_RANGE = "level_range"; + + /** + * @hide + * + */ + public static final String PARAMETER_GAMUT_MAPPING = "gamut_mapping"; + + /** + * @hide + * + */ + public static final String PARAMETER_PC_MODE = "pc_mode"; + + /** + * @hide + * + */ + public static final String PARAMETER_LOW_LATENCY = "low_latency"; + + /** + * @hide + * + */ + public static final String PARAMETER_VRR = "vrr"; + + /** + * @hide + * + */ + public static final String PARAMETER_CVRR = "cvrr"; + + /** + * @hide + * + */ + public static final String PARAMETER_HDMI_RGB_RANGE = "hdmi_rgb_range"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_SPACE = "color_space"; + + /** + * @hide + * + */ + public static final String PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS = + "panel_init_max_lumince_nits"; + + /** + * @hide + * + */ + public static final String PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID = + "panel_init_max_lumince_valid"; + + /** + * @hide + * + */ + public static final String PARAMETER_GAMMA = "gamma"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TEMPERATURE_RED_OFFSET = + "color_temperature_red_offset"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET = + "color_temperature_green_offset"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET = + "color_temperature_blue_offset"; + + /** + * @hide + * + */ + public static final String PARAMETER_ELEVEN_POINT_RED = "eleven_point_red"; + + /** + * @hide + * + */ + public static final String PARAMETER_ELEVEN_POINT_GREEN = "eleven_point_green"; + + /** + * @hide + * + */ + public static final String PARAMETER_ELEVEN_POINT_BLUE = "eleven_point_blue"; + + /** + * @hide + * + */ + public static final String PARAMETER_LOW_BLUE_LIGHT = "low_blue_light"; + + /** + * @hide + * + */ + public static final String PARAMETER_LD_MODE = "ld_mode"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_RED_GAIN = "osd_red_gain"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_GREEN_GAIN = "osd_green_gain"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_BLUE_GAIN = "osd_blue_gain"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_RED_OFFSET = "osd_red_offset"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_GREEN_OFFSET = "osd_green_offset"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_BLUE_OFFSET = "osd_blue_offset"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_HUE = "osd_hue"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_SATURATION = "osd_saturation"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_CONTRAST = "osd_contrast"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SWITCH = "color_tuner_switch"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_RED = "color_tuner_hue_red"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_GREEN = "color_tuner_hue_green"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_BLUE = "color_tuner_hue_blue"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_CYAN = "color_tuner_hue_cyan"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_MAGENTA = "color_tuner_hue_magenta"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_YELLOW = "color_tuner_hue_yellow"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_FLESH = "color_tuner_hue_flesh"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_RED = + "color_tuner_saturation_red"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_GREEN = + "color_tuner_saturation_green"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_BLUE = + "color_tuner_saturation_blue"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_CYAN = + "color_tuner_saturation_cyan"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_MAGENTA = + "color_tuner_saturation_magenta"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_YELLOW = + "color_tuner_saturation_yellow"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_FLESH = + "color_tuner_saturation_flesh"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_RED = + "color_tuner_luminance_red"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_GREEN = + "color_tuner_luminance_green"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_BLUE = + "color_tuner_luminance_blue"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_CYAN = + "color_tuner_luminance_cyan"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA = + "color_tuner_luminance_magenta"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW = + "color_tuner_luminance_yellow"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_FLESH = + "color_tuner_luminance_flesh"; + + /** + * @hide + * + */ + public static final String PARAMETER_ACTIVE_PROFILE = "active_profile"; + + /** + * @hide + * + */ + public static final String PARAMETER_PICTURE_QUALITY_EVENT_TYPE = + "picture_quality_event_type"; + private PictureQuality() { } } @@ -641,6 +974,17 @@ public class MediaQualityContract { */ public static final String PARAMETER_DIGITAL_OUTPUT_MODE = "digital_output_mode"; + /** + * @hide + */ + public static final String PARAMETER_ACTIVE_PROFILE = "active_profile"; + + /** + * @hide + */ + public static final String PARAMETER_SOUND_STYLE = "sound_style"; + + private SoundQuality() { } diff --git a/media/java/android/mtp/OWNERS b/media/java/android/mtp/OWNERS index 77ed08b1f9a5..c57265a91eff 100644 --- a/media/java/android/mtp/OWNERS +++ b/media/java/android/mtp/OWNERS @@ -1,9 +1,9 @@ set noparent -anothermark@google.com +vmartensson@google.com +nkapron@google.com febinthattil@google.com -aprasath@google.com +shubhankarm@google.com jsharkey@android.com jameswei@google.com rmojumder@google.com -kumarashishg@google.com diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp index a77bc9fe0570..a1ce495fe33d 100644 --- a/media/jni/android_mtp_MtpDatabase.cpp +++ b/media/jni/android_mtp_MtpDatabase.cpp @@ -910,7 +910,7 @@ MtpResponseCode MtpDatabase::getObjectInfo(MtpObjectHandle handle, case MTP_FORMAT_TIFF: case MTP_FORMAT_TIFF_EP: case MTP_FORMAT_DEFINED: { - String8 temp(path); + String8 temp {static_cast<std::string_view>(path)}; std::unique_ptr<FileStream> stream(new FileStream(temp)); piex::PreviewImageData image_data; if (!GetExifFromRawImage(stream.get(), temp, image_data)) { @@ -967,7 +967,7 @@ void* MtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) { case MTP_FORMAT_TIFF: case MTP_FORMAT_TIFF_EP: case MTP_FORMAT_DEFINED: { - String8 temp(path); + String8 temp {static_cast<std::string_view>(path)}; std::unique_ptr<FileStream> stream(new FileStream(temp)); piex::PreviewImageData image_data; if (!GetExifFromRawImage(stream.get(), temp, image_data)) { diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java index e9a0d3eceba3..017a1029d35c 100644 --- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java @@ -16,6 +16,15 @@ package com.android.audiopolicytest; +import static android.media.AudioManager.STREAM_ACCESSIBILITY; +import static android.media.AudioManager.STREAM_ALARM; +import static android.media.AudioManager.STREAM_DTMF; +import static android.media.AudioManager.STREAM_MUSIC; +import static android.media.AudioManager.STREAM_NOTIFICATION; +import static android.media.AudioManager.STREAM_RING; +import static android.media.AudioManager.STREAM_SYSTEM; +import static android.media.AudioManager.STREAM_VOICE_CALL; + import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.android.audiopolicytest.AudioVolumeTestUtil.DEFAULT_ATTRIBUTES; @@ -28,11 +37,15 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioSystem; +import android.media.IAudioService; import android.media.audiopolicy.AudioProductStrategy; import android.media.audiopolicy.AudioVolumeGroup; +import android.os.IBinder; +import android.os.ServiceManager; import android.platform.test.annotations.Presubmit; import android.util.Log; @@ -207,6 +220,32 @@ public class AudioManagerTest { } //----------------------------------------------------------------- + // Test getStreamVolume consistency with AudioService + //----------------------------------------------------------------- + @Test + public void getStreamMinMaxVolume_consistentWithAs() throws Exception { + IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); + IAudioService service = IAudioService.Stub.asInterface(b); + final int[] streamTypes = { + STREAM_VOICE_CALL, + STREAM_SYSTEM, + STREAM_RING, + STREAM_MUSIC, + STREAM_ALARM, + STREAM_NOTIFICATION, + STREAM_DTMF, + STREAM_ACCESSIBILITY, + }; + + for (int streamType : streamTypes) { + assertEquals(service.getStreamMinVolume(streamType), + mAudioManager.getStreamMinVolume(streamType)); + assertEquals(service.getStreamMaxVolume(streamType), + mAudioManager.getStreamMaxVolume(streamType)); + } + } + + //----------------------------------------------------------------- // Test Volume per Attributes setter/getters //----------------------------------------------------------------- @Test diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS index bdb6cdbea332..c57265a91eff 100644 --- a/media/tests/MtpTests/OWNERS +++ b/media/tests/MtpTests/OWNERS @@ -1,9 +1,9 @@ set noparent -anothermark@google.com +vmartensson@google.com +nkapron@google.com febinthattil@google.com -aprasath@google.com +shubhankarm@google.com jsharkey@android.com jameswei@google.com rmojumder@google.com -kumarashishg@google.com
\ No newline at end of file diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java index fe27cee7ba2d..acead8e2a0eb 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java +++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java @@ -510,7 +510,10 @@ public final class PageContentRepository { protected Void doInBackground(Void... params) { synchronized (mLock) { try { - if (mRenderer != null) { + // A page count < 0 indicates there was an error + // opening the document, in which case it doesn't + // need to be closed. + if (mRenderer != null && mPageCount >= 0) { mRenderer.closeDocument(); } } catch (RemoteException re) { diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java index b48c55ddfef0..a9d00e9a77eb 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java +++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java @@ -70,6 +70,7 @@ public final class RemotePrintDocument { private static final int STATE_CANCELING = 6; private static final int STATE_CANCELED = 7; private static final int STATE_DESTROYED = 8; + private static final int STATE_INVALID = 9; private final Context mContext; @@ -287,7 +288,8 @@ public final class RemotePrintDocument { } if (mState != STATE_STARTED && mState != STATE_UPDATED && mState != STATE_FAILED && mState != STATE_CANCELING - && mState != STATE_CANCELED && mState != STATE_DESTROYED) { + && mState != STATE_CANCELED && mState != STATE_DESTROYED + && mState != STATE_INVALID) { throw new IllegalStateException("Cannot finish in state:" + stateToString(mState)); } @@ -300,6 +302,16 @@ public final class RemotePrintDocument { } } + /** + * Mark this document as invalid. + */ + public void invalid() { + if (DEBUG) { + Log.i(LOG_TAG, "[CALLED] invalid()"); + } + mState = STATE_INVALID; + } + public void cancel(boolean force) { if (DEBUG) { Log.i(LOG_TAG, "[CALLED] cancel(" + force + ")"); @@ -491,6 +503,9 @@ public final class RemotePrintDocument { case STATE_DESTROYED: { return "STATE_DESTROYED"; } + case STATE_INVALID: { + return "STATE_INVALID"; + } default: { return "STATE_UNKNOWN"; } diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index 4a3a6d248254..2e3234e6e622 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java @@ -167,6 +167,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat private static final int STATE_PRINTER_UNAVAILABLE = 6; private static final int STATE_UPDATE_SLOW = 7; private static final int STATE_PRINT_COMPLETED = 8; + private static final int STATE_FILE_INVALID = 9; private static final int UI_STATE_PREVIEW = 0; private static final int UI_STATE_ERROR = 1; @@ -404,6 +405,11 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat public void onPause() { PrintSpoolerService spooler = mSpoolerProvider.getSpooler(); + if (isInvalid()) { + super.onPause(); + return; + } + if (mState == STATE_INITIALIZING) { if (isFinishing()) { if (spooler != null) { @@ -478,7 +484,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } if (mState == STATE_PRINT_CANCELED || mState == STATE_PRINT_CONFIRMED - || mState == STATE_PRINT_COMPLETED) { + || mState == STATE_PRINT_COMPLETED + || mState == STATE_FILE_INVALID) { return true; } @@ -509,23 +516,32 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat @Override public void onMalformedPdfFile() { onPrintDocumentError("Cannot print a malformed PDF file"); + mPrintedDocument.invalid(); + setState(STATE_FILE_INVALID); } @Override public void onSecurePdfFile() { onPrintDocumentError("Cannot print a password protected PDF file"); + mPrintedDocument.invalid(); + setState(STATE_FILE_INVALID); } private void onPrintDocumentError(String message) { setState(mProgressMessageController.cancel()); - ensureErrorUiShown(null, PrintErrorFragment.ACTION_RETRY); + ensureErrorUiShown( + getString(R.string.print_cannot_load_page), PrintErrorFragment.ACTION_NONE); setState(STATE_UPDATE_FAILED); if (DEBUG) { Log.i(LOG_TAG, "PrintJob state[" + PrintJobInfo.STATE_FAILED + "] reason: " + message); } PrintSpoolerService spooler = mSpoolerProvider.getSpooler(); - spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_FAILED, message); + // Use a cancel state for the spooler. This will prevent the notification from getting + // displayed and will remove the job. The notification (which displays the cancel and + // restart options) doesn't make sense for an invalid document since it will just fail + // again. + spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, message); mPrintedDocument.finish(); } @@ -995,6 +1011,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } private void setState(int state) { + if (isInvalid()) { + return; + } if (isFinalState(mState)) { if (isFinalState(state)) { if (DEBUG) { @@ -1015,7 +1034,12 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat private static boolean isFinalState(int state) { return state == STATE_PRINT_CANCELED || state == STATE_PRINT_COMPLETED - || state == STATE_CREATE_FILE_FAILED; + || state == STATE_CREATE_FILE_FAILED + || state == STATE_FILE_INVALID; + } + + private boolean isInvalid() { + return mState == STATE_FILE_INVALID; } private void updateSelectedPagesFromPreview() { @@ -1100,7 +1124,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } private void ensurePreviewUiShown() { - if (isFinishing() || isDestroyed()) { + if (isFinishing() || isDestroyed() || isInvalid()) { return; } if (mUiState != UI_STATE_PREVIEW) { @@ -1257,6 +1281,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } private boolean updateDocument(boolean clearLastError) { + if (isInvalid()) { + return false; + } if (!clearLastError && mPrintedDocument.hasUpdateError()) { return false; } @@ -1676,7 +1703,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat || mState == STATE_UPDATE_FAILED || mState == STATE_CREATE_FILE_FAILED || mState == STATE_PRINTER_UNAVAILABLE - || mState == STATE_UPDATE_SLOW) { + || mState == STATE_UPDATE_SLOW + || mState == STATE_FILE_INVALID) { disableOptionsUi(isFinalState(mState)); return; } @@ -2100,6 +2128,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } private boolean canUpdateDocument() { + if (isInvalid()) { + return false; + } if (mPrintedDocument.isDestroyed()) { return false; } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt index 3309faaa8db2..3a6327962dc2 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.flowOn data class EnhancedConfirmation( val key: String, val packageName: String, + val isRestrictedSettingAllowed: Boolean? ) data class Restrictions( val userId: Int = UserHandle.myUserId(), @@ -92,6 +93,9 @@ internal class RestrictionsProviderImpl( } restrictions.enhancedConfirmation?.let { ec -> + if (ec.isRestrictedSettingAllowed == true) { + return NoRestricted + } RestrictedLockUtilsInternal .checkIfRequiresEnhancedConfirmation(context, ec.key, ec.packageName) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index 7466f95e3fb8..5580d2e3211b 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -155,7 +155,7 @@ internal fun <T : AppRecord> TogglePermissionAppListModel<T>.TogglePermissionApp } RestrictedSwitchPreference( model = switchModel, - restrictions = getRestrictions(userId, packageName), + restrictions = getRestrictions(userId, packageName, isAllowed()), ifBlockedByAdminOverrideCheckedValueTo = switchifBlockedByAdminOverrideCheckedValueTo, restrictionsProviderFactory = restrictionsProviderFactory, ) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt index d2867af1eda6..771eb85ee21a 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt @@ -116,12 +116,15 @@ fun <T : AppRecord> TogglePermissionAppListModel<T>.isChangeableWithSystemUidChe fun <T : AppRecord> TogglePermissionAppListModel<T>.getRestrictions( userId: Int, packageName: String, + isRestrictedSettingAllowed: Boolean? ) = Restrictions( userId = userId, keys = switchRestrictionKeys, enhancedConfirmation = - enhancedConfirmationKey?.let { key -> EnhancedConfirmation(key, packageName) }, + enhancedConfirmationKey?.let { + key -> EnhancedConfirmation(key, packageName, isRestrictedSettingAllowed) + }, ) interface TogglePermissionAppListProvider { diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt index ec44d2af4ffa..bef2bdaaefaf 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt @@ -149,13 +149,14 @@ internal class TogglePermissionInternalAppListModel<T : AppRecord>( @Composable fun getSummary(record: T): () -> String { + val allowed = listModel.isAllowed(record) val restrictions = listModel.getRestrictions( userId = record.app.userId, packageName = record.app.packageName, + allowed() ) val restrictedMode by restrictionsProviderFactory.rememberRestrictedMode(restrictions) - val allowed = listModel.isAllowed(record) return RestrictedSwitchPreferenceModel.getSummary( context = context, summaryIfNoRestricted = { getSummaryIfNoRestricted(allowed()) }, diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index ec346c87cdf3..6491bf5c8f5b 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -553,7 +553,11 @@ <service android:name=".wallpapers.GradientColorWallpaper" android:singleUser="true" android:permission="android.permission.BIND_WALLPAPER" - android:exported="true" /> + android:exported="true"> + <meta-data android:name="android.service.wallpaper" + android:resource="@xml/gradient_color_wallpaper"> + </meta-data> + </service> <activity android:name=".tuner.TunerActivity" android:enabled="false" diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 01ee2cd5d22b..7cf83135c237 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1852,6 +1852,13 @@ flag { } flag { + name: "double_tap_to_sleep" + namespace: "systemui" + description: "Enable Double Tap to Sleep" + bug: "385194612" +} + +flag{ name: "gsf_bouncer" namespace: "systemui" description: "Applies GSF font styles to Bouncer surfaces." @@ -1935,16 +1942,6 @@ flag { } flag { - name: "stabilize_heads_up_group" - namespace: "systemui" - description: "Stabilize heads up groups in VisualStabilityCoordinator" - bug: "381864715" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "magnetic_notification_horizontal_swipe" namespace: "systemui" description: "Add support for magnetic behavior on horizontal notification swipes." @@ -1962,6 +1959,13 @@ flag { } flag { + name: "permission_helper_ui_rich_ongoing" + namespace: "systemui" + description: "[RONs] Guards inline permission helper for demoting RONs" + bug: "379186372" +} + +flag { name: "aod_ui_rich_ongoing" namespace: "systemui" description: "Show a rich ongoing notification on the always-on display (depends on ui_rich_ongoing)" @@ -1976,4 +1980,14 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "shade_launch_accessibility" + namespace: "systemui" + description: "Intercept accessibility focus events for the Shade during launch animations to avoid stray TalkBack events." + bug: "379222226" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index bdd0da9ce4a4..4e10ff689b19 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -67,11 +67,10 @@ import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon import com.android.systemui.compose.modifiers.sysuiResTag -import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig -import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.res.R +import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState import kotlin.math.round import kotlinx.coroutines.flow.distinctUntilChanged @@ -103,6 +102,10 @@ fun VolumeSlider( } val value by valueState(state) + val interactionSource = remember { MutableInteractionSource() } + val hapticsViewModel: SliderHapticsViewModel? = + setUpHapticsViewModel(value, state.valueRange, interactionSource, hapticsViewModelFactory) + Column(modifier = modifier.animateContentSize(), verticalArrangement = Arrangement.Top) { Row( horizontalArrangement = Arrangement.spacedBy(12.dp), @@ -127,8 +130,14 @@ fun VolumeSlider( Slider( value = value, valueRange = state.valueRange, - onValueChange = onValueChange, - onValueChangeFinished = onValueChangeFinished, + onValueChange = { newValue -> + hapticsViewModel?.addVelocityDataPoint(newValue) + onValueChange(newValue) + }, + onValueChangeFinished = { + hapticsViewModel?.onValueChangeEnded() + onValueChangeFinished?.invoke() + }, enabled = state.isEnabled, modifier = Modifier.height(40.dp) @@ -210,41 +219,8 @@ private fun LegacyVolumeSlider( ) { val value by valueState(state) val interactionSource = remember { MutableInteractionSource() } - val sliderStepSize = 1f / (state.valueRange.endInclusive - state.valueRange.start) val hapticsViewModel: SliderHapticsViewModel? = - hapticsViewModelFactory?.let { - rememberViewModel(traceName = "SliderHapticsViewModel") { - it.create( - interactionSource, - state.valueRange, - Orientation.Horizontal, - SliderHapticFeedbackConfig( - lowerBookendScale = 0.2f, - progressBasedDragMinScale = 0.2f, - progressBasedDragMaxScale = 0.5f, - deltaProgressForDragThreshold = 0f, - additionalVelocityMaxBump = 0.2f, - maxVelocityToScale = 0.1f, /* slider progress(from 0 to 1) per sec */ - sliderStepSize = sliderStepSize, - ), - SeekableSliderTrackerConfig( - lowerBookendThreshold = 0f, - upperBookendThreshold = 1f, - ), - ) - } - } - var lastDiscreteStep by remember { mutableFloatStateOf(round(value)) } - LaunchedEffect(value) { - snapshotFlow { value } - .map { round(it) } - .filter { it != lastDiscreteStep } - .distinctUntilChanged() - .collect { discreteStep -> - lastDiscreteStep = discreteStep - hapticsViewModel?.onValueChange(discreteStep) - } - } + setUpHapticsViewModel(value, state.valueRange, interactionSource, hapticsViewModelFactory) PlatformSlider( modifier = @@ -357,3 +333,36 @@ private fun SliderIcon( content = { Icon(modifier = Modifier.size(24.dp), icon = icon) }, ) } + +@Composable +fun setUpHapticsViewModel( + value: Float, + valueRange: ClosedFloatingPointRange<Float>, + interactionSource: MutableInteractionSource, + hapticsViewModelFactory: SliderHapticsViewModel.Factory?, +): SliderHapticsViewModel? { + return hapticsViewModelFactory?.let { + rememberViewModel(traceName = "SliderHapticsViewModel") { + it.create( + interactionSource, + valueRange, + Orientation.Horizontal, + VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(valueRange), + VolumeHapticsConfigsProvider.seekableSliderTrackerConfig, + ) + } + .also { hapticsViewModel -> + var lastDiscreteStep by remember { mutableFloatStateOf(round(value)) } + LaunchedEffect(value) { + snapshotFlow { value } + .map { round(it) } + .filter { it != lastDiscreteStep } + .distinctUntilChanged() + .collect { discreteStep -> + lastDiscreteStep = discreteStep + hapticsViewModel.onValueChange(discreteStep) + } + } + } + } +} 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 e69fa994931d..b2bc9ef4e363 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 @@ -124,7 +124,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController ClockFontAxis( key = "wght", type = AxisType.Float, - minValue = 1f, + minValue = 25f, currentValue = 400f, maxValue = 1000f, name = "Weight", diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt index 827bd6898310..67cbf3082632 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt @@ -125,7 +125,19 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: layerController.faceEvents.onThemeChanged(theme) } - override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) { + override fun onFontAxesChanged(settings: List<ClockFontAxisSetting>) { + var axes = settings + if (!isLargeClock) { + axes = + axes.map { axis -> + if (axis.key == "wdth" && axis.value > SMALL_CLOCK_MAX_WDTH) { + axis.copy(value = SMALL_CLOCK_MAX_WDTH) + } else { + axis + } + } + } + layerController.events.onFontAxesChanged(axes) } @@ -236,6 +248,7 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: } companion object { + val SMALL_CLOCK_MAX_WDTH = 120f val SMALL_LAYER_CONFIG = LayerConfig( timespec = DigitalTimespec.TIME_FULL_FORMAT, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt index 3bf59f34db76..cd05980385e0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt @@ -50,6 +50,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory +import com.android.systemui.keyboard.shortcut.shortcutHelperAccessibilityShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource @@ -88,6 +89,7 @@ class DefaultShortcutCategoriesRepositoryTest : SysuiTestCase() { it.shortcutHelperAppCategoriesShortcutsSource = fakeAppCategoriesSource it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperAccessibilityShortcutsSource = FakeKeyboardShortcutGroupsSource() } private val repo = kosmos.defaultShortcutCategoriesRepository @@ -284,14 +286,20 @@ class DefaultShortcutCategoriesRepositoryTest : SysuiTestCase() { val categories by collectLastValue(repo.categories) val cycleForwardThroughRecentAppsShortcut = - categories?.first { it.type == ShortcutCategoryType.MultiTasking } - ?.subCategories?.first { it.label == recentAppsGroup.label } - ?.shortcuts?.first { it.label == CYCLE_FORWARD_THROUGH_RECENT_APPS_SHORTCUT_LABEL } + categories + ?.first { it.type == ShortcutCategoryType.MultiTasking } + ?.subCategories + ?.first { it.label == recentAppsGroup.label } + ?.shortcuts + ?.first { it.label == CYCLE_FORWARD_THROUGH_RECENT_APPS_SHORTCUT_LABEL } val cycleBackThroughRecentAppsShortcut = - categories?.first { it.type == ShortcutCategoryType.MultiTasking } - ?.subCategories?.first { it.label == recentAppsGroup.label } - ?.shortcuts?.first { it.label == CYCLE_BACK_THROUGH_RECENT_APPS_SHORTCUT_LABEL } + categories + ?.first { it.type == ShortcutCategoryType.MultiTasking } + ?.subCategories + ?.first { it.label == recentAppsGroup.label } + ?.shortcuts + ?.first { it.label == CYCLE_BACK_THROUGH_RECENT_APPS_SHORTCUT_LABEL } assertThat(cycleForwardThroughRecentAppsShortcut?.isCustomizable).isFalse() assertThat(cycleBackThroughRecentAppsShortcut?.isCustomizable).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt index 8f0bc640f0eb..61490986f4a9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System +import com.android.systemui.keyboard.shortcut.shortcutHelperAccessibilityShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource @@ -76,6 +77,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperAccessibilityShortcutsSource = FakeKeyboardShortcutGroupsSource() it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt index 000024f9b814..7a343351ef64 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts import com.android.systemui.keyboard.shortcut.shortcutCustomizationDialogStarterFactory +import com.android.systemui.keyboard.shortcut.shortcutHelperAccessibilityShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource @@ -66,6 +67,7 @@ class ShortcutHelperDialogStarterTest : SysuiTestCase() { it.testDispatcher = UnconfinedTestDispatcher() it.shortcutHelperSystemShortcutsSource = fakeSystemSource it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource + it.shortcutHelperAccessibilityShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt index 3fc46b973959..cf38072912e9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt @@ -45,6 +45,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType. import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import com.android.systemui.keyboard.shortcut.shared.model.shortcut +import com.android.systemui.keyboard.shortcut.shortcutHelperAccessibilityShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource @@ -95,6 +96,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperAccessibilityShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } @@ -112,9 +114,12 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { fakeSystemSource.setGroups(TestShortcuts.systemGroups) fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups) fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups) - whenever(mockPackageManager.getApplicationInfo(anyString(), eq(0))).thenReturn(mockApplicationInfo) - whenever(mockPackageManager.getApplicationLabel(mockApplicationInfo)).thenReturn("Current App") - whenever(mockPackageManager.getApplicationIcon(anyString())).thenThrow(NameNotFoundException()) + whenever(mockPackageManager.getApplicationInfo(anyString(), eq(0))) + .thenReturn(mockApplicationInfo) + whenever(mockPackageManager.getApplicationLabel(mockApplicationInfo)) + .thenReturn("Current App") + whenever(mockPackageManager.getApplicationIcon(anyString())) + .thenThrow(NameNotFoundException()) whenever(mockUserContext.packageManager).thenReturn(mockPackageManager) whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager) } @@ -278,11 +283,11 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { fun shortcutsUiState_currentAppIsLauncher_defaultSelectedCategoryIsSystem() = testScope.runTest { whenever( - mockRoleManager.getRoleHoldersAsUser( - RoleManager.ROLE_HOME, - fakeUserTracker.userHandle, + mockRoleManager.getRoleHoldersAsUser( + RoleManager.ROLE_HOME, + fakeUserTracker.userHandle, + ) ) - ) .thenReturn(listOf(TestShortcuts.currentAppPackageName)) val uiState by collectLastValue(viewModel.shortcutsUiState) @@ -318,23 +323,23 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { label = "System", iconSource = IconSource(imageVector = Icons.Default.Tv), shortcutCategory = - ShortcutCategory( - System, - subCategoryWithShortcutLabels("first Foo shortcut1"), - subCategoryWithShortcutLabels( - "second foO shortcut2", - subCategoryLabel = SECOND_SIMPLE_GROUP_LABEL, + ShortcutCategory( + System, + subCategoryWithShortcutLabels("first Foo shortcut1"), + subCategoryWithShortcutLabels( + "second foO shortcut2", + subCategoryLabel = SECOND_SIMPLE_GROUP_LABEL, + ), ), - ), ), ShortcutCategoryUi( label = "Multitasking", iconSource = IconSource(imageVector = Icons.Default.VerticalSplit), shortcutCategory = - ShortcutCategory( - MultiTasking, - subCategoryWithShortcutLabels("third FoO shortcut1"), - ), + ShortcutCategory( + MultiTasking, + subCategoryWithShortcutLabels("third FoO shortcut1"), + ), ), ) } @@ -420,9 +425,8 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { @Test fun shortcutsUiState_shouldShowResetButton_isTrueWhenThereAreCustomShortcuts() = testScope.runTest { - whenever( - inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) - ).thenReturn(listOf(allAppsInputGestureData)) + whenever(inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)) + .thenReturn(listOf(allAppsInputGestureData)) val uiState by collectLastValue(viewModel.shortcutsUiState) testHelper.showFromActivity() @@ -433,7 +437,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { @Test fun shortcutsUiState_searchQuery_isResetAfterHelperIsClosedAndReOpened() = - testScope.runTest{ + testScope.runTest { val uiState by collectLastValue(viewModel.shortcutsUiState) openHelperAndSearchForFooString() @@ -443,7 +447,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { assertThat((uiState as? ShortcutsUiState.Active)?.searchQuery).isEqualTo("") } - private fun openHelperAndSearchForFooString(){ + private fun openHelperAndSearchForFooString() { testHelper.showFromActivity() viewModel.onSearchQueryChanged("foo") } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt index 83b821619659..286f8bfd63a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt @@ -123,8 +123,8 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f), - startValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f, - endValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f, + startValue = kosmos.blurConfig.maxBlurRadiusPx, + endValue = kosmos.blurConfig.maxBlurRadiusPx, transitionFactory = ::step, actualValuesProvider = { values }, checkInterpolatedValues = false, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt index fdee8d0544f0..60a19a4c7d07 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt @@ -198,8 +198,8 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f), - startValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f, - endValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f, + startValue = kosmos.blurConfig.maxBlurRadiusPx, + endValue = kosmos.blurConfig.maxBlurRadiusPx, transitionFactory = ::step, actualValuesProvider = { values }, checkInterpolatedValues = false, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java index 7f9313cbeb5b..83b68fed768e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java @@ -18,7 +18,7 @@ package com.android.systemui.navigationbar.views; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; import static android.inputmethodservice.InputMethodService.IME_VISIBLE; @@ -32,7 +32,7 @@ import static com.android.systemui.navigationbar.views.NavigationBar.NavBarActio import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS; import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import static com.google.common.truth.Truth.assertThat; @@ -501,7 +501,7 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */); verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SHOWING), eq(true)); - verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_SHOWING), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING), eq(true)); } /** @@ -515,7 +515,7 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, false /* showImeSwitcher */); verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SHOWING), eq(true)); - verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_SHOWING), eq(false)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING), eq(false)); } /** @@ -532,7 +532,7 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, 0 /* vis */, BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */); verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SHOWING), eq(false)); - verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_SHOWING), eq(false)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING), eq(false)); } /** @@ -546,7 +546,7 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, BACK_DISPOSITION_ADJUST_NOTHING, true /* showImeSwitcher */); verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SHOWING), eq(true)); - verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_SHOWING), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING), eq(true)); } @Test @@ -568,12 +568,12 @@ public class NavigationBarTest extends SysuiTestCase { // Verify IME window state will be updated in default NavBar & external NavBar state reset. assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN - | NAVIGATION_HINT_IME_SWITCHER_SHOWN, + | NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN, defaultNavBar.getNavigationIconHints()); assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); - assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) - != 0); + assertFalse((externalNavBar.getNavigationIconHints() + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0); externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); @@ -581,12 +581,12 @@ public class NavigationBarTest extends SysuiTestCase { BACK_DISPOSITION_DEFAULT, false); // Verify IME window state will be updated in external NavBar & default NavBar state reset. assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN - | NAVIGATION_HINT_IME_SWITCHER_SHOWN, + | NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN, externalNavBar.getNavigationIconHints()); assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); - assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) - != 0); + assertFalse((defaultNavBar.getNavigationIconHints() + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0); } @Test @@ -604,8 +604,8 @@ public class NavigationBarTest extends SysuiTestCase { BACK_DISPOSITION_DEFAULT, true); assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); - assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) - != 0); + assertTrue((mNavigationBar.getNavigationIconHints() + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0); // Verify navbar didn't alter and showing back icon when the keyguard is showing without // requesting IME insets visible. @@ -614,8 +614,8 @@ public class NavigationBarTest extends SysuiTestCase { BACK_DISPOSITION_DEFAULT, true); assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); - assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) - != 0); + assertFalse((mNavigationBar.getNavigationIconHints() + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0); // Verify navbar altered and showing back icon when the keyguard is showing and // requesting IME insets visible. @@ -625,8 +625,8 @@ public class NavigationBarTest extends SysuiTestCase { BACK_DISPOSITION_DEFAULT, true); assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); - assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) - != 0); + assertTrue((mNavigationBar.getNavigationIconHints() + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index ee9cb141a700..555c717e1e65 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -20,7 +20,7 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper.RunWithLooper import android.view.Choreographer -import android.view.MotionEvent +import android.view.accessibility.AccessibilityEvent import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -45,7 +45,10 @@ import com.android.systemui.res.R import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler +import com.android.systemui.shade.data.repository.ShadeAnimationRepository +import com.android.systemui.shade.data.repository.ShadeRepositoryImpl import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.DragDownHelper import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -185,6 +188,10 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { notificationShadeDepthController, underTest, shadeViewController, + ShadeAnimationInteractorLegacyImpl( + ShadeAnimationRepository(), + ShadeRepositoryImpl(testScope), + ), panelExpansionInteractor, ShadeExpansionStateManager(), notificationStackScrollLayoutController, @@ -259,6 +266,20 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { verify(configurationForwarder).dispatchOnMovedToDisplay(eq(1), eq(config)) } + @Test + @EnableFlags(AConfigFlags.FLAG_SHADE_LAUNCH_ACCESSIBILITY) + fun requestSendAccessibilityEvent_duringLaunchAnimation_blocksFocusEvent() { + underTest.setAnimatingContentLaunch(true) + + assertThat( + underTest.requestSendAccessibilityEvent( + underTest.getChildAt(0), + AccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED), + ) + ) + .isFalse() + } + private fun captureInteractionEventHandler() { verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) interactionEventHandler = interactionEventHandlerCaptor.value diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt index ab5fa8ef43fb..5566c10dc9e9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt @@ -38,7 +38,7 @@ class QsBatteryModeControllerTest : SysuiTestCase() { private val kosmos = testKosmos() private val insetsProviderStore = kosmos.fakeStatusBarContentInsetsProviderStore - private val insetsProvider = insetsProviderStore.defaultDisplay + private val insetsProvider = insetsProviderStore.forDisplay(context.displayId) @JvmField @Rule val mockitoRule = MockitoJUnit.rule()!! diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt index a47db2ec728b..668f568d7f46 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt @@ -16,15 +16,11 @@ package com.android.systemui.shade.domain.interactor -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope -import com.android.systemui.shade.data.repository.shadeRepository -import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -48,82 +44,79 @@ class ShadeModeInteractorImplTest : SysuiTestCase() { } @Test - @DisableFlags(DualShade.FLAG_NAME) fun legacyShadeMode_narrowScreen_singleShade() = testScope.runTest { val shadeMode by collectLastValue(underTest.shadeMode) - kosmos.shadeRepository.setShadeLayoutWide(false) + kosmos.enableSingleShade() assertThat(shadeMode).isEqualTo(ShadeMode.Single) } @Test - @DisableFlags(DualShade.FLAG_NAME) fun legacyShadeMode_wideScreen_splitShade() = testScope.runTest { val shadeMode by collectLastValue(underTest.shadeMode) - kosmos.shadeRepository.setShadeLayoutWide(true) + kosmos.enableSplitShade() assertThat(shadeMode).isEqualTo(ShadeMode.Split) } @Test - @EnableFlags(DualShade.FLAG_NAME) fun shadeMode_wideScreen_isDual() = testScope.runTest { val shadeMode by collectLastValue(underTest.shadeMode) - kosmos.shadeRepository.setShadeLayoutWide(true) + kosmos.enableDualShade(wideLayout = true) assertThat(shadeMode).isEqualTo(ShadeMode.Dual) } @Test - @EnableFlags(DualShade.FLAG_NAME) fun shadeMode_narrowScreen_isDual() = testScope.runTest { val shadeMode by collectLastValue(underTest.shadeMode) - kosmos.shadeRepository.setShadeLayoutWide(false) + kosmos.enableDualShade(wideLayout = false) assertThat(shadeMode).isEqualTo(ShadeMode.Dual) } @Test - @EnableFlags(DualShade.FLAG_NAME) - fun isDualShade_flagEnabled_true() = + fun isDualShade_settingEnabled_returnsTrue() = testScope.runTest { - // Initiate collection. + // TODO(b/391578667): Add a test case for user switching once the bug is fixed. val shadeMode by collectLastValue(underTest.shadeMode) + kosmos.enableDualShade() + assertThat(shadeMode).isEqualTo(ShadeMode.Dual) assertThat(underTest.isDualShade).isTrue() } @Test - @DisableFlags(DualShade.FLAG_NAME) - fun isDualShade_flagDisabled_false() = + fun isDualShade_settingDisabled_returnsFalse() = testScope.runTest { - // Initiate collection. val shadeMode by collectLastValue(underTest.shadeMode) + kosmos.disableDualShade() + assertThat(shadeMode).isNotEqualTo(ShadeMode.Dual) assertThat(underTest.isDualShade).isFalse() } @Test fun getTopEdgeSplitFraction_narrowScreen_splitInHalf() = testScope.runTest { - // Ensure isShadeLayoutWide is collected. - val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide) - kosmos.shadeRepository.setShadeLayoutWide(false) + val shadeMode by collectLastValue(underTest.shadeMode) + kosmos.enableDualShade(wideLayout = false) + assertThat(shadeMode).isEqualTo(ShadeMode.Dual) assertThat(underTest.getTopEdgeSplitFraction()).isEqualTo(0.5f) } @Test fun getTopEdgeSplitFraction_wideScreen_splitInHalf() = testScope.runTest { - // Ensure isShadeLayoutWide is collected. - val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide) - kosmos.shadeRepository.setShadeLayoutWide(true) + val shadeMode by collectLastValue(underTest.shadeMode) + kosmos.enableDualShade(wideLayout = true) + assertThat(shadeMode).isEqualTo(ShadeMode.Dual) assertThat(underTest.getTopEdgeSplitFraction()).isEqualTo(0.5f) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index eec23d3ffb1a..55f3717535b7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -609,7 +609,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = promotedContentBuilder.build(), ) ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt index 22906b8724b5..c515d940d2aa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt @@ -144,7 +144,8 @@ class TargetSdkResolverTest : SysuiTestCase() { /* rankingAdjustment = */ 0, /* isBubble = */ false, /* proposedImportance = */ 0, - /* sensitiveContent = */ false + /* sensitiveContent = */ false, + /* summarization = */ null ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java index 7b120947b1d6..2aa1efaa429f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java @@ -107,7 +107,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { return SceneContainerFlagParameterizationKt - .andSceneContainer(allCombinationsOf(Flags.FLAG_STABILIZE_HEADS_UP_GROUP)); + .andSceneContainer(allCombinationsOf(Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2)); } private VisualStabilityCoordinator mCoordinator; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java index b2962eeb9001..66277e2d13a6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java @@ -198,7 +198,8 @@ public class BundleNotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); // and the feedback button is clicked, final View feedbackButton = mInfo.findViewById(R.id.notification_guts_bundle_feedback); feedbackButton.performClick(); @@ -253,7 +254,8 @@ public class BundleNotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final View feedbackButton = mInfo.findViewById(R.id.notification_guts_bundle_feedback); feedbackButton.performClick(); @@ -294,7 +296,8 @@ public class BundleNotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final View feedbackButton = mInfo.findViewById(R.id.notification_guts_bundle_feedback); feedbackButton.performClick(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt index 6a0a5bb3b191..39c42f183481 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt @@ -544,6 +544,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( /* wasShownHighPriority = */ eq(true), eq(assistantFeedbackController), eq(metricsLogger), + any<View.OnClickListener>(), ) } @@ -580,6 +581,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( /* wasShownHighPriority = */ eq(false), eq(assistantFeedbackController), eq(metricsLogger), + any<View.OnClickListener>(), ) } @@ -614,6 +616,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( /* wasShownHighPriority = */ eq(false), eq(assistantFeedbackController), eq(metricsLogger), + any<View.OnClickListener>(), ) } @@ -651,6 +654,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( /* wasShownHighPriority = */ eq(false), eq(assistantFeedbackController), eq(metricsLogger), + any<View.OnClickListener>(), ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java index 245a6a0b130c..fdba7ba34855 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java @@ -187,7 +187,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name); assertTrue(textView.getText().toString().contains("App Name")); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); @@ -213,7 +213,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final ImageView iconView = mNotificationInfo.findViewById(R.id.pkg_icon); assertEquals(iconDrawable, iconView.getDrawable()); } @@ -235,7 +235,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(GONE, nameView.getVisibility()); } @@ -266,7 +266,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(VISIBLE, nameView.getVisibility()); assertTrue(nameView.getText().toString().contains("Proxied")); @@ -289,7 +289,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(GONE, groupNameView.getVisibility()); } @@ -317,7 +317,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(View.VISIBLE, groupNameView.getVisibility()); assertEquals("Test Group Name", groupNameView.getText()); @@ -340,7 +340,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(TEST_CHANNEL_NAME, textView.getText()); } @@ -362,7 +362,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, textView.getVisibility()); } @@ -388,7 +388,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -410,7 +411,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -436,7 +438,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final View settingsButton = mNotificationInfo.findViewById(R.id.info); settingsButton.performClick(); @@ -461,7 +464,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -486,7 +490,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -508,7 +513,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.bindNotification( mMockPackageManager, mMockINotificationManager, @@ -524,7 +530,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertEquals(View.VISIBLE, settingsButton.getVisibility()); } @@ -546,7 +553,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_text); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.notification_unblockable_desc), @@ -589,7 +597,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_call_text); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.notification_unblockable_call_desc), @@ -632,7 +641,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(GONE, mNotificationInfo.findViewById(R.id.non_configurable_call_text).getVisibility()); assertEquals(VISIBLE, @@ -659,7 +669,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic).getVisibility()); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility()); } @@ -681,7 +692,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic).getVisibility()); assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility()); } @@ -705,7 +717,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertTrue(mNotificationInfo.findViewById(R.id.automatic).isSelected()); } @@ -726,7 +739,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertTrue(mNotificationInfo.findViewById(R.id.alert).isSelected()); } @@ -747,7 +761,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertTrue(mNotificationInfo.findViewById(R.id.silence).isSelected()); } @@ -768,7 +783,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mTestableLooper.processAllMessages(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), eq(TEST_UID), any()); @@ -791,7 +807,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(1, mUiEventLogger.numLogs()); assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(), mUiEventLogger.eventId(0)); @@ -815,7 +832,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.alert).performClick(); mTestableLooper.processAllMessages(); @@ -842,7 +860,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.silence).performClick(); mTestableLooper.processAllMessages(); @@ -869,7 +888,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.automatic).performClick(); mTestableLooper.processAllMessages(); @@ -897,7 +917,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -924,7 +945,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -959,7 +981,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.handleCloseControls(true, false); @@ -987,7 +1010,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.silence).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1028,7 +1052,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.alert).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1065,7 +1090,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.automatic).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1097,7 +1123,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.silence).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1133,7 +1160,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(mContext.getString(R.string.inline_done_button), ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); @@ -1171,7 +1199,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.silence).performClick(); mNotificationInfo.handleCloseControls(false, false); @@ -1202,7 +1231,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(mContext.getString(R.string.inline_done_button), ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); @@ -1240,7 +1270,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.silence).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1269,7 +1300,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(mContext.getString(R.string.inline_done_button), ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); @@ -1300,7 +1332,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.alert).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1335,7 +1368,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.alert).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1368,7 +1402,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.alert).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1401,7 +1436,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.alert).performClick(); @@ -1427,7 +1463,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertFalse(mNotificationInfo.willBeRemoved()); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java new file mode 100644 index 000000000000..b33f93d5b523 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO; +import static android.app.NotificationManager.IMPORTANCE_LOW; + +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.INotificationManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.platform.test.annotations.EnableFlags; +import android.service.notification.StatusBarNotification; +import android.telecom.TelecomManager; +import android.testing.TestableLooper; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.testing.UiEventLoggerFake; +import com.android.systemui.Dependency; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.res.R; +import com.android.systemui.statusbar.notification.AssistantFeedbackController; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.concurrent.CountDownLatch; + +@SmallTest +@RunWith(AndroidJUnit4.class) +@TestableLooper.RunWithLooper +public class PromotedNotificationInfoTest extends SysuiTestCase { + private static final String TEST_PACKAGE_NAME = "test_package"; + private static final int TEST_UID = 1; + private static final String TEST_CHANNEL = "test_channel"; + private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME"; + + private TestableLooper mTestableLooper; + private PromotedNotificationInfo mInfo; + private NotificationChannel mNotificationChannel; + private StatusBarNotification mSbn; + private NotificationEntry mEntry; + private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake(); + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + @Mock + private MetricsLogger mMetricsLogger; + @Mock + private INotificationManager mMockINotificationManager; + @Mock + private PackageManager mMockPackageManager; + @Mock + private OnUserInteractionCallback mOnUserInteractionCallback; + @Mock + private ChannelEditorDialogController mChannelEditorDialogController; + @Mock + private AssistantFeedbackController mAssistantFeedbackController; + @Mock + private TelecomManager mTelecomManager; + + @Before + public void setUp() throws Exception { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = TEST_UID; // non-zero + + mNotificationChannel = new NotificationChannel( + TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW); + Notification notification = new Notification(); + notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, applicationInfo); + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, + notification, UserHandle.getUserHandleForUid(TEST_UID), null, 0); + mEntry = new NotificationEntryBuilder().setSbn(mSbn).build(); + when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(false); + + mTestableLooper = TestableLooper.get(this); + + mContext.addMockSystemService(TelecomManager.class, mTelecomManager); + + mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); + // Inflate the layout + final LayoutInflater layoutInflater = LayoutInflater.from(mContext); + mInfo = (PromotedNotificationInfo) layoutInflater.inflate( + R.layout.promoted_notification_info, null); + mInfo.setGutsParent(mock(NotificationGuts.class)); + // Our view is never attached to a window so the View#post methods in + // BundleNotificationInfo never get called. Setting this will skip the post and do the + // action immediately. + mInfo.mSkipPost = true; + } + + @Test + @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + public void testBindNotification_setsOnClickListenerForFeedback() throws Exception { + + // Bind the notification to the Info object + final CountDownLatch latch = new CountDownLatch(1); + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + mOnUserInteractionCallback, + mChannelEditorDialogController, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + mUiEventLogger, + true, + false, + true, + mAssistantFeedbackController, + mMetricsLogger, + null); + // Click demote button + final View demoteButton = mInfo.findViewById(R.id.promoted_demote); + demoteButton.performClick(); + // verify that notiManager tried to demote + verify(mMockINotificationManager, atLeastOnce()).setCanBePromoted(TEST_PACKAGE_NAME, + mSbn.getUid(), false, true); + + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt index 43ad042ecf78..57b7df7a8d31 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt @@ -162,7 +162,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any())) .thenReturn(iconManager) - Mockito.`when`(statusBarContentInsetsProviderStore.defaultDisplay) + Mockito.`when`(statusBarContentInsetsProviderStore.forDisplay(context.displayId)) .thenReturn(kosmos.mockStatusBarContentInsetsProvider) allowTestableLooperAsMainThread() looper.runWithLooper { @@ -180,6 +180,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { private fun createController(): KeyguardStatusBarViewController { return KeyguardStatusBarViewController( kosmos.testDispatcher, + context, keyguardStatusBarView, carrierTextController, configurationController, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt index fec186e862be..b837253f44a4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt @@ -16,12 +16,16 @@ package com.android.systemui.volume.dialog.domain.interactor +import android.media.AudioManager.RINGER_MODE_NORMAL +import android.media.AudioManager.RINGER_MODE_SILENT +import android.media.AudioManager.RINGER_MODE_VIBRATE import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.plugins.fakeVolumeDialogController import com.android.systemui.testKosmos import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel import com.google.common.truth.Truth.assertThat @@ -44,4 +48,30 @@ class VolumeDialogCallbacksInteractorTest : SysuiTestCase() { val event by collectLastValue(underTest.event) assertThat(event).isInstanceOf(VolumeDialogEventModel.SubscribedToEvents::class.java) } + + @Test + fun showSilentHint_setsRingerModeToNormal() = + kosmos.runTest { + fakeVolumeDialogController.setRingerMode(RINGER_MODE_VIBRATE, false) + + underTest // It should eagerly collect the values and update the controller + fakeVolumeDialogController.onShowSilentHint() + fakeVolumeDialogController.getState() + + assertThat(fakeVolumeDialogController.state.ringerModeInternal) + .isEqualTo(RINGER_MODE_NORMAL) + } + + @Test + fun showVibrateHint_setsRingerModeToSilent() = + kosmos.runTest { + fakeVolumeDialogController.setRingerMode(RINGER_MODE_VIBRATE, false) + + underTest // It should eagerly collect the values and update the controller + fakeVolumeDialogController.onShowVibrateHint() + fakeVolumeDialogController.getState() + + assertThat(fakeVolumeDialogController.state.ringerModeInternal) + .isEqualTo(RINGER_MODE_SILENT) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt index 7d5559933cd8..12885a83a70b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt @@ -25,14 +25,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.plugins.fakeVolumeDialogController import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -43,8 +42,7 @@ import org.junit.runner.RunWith @TestableLooper.RunWithLooper class VolumeDialogRingerInteractorTest : SysuiTestCase() { - private val kosmos = testKosmos() - private val testScope = kosmos.testScope + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val controller = kosmos.fakeVolumeDialogController private lateinit var underTest: VolumeDialogRingerInteractor @@ -57,13 +55,11 @@ class VolumeDialogRingerInteractorTest : SysuiTestCase() { @Test fun setRingerMode_normal() = - testScope.runTest { - runCurrent() + kosmos.runTest { val ringerModel by collectLastValue(underTest.ringerModel) underTest.setRingerMode(RingerMode(RINGER_MODE_NORMAL)) controller.getState() - runCurrent() assertThat(ringerModel).isNotNull() assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_NORMAL)) @@ -71,13 +67,11 @@ class VolumeDialogRingerInteractorTest : SysuiTestCase() { @Test fun setRingerMode_silent() = - testScope.runTest { - runCurrent() + kosmos.runTest { val ringerModel by collectLastValue(underTest.ringerModel) underTest.setRingerMode(RingerMode(RINGER_MODE_SILENT)) controller.getState() - runCurrent() assertThat(ringerModel).isNotNull() assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_SILENT)) @@ -85,13 +79,11 @@ class VolumeDialogRingerInteractorTest : SysuiTestCase() { @Test fun setRingerMode_vibrate() = - testScope.runTest { - runCurrent() + kosmos.runTest { val ringerModel by collectLastValue(underTest.ringerModel) underTest.setRingerMode(RingerMode(RINGER_MODE_VIBRATE)) controller.getState() - runCurrent() assertThat(ringerModel).isNotNull() assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_VIBRATE)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt index 799ca4a49038..0a50722d8fed 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.volume.dialog.sliders.domain.interactor import android.app.ActivityManager import android.testing.TestableLooper -import android.view.MotionEvent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -29,6 +28,7 @@ import com.android.systemui.testKosmos import com.android.systemui.volume.Events import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel +import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlin.time.Duration.Companion.seconds @@ -73,16 +73,7 @@ class VolumeDialogSliderInputEventsInteractorTest : SysuiTestCase() { assertThat(dialogVisibility) .isInstanceOf(VolumeDialogVisibilityModel.Visible::class.java) - underTest.onTouchEvent( - MotionEvent.obtain( - /* downTime = */ 0, - /* eventTime = */ 0, - /* action = */ 0, - /* x = */ 0f, - /* y = */ 0f, - /* metaState = */ 0, - ) - ) + underTest.onTouchEvent(SliderInputEvent.Touch.Start(0f, 0f)) advanceTimeBy(volumeDialogTimeout / 2) assertThat(dialogVisibility) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt index 2985053f56d5..d6343c840d9b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt @@ -26,4 +26,5 @@ class FakeWallpaperRepository : WallpaperRepository { override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null) override val wallpaperSupportsAmbientMode = flowOf(false) override var rootView: View? = null + override val shouldSendFocalArea = MutableStateFlow(false) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt index 03753d9aa884..115edd0d3bb0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt @@ -67,7 +67,6 @@ class WallpaperRepositoryImplTest : SysuiTestCase() { fakeBroadcastDispatcher, userRepository, keyguardRepository, - keyguardClockRepository, wallpaperManager, context, ) @@ -252,7 +251,7 @@ class WallpaperRepositoryImplTest : SysuiTestCase() { @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS) fun shouldSendNotificationLayout_setMagicPortraitWallpaper_launchSendLayoutJob() = testScope.runTest { - val latest by collectLastValue(underTest.shouldSendNotificationLayout) + val latest by collectLastValue(underTest.shouldSendFocalArea) val magicPortraitWallpaper = mock<WallpaperInfo>().apply { whenever(this.component) @@ -273,7 +272,7 @@ class WallpaperRepositoryImplTest : SysuiTestCase() { @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS) fun shouldSendNotificationLayout_setNotMagicPortraitWallpaper_cancelSendLayoutJob() = testScope.runTest { - val latest by collectLastValue(underTest.shouldSendNotificationLayout) + val latest by collectLastValue(underTest.shouldSendFocalArea) val magicPortraitWallpaper = MAGIC_PORTRAIT_WP whenever(wallpaperManager.getWallpaperInfoForUser(any())) .thenReturn(magicPortraitWallpaper) diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 433c0a71008d..e7d6b2fe08f4 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -159,6 +159,17 @@ <item name="android:shadowRadius">?attr/shadowRadius</item> </style> + <style name="TextAppearance.Keyguard.BottomArea.DoubleShadow"> + <item name="keyShadowBlur">0.5dp</item> + <item name="keyShadowOffsetX">0.5dp</item> + <item name="keyShadowOffsetY">0.5dp</item> + <item name="keyShadowAlpha">0.8</item> + <item name="ambientShadowBlur">0.5dp</item> + <item name="ambientShadowOffsetX">0.5dp</item> + <item name="ambientShadowOffsetY">0.5dp</item> + <item name="ambientShadowAlpha">0.6</item> + </style> + <style name="TextAppearance.Keyguard.BottomArea.Button"> <item name="android:shadowRadius">0</item> </style> diff --git a/packages/SystemUI/res/layout/promoted_notification_info.xml b/packages/SystemUI/res/layout/promoted_notification_info.xml new file mode 100644 index 000000000000..5d170a98a806 --- /dev/null +++ b/packages/SystemUI/res/layout/promoted_notification_info.xml @@ -0,0 +1,387 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2024, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<com.android.systemui.statusbar.notification.row.PromotedNotificationInfo + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:id="@+id/notification_guts" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:focusable="true" + android:clipChildren="false" + android:clipToPadding="true" + android:orientation="vertical" + android:paddingStart="@dimen/notification_shade_content_margin_horizontal"> + + <!-- Package Info --> + <LinearLayout + android:id="@+id/header" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:clipChildren="false" + android:paddingTop="@dimen/notification_guts_header_top_padding" + android:clipToPadding="true"> + <ImageView + android:id="@+id/pkg_icon" + android:layout_width="@dimen/notification_guts_conversation_icon_size" + android:layout_height="@dimen/notification_guts_conversation_icon_size" + android:layout_centerVertical="true" + android:layout_alignParentStart="true" + android:layout_marginEnd="15dp" /> + <LinearLayout + android:id="@+id/names" + android:layout_weight="1" + android:layout_width="0dp" + android:orientation="vertical" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_guts_conversation_icon_size" + android:layout_centerVertical="true" + android:gravity="center_vertical" + android:layout_alignEnd="@id/pkg_icon" + android:layout_toEndOf="@id/pkg_icon"> + <TextView + android:id="@+id/channel_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textDirection="locale" + style="@style/TextAppearance.NotificationImportanceChannel"/> + <TextView + android:id="@+id/group_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textDirection="locale" + android:ellipsize="end" + style="@style/TextAppearance.NotificationImportanceChannelGroup"/> + <TextView + android:id="@+id/pkg_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/TextAppearance.NotificationImportanceApp" + android:ellipsize="end" + android:textDirection="locale" + android:maxLines="1"/> + <TextView + android:id="@+id/delegate_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:ellipsize="end" + android:textDirection="locale" + android:text="@string/notification_delegate_header" + android:maxLines="1" /> + + </LinearLayout> + + <!-- end aligned fields --> + <!-- Optional link to app. Only appears if the channel is not disabled and the app +asked for it --> + <ImageButton + android:id="@+id/app_settings" + android:layout_width="@dimen/notification_importance_toggle_size" + android:layout_height="@dimen/notification_importance_toggle_size" + android:layout_centerVertical="true" + android:visibility="gone" + android:background="@drawable/ripple_drawable" + android:contentDescription="@string/notification_app_settings" + android:src="@drawable/ic_info" + android:layout_toStartOf="@id/info" + android:tint="@androidprv:color/materialColorPrimary"/> + <ImageButton + android:id="@+id/info" + android:layout_width="@dimen/notification_importance_toggle_size" + android:layout_height="@dimen/notification_importance_toggle_size" + android:layout_centerVertical="true" + android:contentDescription="@string/notification_more_settings" + android:background="@drawable/ripple_drawable_20dp" + android:src="@drawable/ic_settings" + android:tint="@androidprv:color/materialColorPrimary" + android:layout_alignParentEnd="true" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/inline_controls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingEnd="@dimen/notification_shade_content_margin_horizontal" + android:layout_marginTop="@dimen/notification_guts_option_vertical_padding" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical"> + + <!-- Non configurable app/channel text. appears instead of @+id/interruptiveness_settings--> + <TextView + android:id="@+id/non_configurable_text" + android:text="@string/notification_unblockable_desc" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@*android:style/TextAppearance.DeviceDefault.Notification" /> + + <!-- Non configurable app/channel text. appears instead of @+id/interruptiveness_settings--> + <TextView + android:id="@+id/non_configurable_call_text" + android:text="@string/notification_unblockable_call_desc" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@*android:style/TextAppearance.DeviceDefault.Notification" /> + + <!-- Non configurable multichannel text. appears instead of @+id/interruptiveness_settings--> + <TextView + android:id="@+id/non_configurable_multichannel_text" + android:text="@string/notification_multichannel_desc" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@*android:style/TextAppearance.DeviceDefault.Notification" /> + + <LinearLayout + android:id="@+id/interruptiveness_settings" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="vertical"> + <com.android.systemui.statusbar.notification.row.ButtonLinearLayout + android:id="@+id/automatic" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/notification_importance_button_separation" + android:padding="@dimen/notification_importance_button_padding" + android:clickable="true" + android:focusable="true" + android:background="@drawable/notification_guts_priority_button_bg" + android:orientation="vertical" + android:visibility="gone"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center" + > + <ImageView + android:id="@+id/automatic_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_notifications_automatic" + android:background="@android:color/transparent" + android:tint="@color/notification_guts_priority_contents" + android:clickable="false" + android:focusable="false"/> + <TextView + android:id="@+id/automatic_label" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_importance_drawable_padding" + android:layout_weight="1" + android:ellipsize="end" + android:maxLines="1" + android:clickable="false" + android:focusable="false" + android:textAppearance="@style/TextAppearance.NotificationImportanceButton" + android:text="@string/notification_automatic_title"/> + </LinearLayout> + <TextView + android:id="@+id/automatic_summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_importance_button_description_top_margin" + android:visibility="gone" + android:text="@string/notification_channel_summary_automatic" + android:clickable="false" + android:focusable="false" + android:ellipsize="end" + android:maxLines="2" + android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> + </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> + + <com.android.systemui.statusbar.notification.row.ButtonLinearLayout + android:id="@+id/alert" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="@dimen/notification_importance_button_padding" + android:clickable="true" + android:focusable="true" + android:background="@drawable/notification_guts_priority_button_bg" + android:orientation="vertical"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center" + > + <ImageView + android:id="@+id/alert_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_notifications_alert" + android:background="@android:color/transparent" + android:tint="@color/notification_guts_priority_contents" + android:clickable="false" + android:focusable="false"/> + <TextView + android:id="@+id/alert_label" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_importance_drawable_padding" + android:layout_weight="1" + android:ellipsize="end" + android:maxLines="1" + android:clickable="false" + android:focusable="false" + android:textAppearance="@style/TextAppearance.NotificationImportanceButton" + android:text="@string/notification_alert_title"/> + </LinearLayout> + <TextView + android:id="@+id/alert_summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_importance_button_description_top_margin" + android:visibility="gone" + android:text="@string/notification_channel_summary_default" + android:clickable="false" + android:focusable="false" + android:ellipsize="end" + android:maxLines="2" + android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> + </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> + + <com.android.systemui.statusbar.notification.row.ButtonLinearLayout + android:id="@+id/silence" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_importance_button_separation" + android:padding="@dimen/notification_importance_button_padding" + android:clickable="true" + android:focusable="true" + android:background="@drawable/notification_guts_priority_button_bg" + android:orientation="vertical"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center" + > + <ImageView + android:id="@+id/silence_icon" + android:src="@drawable/ic_notifications_silence" + android:background="@android:color/transparent" + android:tint="@color/notification_guts_priority_contents" + android:layout_gravity="center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:clickable="false" + android:focusable="false"/> + <TextView + android:id="@+id/silence_label" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + android:clickable="false" + android:focusable="false" + android:layout_toEndOf="@id/silence_icon" + android:layout_marginStart="@dimen/notification_importance_drawable_padding" + android:textAppearance="@style/TextAppearance.NotificationImportanceButton" + android:text="@string/notification_silence_title"/> + </LinearLayout> + <TextView + android:id="@+id/silence_summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_importance_button_description_top_margin" + android:visibility="gone" + android:text="@string/notification_channel_summary_low" + android:clickable="false" + android:focusable="false" + android:ellipsize="end" + android:maxLines="2" + android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> + </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> + + </LinearLayout> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="60dp" + android:gravity="center_vertical" + android:paddingStart="4dp" + android:paddingEnd="4dp" + > + <TextView + android:id="@+id/promoted_demote" + android:text="@string/notification_inline_disable_promotion" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:gravity="start|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="200dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + <TextView + android:id="@+id/promoted_dismiss" + android:text="@string/notification_inline_dismiss" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:gravity="end|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="125dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/bottom_buttons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="60dp" + android:gravity="center_vertical" + android:paddingStart="4dp" + android:paddingEnd="4dp" + > + <TextView + android:id="@+id/turn_off_notifications" + android:text="@string/inline_turn_off_notifications" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:gravity="start|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="200dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + <TextView + android:id="@+id/done" + android:text="@string/inline_ok_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:gravity="end|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="125dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + </RelativeLayout> + </LinearLayout> +</com.android.systemui.statusbar.notification.row.PromotedNotificationInfo> diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml index c5f468e731f5..2628f4991b49 100644 --- a/packages/SystemUI/res/layout/volume_dialog_slider.xml +++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml @@ -18,14 +18,10 @@ android:layout_height="match_parent" android:maxHeight="@dimen/volume_dialog_slider_height"> - <com.google.android.material.slider.Slider + <androidx.compose.ui.platform.ComposeView android:id="@+id/volume_dialog_slider" - style="@style/SystemUI.Material3.Slider.Volume" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" - android:layout_marginTop="-20dp" - android:layout_marginBottom="-20dp" - android:orientation="vertical" - android:theme="@style/Theme.Material3.DayNight" /> + android:orientation="vertical" /> </FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-xlarge-land/config.xml b/packages/SystemUI/res/values-xlarge-land/config.xml index 5e4304e1c13a..6d8b64ade259 100644 --- a/packages/SystemUI/res/values-xlarge-land/config.xml +++ b/packages/SystemUI/res/values-xlarge-land/config.xml @@ -16,4 +16,5 @@ <resources> <item name="shortcut_helper_screen_width_fraction" format="float" type="dimen">0.8</item> + <bool name="center_align_magic_portrait_shape">true</bool> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 940e87d3d163..68e33f27aefa 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -1104,7 +1104,10 @@ --> <bool name="config_userSwitchingMustGoThroughLoginScreen">false</bool> - <!-- The dream component used when the device is low light environment. --> <string translatable="false" name="config_lowLightDreamComponent"/> + + <!--Whether we should position magic portrait shape effects in the center of lockscreen + it's false by default, and only be true in tablet landscape --> + <bool name="center_align_magic_portrait_shape">false</bool> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 1e52e9f135cb..64367ef79856 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2083,6 +2083,12 @@ <!-- [CHAR LIMIT=80] Text shown in feedback button in notification guts for a bundled notification --> <string name="notification_guts_bundle_feedback" translatable="false">Provide Bundle Feedback</string> + <!-- [CHAR LIMIT=30] Text shown in button used to dismiss this single notification. --> + <string name="notification_inline_dismiss">Dismiss</string> + + <!-- [CHAR LIMIT=30] Text shown in button used to prevent app from showing Live Updates, for this notification and all future ones --> + <string name="notification_inline_disable_promotion">Don\'t show again</string> + <!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. --> <string name="notification_unblockable_desc">These notifications can\'t be modified.</string> @@ -2310,9 +2316,6 @@ <string name="group_system_lock_screen">Lock screen</string> <!-- User visible title for the keyboard shortcut that pulls up Notes app for quick memo. [CHAR LIMIT=70] --> <string name="group_system_quick_memo">Take a note</string> - <!-- TODO(b/383734125): make it translatable once string is finalized by UXW.--> - <!-- User visible title for the keyboard shortcut that toggles Voice Access. [CHAR LIMIT=70] --> - <string name="group_system_toggle_voice_access" translatable="false">Toggle Voice Access</string> <!-- User visible title for the multitasking keyboard shortcuts list. [CHAR LIMIT=70] --> <string name="keyboard_shortcut_group_system_multitasking">Multitasking</string> @@ -2371,6 +2374,17 @@ <!-- User visible title for the keyboard shortcut that takes the user to the maps app. [CHAR LIMIT=70] --> <string name="keyboard_shortcut_group_applications_maps">Maps</string> + <!-- User visible title for the keyboard shortcut that toggles bounce keys. [CHAR LIMIT=70]--> + <string name="group_accessibility_toggle_bounce_keys">Toggle bounce keys</string> + <!-- User visible title for the keyboard shortcut that toggles mouse keys. [CHAR LIMIT=70]--> + <string name="group_accessibility_toggle_mouse_keys">Toggle mouse keys</string> + <!-- User visible title for the keyboard shortcut that toggles sticky keys. [CHAR LIMIT=70]--> + <string name="group_accessibility_toggle_sticky_keys">Toggle sticky keys</string> + <!-- User visible title for the keyboard shortcut that toggles slow keys. [CHAR LIMIT=70]--> + <string name="group_accessibility_toggle_slow_keys">Toggle slow keys</string> + <!-- User visible title for the keyboard shortcut that toggles Voice Access. [CHAR LIMIT=70] --> + <string name="group_accessibility_toggle_voice_access">Toggle Voice Access</string> + <!-- SysUI Tuner: Label for screen about do not disturb settings [CHAR LIMIT=60] --> <string name="volume_and_do_not_disturb">Do Not Disturb</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index b0d9bed05e27..c95c6dd89353 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -565,16 +565,6 @@ <item name="android:windowNoTitle">true</item> </style> - <style name="SystemUI.Material3.Slider.Volume"> - <item name="trackHeight">40dp</item> - <item name="thumbHeight">52dp</item> - <item name="trackCornerSize">12dp</item> - <item name="trackInsideCornerSize">2dp</item> - <item name="trackStopIndicatorSize">6dp</item> - <item name="trackIconSize">20dp</item> - <item name="labelBehavior">gone</item> - </style> - <style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider"> <item name="labelStyle">@style/Widget.Material3.Slider.Label</item> <item name="thumbColor">@color/thumb_color</item> diff --git a/packages/SystemUI/res/xml/gradient_color_wallpaper.xml b/packages/SystemUI/res/xml/gradient_color_wallpaper.xml new file mode 100644 index 000000000000..f453a4450750 --- /dev/null +++ b/packages/SystemUI/res/xml/gradient_color_wallpaper.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<wallpaper + xmlns:android="http://schemas.android.com/apk/res/android" + android:showMetadataInPreview="false" + android:supportsMultipleDisplays="true" />
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 41ad4373455e..818e39800b0c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -18,9 +18,10 @@ package com.android.systemui.shared.recents.utilities; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; import android.annotation.TargetApi; +import android.app.StatusBarManager.NavigationHint; import android.content.Context; import android.content.res.Resources; import android.graphics.Color; @@ -103,10 +104,15 @@ public class Utilities { } /** - * @return updated set of flags from InputMethodService based off {@param oldHints} - * Leaves original hints unmodified + * Gets the updated navigation icon hints, based on the current ones and the given IME state. + * + * @param oldHints current navigation icon hints. + * @param backDisposition the IME back disposition mode. + * @param imeShown whether the IME is currently visible. + * @param showImeSwitcher whether the IME Switcher button should be shown. */ - public static int calculateBackDispositionHints(int oldHints, + @NavigationHint + public static int calculateNavigationIconHints(@NavigationHint int oldHints, @BackDispositionMode int backDisposition, boolean imeShown, boolean showImeSwitcher) { int hints = oldHints; switch (backDisposition) { @@ -129,9 +135,9 @@ public class Utilities { hints &= ~NAVIGATION_HINT_IME_SHOWN; } if (showImeSwitcher) { - hints |= NAVIGATION_HINT_IME_SWITCHER_SHOWN; + hints |= NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; } else { - hints &= ~NAVIGATION_HINT_IME_SWITCHER_SHOWN; + hints &= ~NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; } return hints; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt index bd20777c7102..e928525afc14 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt @@ -16,6 +16,7 @@ package com.android.systemui.shared.shadow import android.content.Context +import android.content.res.TypedArray import android.graphics.Canvas import android.graphics.drawable.Drawable import android.util.AttributeSet @@ -31,19 +32,23 @@ constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, - defStyleRes: Int = 0 + defStyleRes: Int = 0, ) : TextView(context, attrs, defStyleAttr, defStyleRes) { - private val mKeyShadowInfo: ShadowInfo - private val mAmbientShadowInfo: ShadowInfo + private lateinit var mKeyShadowInfo: ShadowInfo + private lateinit var mAmbientShadowInfo: ShadowInfo init { - val attributes = + updateShadowDrawables( context.obtainStyledAttributes( attrs, R.styleable.DoubleShadowTextView, defStyleAttr, - defStyleRes + defStyleRes, ) + ) + } + + private fun updateShadowDrawables(attributes: TypedArray) { val drawableSize: Int val drawableInsetSize: Int try { @@ -70,17 +75,17 @@ constructor( ambientShadowBlur, ambientShadowOffsetX, ambientShadowOffsetY, - ambientShadowAlpha + ambientShadowAlpha, ) drawableSize = attributes.getDimensionPixelSize( R.styleable.DoubleShadowTextView_drawableIconSize, - 0 + 0, ) drawableInsetSize = attributes.getDimensionPixelSize( R.styleable.DoubleShadowTextView_drawableIconInsetSize, - 0 + 0, ) } finally { attributes.recycle() @@ -95,12 +100,19 @@ constructor( mAmbientShadowInfo, drawable, drawableSize, - drawableInsetSize + drawableInsetSize, ) } setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3]) } + override fun setTextAppearance(resId: Int) { + super.setTextAppearance(resId) + updateShadowDrawables( + context.obtainStyledAttributes(resId, R.styleable.DoubleShadowTextView) + ) + } + public override fun onDraw(canvas: Canvas) { applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 6f13d637d5c5..f87d9b05d795 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -98,12 +98,12 @@ public class QuickStepContract { public static final long SYSUI_STATE_ONE_HANDED_ACTIVE = 1L << 16; // Allow system gesture no matter the system bar(s) is visible or not public static final long SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1L << 17; - // The IME is showing + // The IME is visible. public static final long SYSUI_STATE_IME_SHOWING = 1L << 18; // The window magnification is overlapped with system gesture insets at the bottom. public static final long SYSUI_STATE_MAGNIFICATION_OVERLAP = 1L << 19; - // ImeSwitcher is showing - public static final long SYSUI_STATE_IME_SWITCHER_SHOWING = 1L << 20; + // The IME Switcher button is visible. + public static final long SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING = 1L << 20; // Device dozing/AOD state public static final long SYSUI_STATE_DEVICE_DOZING = 1L << 21; // The home feature is disabled (either by SUW/SysUI/device policy) @@ -170,7 +170,7 @@ public class QuickStepContract { SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, SYSUI_STATE_IME_SHOWING, SYSUI_STATE_MAGNIFICATION_OVERLAP, - SYSUI_STATE_IME_SWITCHER_SHOWING, + SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING, SYSUI_STATE_DEVICE_DOZING, SYSUI_STATE_BACK_DISABLED, SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED, @@ -250,8 +250,8 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0) { str.add("magnification_overlap"); } - if ((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0) { - str.add("ime_switcher_showing"); + if ((flags & SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING) != 0) { + str.add("ime_switcher_button_visible"); } if ((flags & SYSUI_STATE_DEVICE_DOZING) != 0) { str.add("device_dozing"); diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt index 6a42cdc876ca..8e4c9349c604 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt @@ -39,10 +39,14 @@ import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFO import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS -import com.android.hardware.input.Flags.enableVoiceAccessKeyGestures +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.Accessibility import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System @@ -65,7 +69,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) { KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to System, KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to System, KEY_GESTURE_TYPE_ALL_APPS to System, - KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to System, // Multitasking Category KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to MultiTasking, @@ -82,6 +85,13 @@ class InputGestureMaps @Inject constructor(private val context: Context) { // App Category KEY_GESTURE_TYPE_LAUNCH_APPLICATION to AppCategories, + + // Accessibility Category + KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS to Accessibility, + KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS to Accessibility, + KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS to Accessibility, + KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS to Accessibility, + KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to Accessibility, ) val gestureToInternalKeyboardShortcutGroupLabelResIdMap = @@ -103,7 +113,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) { KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to R.string.shortcut_helper_category_system_apps, KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to R.string.shortcut_helper_category_system_apps, - KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to R.string.shortcut_helper_category_system_apps, // Multitasking Category KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to @@ -128,6 +137,13 @@ class InputGestureMaps @Inject constructor(private val context: Context) { // App Category KEY_GESTURE_TYPE_LAUNCH_APPLICATION to R.string.keyboard_shortcut_group_applications, + + // Accessibility Category + KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS to R.string.shortcutHelper_category_accessibility, + KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS to R.string.shortcutHelper_category_accessibility, + KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS to R.string.shortcutHelper_category_accessibility, + KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS to R.string.shortcutHelper_category_accessibility, + KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to R.string.shortcutHelper_category_accessibility, ) /** @@ -152,7 +168,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) { KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to R.string.group_system_access_google_assistant, KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to R.string.group_system_access_google_assistant, - KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to R.string.group_system_toggle_voice_access, // Multitasking Category KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to R.string.group_system_cycle_forward, @@ -169,6 +184,14 @@ class InputGestureMaps @Inject constructor(private val context: Context) { R.string.system_desktop_mode_toggle_maximize_window, KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY to R.string.system_multitasking_move_to_next_display, + + // Accessibility Category + KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS to R.string.group_accessibility_toggle_bounce_keys, + KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS to R.string.group_accessibility_toggle_mouse_keys, + KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS to R.string.group_accessibility_toggle_sticky_keys, + KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS to R.string.group_accessibility_toggle_slow_keys, + KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to + R.string.group_accessibility_toggle_voice_access, ) val shortcutLabelToKeyGestureTypeMap: Map<String, Int> diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt index d92c45591522..0c98f81e7cef 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt @@ -17,9 +17,20 @@ package com.android.systemui.keyboard.shortcut.data.source import android.content.res.Resources +import android.hardware.input.InputSettings +import android.view.KeyEvent.KEYCODE_3 +import android.view.KeyEvent.KEYCODE_4 +import android.view.KeyEvent.KEYCODE_5 +import android.view.KeyEvent.KEYCODE_6 +import android.view.KeyEvent.KEYCODE_V +import android.view.KeyEvent.META_ALT_ON +import android.view.KeyEvent.META_META_ON import android.view.KeyboardShortcutGroup import android.view.KeyboardShortcutInfo +import com.android.hardware.input.Flags.enableVoiceAccessKeyGestures +import com.android.hardware.input.Flags.keyboardA11yShortcutControl import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo import com.android.systemui.res.R import javax.inject.Inject @@ -33,5 +44,68 @@ class AccessibilityShortcutsSource @Inject constructor(@Main private val resourc ) ) - private fun accessibilityShortcuts() = listOf<KeyboardShortcutInfo>() + private fun accessibilityShortcuts(): List<KeyboardShortcutInfo> { + val shortcuts = mutableListOf<KeyboardShortcutInfo>() + + if (keyboardA11yShortcutControl()) { + if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) { + shortcuts.add( + // Toggle bounce keys: + // - Meta + Alt + 3 + shortcutInfo( + resources.getString(R.string.group_accessibility_toggle_bounce_keys) + ) { + command(META_META_ON or META_ALT_ON, KEYCODE_3) + } + ) + } + if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) { + shortcuts.add( + // Toggle mouse keys: + // - Meta + Alt + 4 + shortcutInfo( + resources.getString(R.string.group_accessibility_toggle_mouse_keys) + ) { + command(META_META_ON or META_ALT_ON, KEYCODE_4) + } + ) + } + if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) { + shortcuts.add( + // Toggle sticky keys: + // - Meta + Alt + 5 + shortcutInfo( + resources.getString(R.string.group_accessibility_toggle_sticky_keys) + ) { + command(META_META_ON or META_ALT_ON, KEYCODE_5) + } + ) + } + if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) { + shortcuts.add( + // Toggle slow keys: + // - Meta + Alt + 6 + shortcutInfo( + resources.getString(R.string.group_accessibility_toggle_slow_keys) + ) { + command(META_META_ON or META_ALT_ON, KEYCODE_6) + } + ) + } + } + + if (enableVoiceAccessKeyGestures()) { + shortcuts.add( + // Toggle voice access: + // - Meta + Alt + V + shortcutInfo( + resources.getString(R.string.group_accessibility_toggle_voice_access) + ) { + command(META_META_ON or META_ALT_ON, KEYCODE_V) + } + ) + } + + return shortcuts + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt index c3c9df97a682..5060abdda247 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt @@ -32,14 +32,12 @@ import android.view.KeyEvent.KEYCODE_RECENT_APPS import android.view.KeyEvent.KEYCODE_S import android.view.KeyEvent.KEYCODE_SLASH import android.view.KeyEvent.KEYCODE_TAB -import android.view.KeyEvent.KEYCODE_V import android.view.KeyEvent.META_ALT_ON import android.view.KeyEvent.META_CTRL_ON import android.view.KeyEvent.META_META_ON import android.view.KeyEvent.META_SHIFT_ON import android.view.KeyboardShortcutGroup import android.view.KeyboardShortcutInfo -import com.android.hardware.input.Flags.enableVoiceAccessKeyGestures import com.android.systemui.Flags.shortcutHelperKeyGlyph import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo @@ -120,8 +118,8 @@ constructor(@Main private val resources: Resources, private val inputManager: In return shortcuts } - private fun systemControlsShortcuts(): List<KeyboardShortcutInfo> { - val shortcuts = mutableListOf( + private fun systemControlsShortcuts() = + listOf( // Access list of all apps and search (i.e. Search/Launcher): // - Meta shortcutInfo(resources.getString(R.string.group_system_access_all_apps_search)) { @@ -178,19 +176,6 @@ constructor(@Main private val resources: Resources, private val inputManager: In }, ) - if (enableVoiceAccessKeyGestures()) { - shortcuts.add( - // Toggle voice access: - // - Meta + Alt + V - shortcutInfo(resources.getString(R.string.group_system_toggle_voice_access)) { - command(META_META_ON or META_ALT_ON, KEYCODE_V) - } - ) - } - - return shortcuts - } - private fun systemAppsShortcuts() = listOf( // Pull up Notes app for quick memo: diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt index f1945e657d52..bd3d46d09f5e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt @@ -108,22 +108,33 @@ constructor( private fun setDialogProperties(dialog: SystemUIDialog, uiState: ShortcutCustomizationUiState) { dialog.setOnDismissListener { viewModel.onDialogDismissed() } - dialog.setTitle( - resources.getString( - when (uiState) { - is AddShortcutDialog -> - R.string.shortcut_customize_mode_add_shortcut_description - is DeleteShortcutDialog -> - R.string.shortcut_customize_mode_remove_shortcut_description - else -> R.string.shortcut_customize_mode_reset_shortcut_description - } - ) - ) + dialog.setTitle("${getDialogTitle(uiState)}. ${getDialogDescription(uiState)}") // By default, apps cannot intercept action key. The system always handles it. This // flag is needed to enable customisation dialog window to intercept action key dialog.window?.addPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) } + private fun getDialogTitle(uiState: ShortcutCustomizationUiState): String { + return when (uiState) { + is AddShortcutDialog -> uiState.shortcutLabel + is DeleteShortcutDialog -> + resources.getString(R.string.shortcut_customize_mode_remove_shortcut_dialog_title) + else -> + resources.getString(R.string.shortcut_customize_mode_reset_shortcut_dialog_title) + } + } + + private fun getDialogDescription(uiState: ShortcutCustomizationUiState): String { + return resources.getString( + when (uiState) { + is AddShortcutDialog -> R.string.shortcut_customize_mode_add_shortcut_description + is DeleteShortcutDialog -> + R.string.shortcut_customize_mode_remove_shortcut_description + else -> R.string.shortcut_customize_mode_reset_shortcut_description + } + ) + } + @AssistedFactory interface Factory { fun create(): ShortcutCustomizationDialogStarter diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 5e0768a2fd24..f37e7685f21c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -27,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder @@ -79,6 +80,7 @@ constructor( private val keyguardClockViewModel: KeyguardClockViewModel, private val smartspaceViewModel: KeyguardSmartspaceViewModel, private val clockInteractor: KeyguardClockInteractor, + private val wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor, private val keyguardViewMediator: KeyguardViewMediator, private val deviceEntryUnlockTrackerViewBinder: Optional<DeviceEntryUnlockTrackerViewBinder>, private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, @@ -139,6 +141,7 @@ constructor( screenOffAnimationController, shadeInteractor, clockInteractor, + wallpaperFocalAreaInteractor, keyguardClockViewModel, interactionJankMonitor, deviceEntryHapticsInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index a39982dd31e7..11477fb6cad1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point +import android.graphics.RectF import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardUpdateMonitor @@ -258,6 +259,8 @@ interface KeyguardRepository { val notificationStackAbsoluteBottom: StateFlow<Float> + val wallpaperFocalAreaBounds: StateFlow<RectF> + /** * Returns `true` if the keyguard is showing; `false` otherwise. * @@ -329,6 +332,8 @@ interface KeyguardRepository { * this value */ fun setNotificationStackAbsoluteBottom(bottom: Float) + + fun setWallpaperFocalAreaBounds(bounds: RectF) } /** Encapsulates application state for the keyguard. */ @@ -380,7 +385,6 @@ constructor( override val onCameraLaunchDetected = MutableStateFlow(CameraLaunchSourceModel()) override val panelAlpha: MutableStateFlow<Float> = MutableStateFlow(1f) - override val topClippingBounds = MutableStateFlow<Int?>(null) override val isKeyguardShowing: MutableStateFlow<Boolean> = @@ -622,6 +626,10 @@ constructor( private val _notificationStackAbsoluteBottom = MutableStateFlow(0F) override val notificationStackAbsoluteBottom = _notificationStackAbsoluteBottom.asStateFlow() + private val _wallpaperFocalAreaBounds = MutableStateFlow(RectF(0F, 0F, 0F, 0F)) + override val wallpaperFocalAreaBounds: StateFlow<RectF> = + _wallpaperFocalAreaBounds.asStateFlow() + init { val callback = object : KeyguardStateController.Callback { @@ -700,6 +708,10 @@ constructor( _notificationStackAbsoluteBottom.value = bottom } + override fun setWallpaperFocalAreaBounds(bounds: RectF) { + _wallpaperFocalAreaBounds.value = bounds + } + private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel { return when (state) { DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt new file mode 100644 index 000000000000..934afe248a36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.content.Context +import android.content.res.Resources +import android.graphics.RectF +import android.util.TypedValue +import com.android.app.animation.MathUtils.max +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardClockRepository +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.res.R +import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.wallpapers.data.repository.WallpaperRepository +import javax.inject.Inject +import kotlin.math.min +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn + +@SysUISingleton +class WallpaperFocalAreaInteractor +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + context: Context, + private val keyguardRepository: KeyguardRepository, + shadeRepository: ShadeRepository, + activeNotificationsInteractor: ActiveNotificationsInteractor, + keyguardClockRepository: KeyguardClockRepository, + wallpaperRepository: WallpaperRepository, +) { + // When there's notifications in splitshade, magic portrait shape effects should be left + // aligned in foldable + private val notificationInShadeWideLayout: Flow<Boolean> = + combine( + shadeRepository.isShadeLayoutWide, + activeNotificationsInteractor.areAnyNotificationsPresent, + ) { isShadeLayoutWide, areAnyNotificationsPresent: Boolean -> + when { + !isShadeLayoutWide -> false + !areAnyNotificationsPresent -> false + else -> true + } + } + + val shouldSendFocalArea = wallpaperRepository.shouldSendFocalArea + val wallpaperFocalAreaBounds: StateFlow<RectF?> = + combine( + shadeRepository.isShadeLayoutWide, + notificationInShadeWideLayout, + keyguardRepository.notificationStackAbsoluteBottom, + keyguardRepository.shortcutAbsoluteTop, + keyguardClockRepository.notificationDefaultTop, + ) { + isShadeLayoutWide, + notificationInShadeWideLayout, + notificationStackAbsoluteBottom, + shortcutAbsoluteTop, + notificationDefaultTop -> + // Wallpaper will be zoomed in with config_wallpaperMaxScale in lockscreen + // so we need to give a bounds taking this scale in consideration + val wallpaperZoomedInScale = getSystemWallpaperMaximumScale(context) + val screenBounds = + RectF( + 0F, + 0F, + context.resources.displayMetrics.widthPixels.toFloat(), + context.resources.displayMetrics.heightPixels.toFloat(), + ) + val scaledBounds = + RectF( + screenBounds.centerX() - screenBounds.width() / 2F / wallpaperZoomedInScale, + screenBounds.centerY() - + screenBounds.height() / 2F / wallpaperZoomedInScale, + screenBounds.centerX() + screenBounds.width() / 2F / wallpaperZoomedInScale, + screenBounds.centerY() + screenBounds.height() / 2F / wallpaperZoomedInScale, + ) + val maxFocalAreaWidth = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + FOCAL_AREA_MAX_WIDTH_DP.toFloat(), + context.resources.displayMetrics, + ) + val (left, right) = + // tablet landscape + if (context.resources.getBoolean(R.bool.center_align_magic_portrait_shape)) { + Pair( + scaledBounds.centerX() - maxFocalAreaWidth / 2F, + scaledBounds.centerX() + maxFocalAreaWidth / 2F, + ) + // unfold foldable landscape + } else if (isShadeLayoutWide) { + if (notificationInShadeWideLayout) { + Pair(scaledBounds.left, scaledBounds.centerX()) + } else { + Pair(scaledBounds.centerX(), scaledBounds.right) + } + // handheld / portrait + } else { + val focalAreaWidth = min(scaledBounds.width(), maxFocalAreaWidth) + Pair( + scaledBounds.centerX() - focalAreaWidth / 2F, + scaledBounds.centerX() + focalAreaWidth / 2F, + ) + } + val scaledBottomMargin = + (context.resources.displayMetrics.heightPixels - shortcutAbsoluteTop) / + wallpaperZoomedInScale + val top = + // tablet landscape + if (context.resources.getBoolean(R.bool.center_align_magic_portrait_shape)) { + // no strict constraints for top, use bottom margin to make it symmetric + // vertically + scaledBounds.top + scaledBottomMargin + } + // unfold foldable landscape + else if (isShadeLayoutWide) { + // For all landscape, we should use bottom of smartspace to constrain + scaledBounds.top + notificationDefaultTop / wallpaperZoomedInScale + // handheld / portrait + } else { + scaledBounds.top + + max(notificationDefaultTop, notificationStackAbsoluteBottom) / + wallpaperZoomedInScale + } + val bottom = scaledBounds.bottom - scaledBottomMargin + RectF(left, top, right, bottom) + } + .stateIn( + applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = null, + ) + + fun setWallpaperFocalAreaBounds(bounds: RectF) { + keyguardRepository.setWallpaperFocalAreaBounds(bounds) + } + + companion object { + fun getSystemWallpaperMaximumScale(context: Context): Float { + return context.resources.getFloat( + Resources.getSystem() + .getIdentifier( + /* name= */ "config_wallpaperMaxScale", + /* defType= */ "dimen", + /* defPackage= */ "android", + ) + ) + } + + // A max width for magic portrait shape effects bounds, to avoid it going too large + // in large screen portrait mode + const val FOCAL_AREA_MAX_WIDTH_DP = 500 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt index 92b49ed6156c..21c9b0b82b2d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt @@ -23,6 +23,7 @@ import android.widget.TextView import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.systemui.Flags import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R @@ -73,7 +74,6 @@ object KeyguardIndicationAreaBinder { disposables += view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { - launch("$TAG#viewModel.indicationAreaTranslationX") { viewModel.indicationAreaTranslationX.collect { translationX -> view.translationX = translationX @@ -119,6 +119,9 @@ object KeyguardIndicationAreaBinder { launch("$TAG#viewModel.configurationChange") { viewModel.configurationChange.collect { configurationBasedDimensions.value = loadFromResources(view) + if (Flags.indicationTextA11yFix()) { + indicationController.onConfigurationChanged() + } } } @@ -140,7 +143,7 @@ object KeyguardIndicationAreaBinder { view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding), indicationTextSizePx = view.resources.getDimensionPixelSize( - com.android.internal.R.dimen.text_size_small_material, + com.android.internal.R.dimen.text_size_small_material ), ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 6d270b219c81..d8bd4452f2a6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -54,6 +54,7 @@ import com.android.systemui.customization.R as customR import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters @@ -87,6 +88,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update @@ -105,6 +107,7 @@ object KeyguardRootViewBinder { screenOffAnimationController: ScreenOffAnimationController, shadeInteractor: ShadeInteractor, clockInteractor: KeyguardClockInteractor, + wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor, clockViewModel: KeyguardClockViewModel, interactionJankMonitor: InteractionJankMonitor?, deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?, @@ -309,12 +312,15 @@ object KeyguardRootViewBinder { .setTag(clockId) jankMonitor.begin(builder) } + TransitionState.CANCELED -> jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) + TransitionState.FINISHED -> { keyguardViewMediator?.maybeHandlePendingLock() jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD) } + TransitionState.RUNNING -> Unit } } @@ -378,6 +384,21 @@ object KeyguardRootViewBinder { } disposables += + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + if (viewModel.shouldSendFocalArea.value) { + launch { + wallpaperFocalAreaInteractor.wallpaperFocalAreaBounds + .filterNotNull() + .collect { + wallpaperFocalAreaInteractor.setWallpaperFocalAreaBounds(it) + } + } + } + } + } + + disposables += view.onLayoutChanged( OnLayoutChange( viewModel, @@ -523,6 +544,7 @@ object KeyguardRootViewBinder { View.INVISIBLE } } + else -> { if (isVisible.value) { CrossFadeHelper.fadeIn(this, animatorListener) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index a2107871a585..7605bdd3d7b5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -55,6 +55,7 @@ import com.android.systemui.customization.R as customR import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor import com.android.systemui.keyguard.shared.model.ClockSizeSetting import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder @@ -117,6 +118,7 @@ constructor( private val secureSettings: SecureSettings, private val defaultShortcutsSection: DefaultShortcutsSection, private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder, + private val wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor, ) { val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) private val width: Int = bundle.getInt(KEY_VIEW_WIDTH) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt index 3eb8522e0338..542fb9b46bef 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt @@ -23,10 +23,4 @@ data class BlurConfig(val minBlurRadiusPx: Float, val maxBlurRadiusPx: Float) { // No-op config that will be used by dagger of other SysUI variants which don't blur the // background surface. @Inject constructor() : this(0.0f, 0.0f) - - companion object { - // Blur the shade much lesser than the background surface so that the surface is - // distinguishable from the background. - @JvmStatic fun Float.maxBlurRadiusToNotificationPanelBlurRadius(): Float = this / 3.0f - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt index 733d7d71061e..b531c7fa49ec 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt @@ -24,7 +24,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCE import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.BlurConfig -import com.android.systemui.keyguard.ui.transitions.BlurConfig.Companion.maxBlurRadiusToNotificationPanelBlurRadius import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -89,9 +88,7 @@ constructor( shadeDependentFlows.transitionFlow( flowWhenShadeIsNotExpanded = emptyFlow(), flowWhenShadeIsExpanded = - transitionAnimation.immediatelyTransitionTo( - blurConfig.maxBlurRadiusPx.maxBlurRadiusToNotificationPanelBlurRadius() - ), + transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx), ) } else { emptyFlow<Float>() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index e51e05b8ab61..aa4293a201ac 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -28,6 +28,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor +import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING @@ -133,6 +134,7 @@ constructor( private val screenOffAnimationController: ScreenOffAnimationController, private val aodBurnInViewModel: AodBurnInViewModel, private val shadeInteractor: ShadeInteractor, + wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor, ) { val burnInLayerVisibility: Flow<Int> = keyguardTransitionInteractor.startedKeyguardTransitionStep @@ -362,6 +364,8 @@ constructor( initialValue = AnimatedValue.NotAnimating(false), ) + val shouldSendFocalArea = wallpaperFocalAreaInteractor.shouldSendFocalArea + fun onNotificationContainerBoundsChanged(top: Float, bottom: Float, animate: Boolean = false) { keyguardInteractor.setNotificationContainerBounds( NotificationContainerBounds(top = top, bottom = bottom, isAnimated = animate) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt index 44c4c8723dcb..89dcbf6aa52b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt @@ -24,7 +24,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.BlurConfig -import com.android.systemui.keyguard.ui.transitions.BlurConfig.Companion.maxBlurRadiusToNotificationPanelBlurRadius import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -88,9 +87,7 @@ constructor( shadeDependentFlows.transitionFlow( flowWhenShadeIsNotExpanded = emptyFlow(), flowWhenShadeIsExpanded = - transitionAnimation.immediatelyTransitionTo( - blurConfig.maxBlurRadiusPx.maxBlurRadiusToNotificationPanelBlurRadius() - ), + transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx), ) } else { emptyFlow() diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index 173a964cc5d3..40197b2daf49 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -536,8 +536,10 @@ public final class NavBarHelper implements } /** - * @return Whether the IME is shown on top of the screen given the {@code vis} flag of - * {@link InputMethodService} and the keyguard states. + * Checks whether the IME is shown on top of the screen, based on the given IME window + * visibility flags, and the current keyguard state. + * + * @param vis the IME window visibility. */ public boolean isImeShown(@ImeWindowVisibility int vis) { View shadeWindowView = mNotificationShadeWindowController.getWindowRootView(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 05d8bff2ceb6..ee24b3120966 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -18,7 +18,7 @@ package com.android.systemui.navigationbar; import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -31,12 +31,13 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import android.app.StatusBarManager; +import android.app.StatusBarManager.NavigationHint; import android.app.StatusBarManager.WindowVisibleState; import android.content.Context; import android.graphics.Rect; @@ -111,6 +112,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private TaskStackChangeListeners mTaskStackChangeListeners; private Optional<Pip> mPipOptional; private int mDefaultDisplayId; + @NavigationHint private int mNavigationIconHints; private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater = new NavBarHelper.NavbarTaskbarStateUpdater() { @@ -362,8 +364,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, .setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable) .setFlag(SYSUI_STATE_IME_SHOWING, (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0) - .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING, - (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0) + .setFlag(SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING, + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0) .setFlag(SYSUI_STATE_OVERVIEW_DISABLED, (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0) .setFlag(SYSUI_STATE_HOME_DISABLED, @@ -489,12 +491,14 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, imeShown = (vis & InputMethodService.IME_VISIBLE_IMPERCEPTIBLE) != 0; } showImeSwitcher = imeShown && showImeSwitcher; - int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition, + int hints = Utilities.calculateNavigationIconHints(mNavigationIconHints, backDisposition, imeShown, showImeSwitcher); - if (hints != mNavigationIconHints) { - mNavigationIconHints = hints; - updateSysuiFlags(); + if (hints == mNavigationIconHints) { + return; } + + mNavigationIconHints = hints; + updateSysuiFlags(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java index c78750718cf0..6842dbc2b12d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java @@ -18,11 +18,12 @@ package com.android.systemui.navigationbar.views; import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; import static android.app.StatusBarManager.WindowVisibleState; +import static android.app.StatusBarManager.navigationHintsToString; import static android.app.StatusBarManager.windowStateToString; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.view.InsetsSource.FLAG_SUPPRESS_SCRIM; @@ -43,7 +44,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode; @@ -56,6 +57,7 @@ import android.annotation.NonNull; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.StatusBarManager; +import android.app.StatusBarManager.NavigationHint; import android.content.Context; import android.content.res.Configuration; import android.graphics.Insets; @@ -233,6 +235,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING; + @NavigationHint private int mNavigationIconHints = 0; private @TransitionMode int mTransitionMode; private boolean mLongPressHomeEnabled; @@ -817,7 +820,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (mSavedState != null) { getBarTransitions().getLightTransitionsController().restoreState(mSavedState); } - setNavigationIconHints(mNavigationIconHints); setWindowVisible(isNavBarWindowVisible()); mView.setBehavior(mBehavior); setNavBarMode(mNavBarMode); @@ -1111,6 +1113,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements pw.println(" mLongPressHomeEnabled=" + mLongPressHomeEnabled); pw.println(" mNavigationBarWindowState=" + windowStateToString(mNavigationBarWindowState)); + pw.println(" mNavigationIconHints=" + navigationHintsToString(mNavigationIconHints)); pw.println(" mTransitionMode=" + BarTransitions.modeToString(mTransitionMode)); pw.println(" mTransientShown=" + mTransientShown); @@ -1137,9 +1140,11 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } boolean imeShown = mNavBarHelper.isImeShown(vis); showImeSwitcher = imeShown && showImeSwitcher; - int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition, + int hints = Utilities.calculateNavigationIconHints(mNavigationIconHints, backDisposition, imeShown, showImeSwitcher); - if (hints == mNavigationIconHints) return; + if (hints == mNavigationIconHints) { + return; + } setNavigationIconHints(hints); checkBarModes(); @@ -1682,8 +1687,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isNavBarWindowVisible()) .setFlag(SYSUI_STATE_IME_SHOWING, (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0) - .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING, - (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0) + .setFlag(SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING, + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0) .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, allowSystemGestureIgnoringBarVisibility()) .commitUpdate(mDisplayId); @@ -1926,12 +1931,20 @@ public class NavigationBar extends ViewController<NavigationBarView> implements }; @VisibleForTesting + @NavigationHint int getNavigationIconHints() { return mNavigationIconHints; } - private void setNavigationIconHints(int hints) { - if (hints == mNavigationIconHints) return; + /** + * Updates the navigation icons based on {@code hints}. + * + * @param hints bit flags defined in {@link StatusBarManager}. + */ + private void setNavigationIconHints(@NavigationHint int hints) { + if (hints == mNavigationIconHints) { + return; + } if (!isLargeScreen(mContext)) { // All IME functions handled by launcher via Sysui flags for large screen final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; @@ -1945,9 +1958,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mView.setNavigationIconHints(hints); } if (DEBUG) { - android.widget.Toast.makeText(mContext, - "Navigation icon hints = " + hints, - 500).show(); + android.widget.Toast.makeText(mContext, "Navigation icon hints = " + hints, 500) + .show(); } mNavigationIconHints = hints; } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java index d5ae72165c4a..ed8e538cc895 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java @@ -16,6 +16,9 @@ package com.android.systemui.navigationbar.views; +import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN; import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; @@ -31,7 +34,7 @@ import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.DrawableRes; -import android.app.StatusBarManager; +import android.app.StatusBarManager.NavigationHint; import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; @@ -113,6 +116,7 @@ public class NavigationBarView extends FrameLayout { boolean mLongClickableAccessibilityButton; int mDisabledFlags = 0; + @NavigationHint int mNavigationIconHints = 0; private int mNavBarMode; private boolean mImeDrawsImeNavBar; @@ -499,8 +503,7 @@ public class NavigationBarView extends FrameLayout { } private void orientBackButton(KeyButtonDrawable drawable) { - final boolean useAltBack = - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + final boolean useAltBack = (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0; final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; float degrees = useAltBack ? (isRtl ? 90 : -90) : 0; if (drawable.getRotation() == degrees) { @@ -555,8 +558,10 @@ public class NavigationBarView extends FrameLayout { super.setLayoutDirection(layoutDirection); } - void setNavigationIconHints(int hints) { - if (hints == mNavigationIconHints) return; + void setNavigationIconHints(@NavigationHint int hints) { + if (hints == mNavigationIconHints) { + return; + } mNavigationIconHints = hints; updateNavButtonIcons(); } @@ -594,8 +599,7 @@ public class NavigationBarView extends FrameLayout { // We have to replace or restore the back and home button icons when exiting or entering // carmode, respectively. Recents are not available in CarMode in nav bar so change // to recent icon is not required. - final boolean useAltBack = - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + final boolean useAltBack = (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0; KeyButtonDrawable backIcon = mBackIcon; orientBackButton(backIcon); KeyButtonDrawable homeIcon = mHomeDefaultIcon; @@ -607,11 +611,12 @@ public class NavigationBarView extends FrameLayout { updateRecentsIcon(); - // Update IME button visibility, a11y and rotate button always overrides the appearance - boolean disableImeSwitcher = - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN) == 0 - || isImeRenderingNavButtons(); - mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, !disableImeSwitcher); + // Update IME switcher button visibility, a11y and rotate button always overrides + // the appearance + boolean isImeSwitcherButtonVisible = + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_SHOWN) != 0 + && !isImeRenderingNavButtons(); + mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, isImeSwitcherButtonVisible); mBarTransitions.reapplyDarkIntensity(); @@ -665,7 +670,7 @@ public class NavigationBarView extends FrameLayout { boolean isImeRenderingNavButtons() { return mImeDrawsImeNavBar && mImeCanRenderGesturalNavButtons - && (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0; + && (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0; } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt index d2ee126ace91..2cccaddc02a8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt @@ -498,11 +498,9 @@ fun gridHeight(rows: Int, tileHeight: Dp, tilePadding: Dp, gridPadding: Dp): Dp return ((tileHeight + tilePadding) * rows) + gridPadding * 2 } -private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any { +private fun GridCell.key(index: Int): Any { return when (this) { - is TileGridCell -> { - if (dragAndDropState.isMoving(tile.tileSpec)) index else key - } + is TileGridCell -> key is SpacerGridCell -> index } } @@ -510,10 +508,13 @@ private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any { /** * Adds a list of [GridCell] to the lazy grid * - * @param cells the pairs of [GridCell] to [AnimatableTileViewModel] + * @param cells the pairs of [GridCell] to [BounceableTileViewModel] + * @param columns the number of columns of this tile grid * @param dragAndDropState the [DragAndDropState] for this grid * @param selectionState the [MutableSelectionState] for this grid - * @param onToggleSize the callback when a tile's size is toggled + * @param coroutineScope the [CoroutineScope] to be used for the tiles + * @param largeTilesSpan the width used for large tiles + * @param onResize the callback when a tile has a new [ResizeOperation] */ fun LazyGridScope.EditTiles( cells: List<Pair<GridCell, BounceableTileViewModel>>, @@ -526,7 +527,7 @@ fun LazyGridScope.EditTiles( ) { items( count = cells.size, - key = { cells[it].first.key(it, dragAndDropState) }, + key = { cells[it].first.key(it) }, span = { cells[it].first.span }, contentType = { TileType }, ) { index -> @@ -536,13 +537,12 @@ fun LazyGridScope.EditTiles( // If the tile is being moved, replace it with a visible spacer SpacerGridCell( Modifier.background( - color = - MaterialTheme.colorScheme.secondary.copy( - alpha = EditModeTileDefaults.PLACEHOLDER_ALPHA - ), - shape = RoundedCornerShape(InactiveCornerRadius), - ) - .animateItem() + color = + MaterialTheme.colorScheme.secondary.copy( + alpha = EditModeTileDefaults.PLACEHOLDER_ALPHA + ), + shape = RoundedCornerShape(InactiveCornerRadius), + ) ) } else { TileGridCell( diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index ecd002705c60..63c10c9b971a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -945,10 +945,6 @@ constructor( override fun onTransitionAnimationEnd() { sceneInteractor.onTransitionAnimationEnd() } - - override fun onTransitionAnimationCancelled() { - sceneInteractor.onTransitionAnimationCancelled() - } } ) } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt index 08214c456897..f5c605211520 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt @@ -35,7 +35,6 @@ import android.util.Log import android.view.Display import android.view.ScrollCaptureResponse import android.view.ViewRootImpl.ActivityConfigCallback -import android.view.WindowManager import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE import android.widget.Toast import android.window.WindowContext @@ -218,9 +217,7 @@ internal constructor( window.setFocusable(true) viewProxy.requestFocus() - if (screenshot.type != WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { - enqueueScrollCaptureRequest(requestId, screenshot.userHandle) - } + enqueueScrollCaptureRequest(requestId, screenshot.userHandle) window.attachWindow() diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt index 2e6c7567259f..d82a8bd0ddcd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt @@ -31,7 +31,7 @@ class ScreenshotCrossProfileService : Service() { private val mBinder: IBinder = object : ICrossProfileService.Stub() { - override fun launchIntent(intent: Intent, bundle: Bundle) { + override fun launchIntent(intent: Intent, bundle: Bundle?) { startActivity(intent, bundle) } } diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java index 49f3cfc4ceaf..917869a66ca4 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java @@ -27,6 +27,8 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; +import android.graphics.RenderEffect; +import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.os.Looper; import android.util.AttributeSet; @@ -373,4 +375,18 @@ public class ScrimView extends View { ((ScrimDrawable) mDrawable).setRoundedCorners(radius); } } + + /** + * Blur the view with the specific blur radius or clear any blurs if the radius is 0 + */ + public void setBlurRadius(float blurRadius) { + if (blurRadius > 0) { + setRenderEffect(RenderEffect.createBlurEffect( + blurRadius, + blurRadius, + Shader.TileMode.CLAMP)); + } else { + setRenderEffect(null); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 19bf4c0bab81..a1b4def09ba9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -109,7 +109,6 @@ import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder; -import com.android.systemui.keyguard.ui.transitions.BlurConfig; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel; import com.android.systemui.media.controls.domain.pipeline.MediaDataManager; @@ -915,8 +914,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump if (!com.android.systemui.Flags.bouncerUiRevamp()) return; if (isBouncerShowing && isExpanded()) { - float shadeBlurEffect = BlurConfig.maxBlurRadiusToNotificationPanelBlurRadius( - mDepthController.getMaxBlurRadiusPx()); + float shadeBlurEffect = mDepthController.getMaxBlurRadiusPx(); mView.setRenderEffect(RenderEffect.createBlurEffect( shadeBlurEffect, shadeBlurEffect, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index 48bbb0407ee3..48e374746bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -17,6 +17,7 @@ package com.android.systemui.shade; import static android.os.Trace.TRACE_TAG_APP; +import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED; import static com.android.systemui.Flags.enableViewCaptureTracing; import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG; @@ -49,10 +50,12 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowInsetsController; +import android.view.accessibility.AccessibilityEvent; import com.android.app.viewcapture.ViewCaptureFactory; import com.android.internal.view.FloatingActionMode; import com.android.internal.widget.floatingtoolbar.FloatingToolbar; +import com.android.systemui.Flags; import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; import com.android.systemui.statusbar.phone.ConfigurationForwarder; @@ -77,6 +80,8 @@ public class NotificationShadeWindowView extends WindowRootView { private SafeCloseable mViewCaptureCloseable; + private boolean mAnimatingContentLaunch = false; + public NotificationShadeWindowView(Context context, AttributeSet attrs) { super(context, attrs); setMotionEventSplittingEnabled(false); @@ -188,6 +193,22 @@ public class NotificationShadeWindowView extends WindowRootView { } } + @Override + public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { + if (Flags.shadeLaunchAccessibility() && mAnimatingContentLaunch + && event.getEventType() == TYPE_VIEW_ACCESSIBILITY_FOCUSED) { + // Block accessibility focus events during launch animations to avoid stray TalkBack + // announcements. + return false; + } + + return super.requestSendAccessibilityEvent(child, event); + } + + public void setAnimatingContentLaunch(boolean animating) { + mAnimatingContentLaunch = animating; + } + public void setConfigurationForwarder(ConfigurationForwarder configurationForwarder) { ShadeWindowGoesAround.isUnexpectedlyInLegacyMode(); mConfigurationForwarder = configurationForwarder; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index e5dcd2338b9d..ffec8f284c48 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -16,10 +16,12 @@ package com.android.systemui.shade; +import static com.android.systemui.Flags.shadeLaunchAccessibility; import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING; import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows; import android.app.StatusBarManager; import android.util.Log; @@ -59,6 +61,7 @@ import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener; import com.android.systemui.statusbar.BlurUtils; @@ -85,6 +88,7 @@ import com.android.systemui.window.ui.WindowRootViewBinder; import com.android.systemui.window.ui.viewmodel.WindowRootViewModel; import kotlinx.coroutines.ExperimentalCoroutinesApi; +import kotlinx.coroutines.flow.Flow; import java.io.PrintWriter; import java.util.Optional; @@ -174,6 +178,7 @@ public class NotificationShadeWindowViewController implements Dumpable { NotificationShadeDepthController depthController, NotificationShadeWindowView notificationShadeWindowView, ShadeViewController shadeViewController, + ShadeAnimationInteractor shadeAnimationInteractor, PanelExpansionInteractor panelExpansionInteractor, ShadeExpansionStateManager shadeExpansionStateManager, NotificationStackScrollLayoutController notificationStackScrollLayoutController, @@ -238,9 +243,17 @@ public class NotificationShadeWindowViewController implements Dumpable { collectFlow(mView, keyguardTransitionInteractor.transition( Edge.create(LOCKSCREEN, DREAMING)), mLockscreenToDreamingTransition); + Flow<Boolean> isLaunchAnimationRunning = + shadeLaunchAccessibility() + ? combineFlows( + notificationLaunchAnimationInteractor.isLaunchAnimationRunning(), + shadeAnimationInteractor.isLaunchingActivity(), + (notificationLaunching, shadeLaunching) -> + notificationLaunching || shadeLaunching) + : notificationLaunchAnimationInteractor.isLaunchAnimationRunning(); collectFlow( mView, - notificationLaunchAnimationInteractor.isLaunchAnimationRunning(), + isLaunchAnimationRunning, this::setExpandAnimationRunning); if (QSComposeFragment.isEnabled()) { collectFlow(mView, @@ -726,9 +739,17 @@ public class NotificationShadeWindowViewController implements Dumpable { if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) { Log.d(TAG, "Setting mExpandAnimationRunning=" + running); } + if (running) { mLaunchAnimationTimeout = mClock.uptimeMillis() + 5000; } + + if (shadeLaunchAccessibility()) { + // The view needs to know when an animation is ongoing so it can intercept + // unnecessary accessibility events. + mView.setAnimatingContentLaunch(running); + } + mExpandAnimationRunning = running; mNotificationShadeWindowController.setLaunchingActivity(mExpandAnimationRunning); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt index 7a70966c2b12..b15615a83698 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt @@ -5,6 +5,7 @@ import android.view.DisplayCutout import com.android.systemui.battery.BatteryMeterView import com.android.systemui.res.R import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore +import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider import javax.inject.Inject /** @@ -15,11 +16,9 @@ class QsBatteryModeController @Inject constructor( @ShadeDisplayAware private val context: Context, - insetsProviderStore: StatusBarContentInsetsProviderStore, + private val insetsProviderStore: StatusBarContentInsetsProviderStore, ) { - private val insetsProvider = insetsProviderStore.defaultDisplay - private companion object { // MotionLayout frames are in [0, 100]. Where 0 and 100 are reserved for start and end // frames. @@ -43,17 +42,19 @@ constructor( * animation. */ @BatteryMeterView.BatteryPercentMode - fun getBatteryMode(cutout: DisplayCutout?, qsExpandedFraction: Float): Int? = - when { + fun getBatteryMode(cutout: DisplayCutout?, qsExpandedFraction: Float): Int? { + val insetsProvider = insetsProviderStore.forDisplay(context.displayId) + return when { qsExpandedFraction > fadeInStartFraction -> BatteryMeterView.MODE_ESTIMATE - qsExpandedFraction < fadeOutCompleteFraction -> - if (hasCenterCutout(cutout)) { + insetsProvider != null && qsExpandedFraction < fadeOutCompleteFraction -> + if (hasCenterCutout(cutout, insetsProvider)) { BatteryMeterView.MODE_ON } else { BatteryMeterView.MODE_ESTIMATE } else -> null } + } fun updateResources() { fadeInStartFraction = @@ -64,7 +65,10 @@ constructor( MOTION_LAYOUT_MAX_FRAME.toFloat() } - private fun hasCenterCutout(cutout: DisplayCutout?): Boolean = + private fun hasCenterCutout( + cutout: DisplayCutout?, + insetsProvider: StatusBarContentInsetsProvider, + ): Boolean = cutout?.let { !insetsProvider.currentRotationHasCornerCutout() && !it.boundingRectTop.isEmpty } ?: false diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index e8a792c30aa2..d82f8e722744 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -57,6 +57,7 @@ import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONS import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER import com.android.systemui.shade.carrier.ShadeCarrierGroup import com.android.systemui.shade.carrier.ShadeCarrierGroupController +import com.android.systemui.shade.data.repository.ShadeDisplaysRepository import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusIconContainer @@ -90,8 +91,9 @@ constructor( private val statusBarIconController: StatusBarIconController, private val tintedIconManagerFactory: TintedIconManager.Factory, private val privacyIconsController: HeaderPrivacyIconsController, - private val insetsProviderStore: StatusBarContentInsetsProviderStore, + private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore, @ShadeDisplayAware private val configurationController: ConfigurationController, + private val shadeDisplaysRepository: ShadeDisplaysRepository, private val variableDateViewControllerFactory: VariableDateViewController.Factory, @Named(SHADE_HEADER) private val batteryMeterViewController: BatteryMeterViewController, private val dumpManager: DumpManager, @@ -104,7 +106,9 @@ constructor( private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, ) : ViewController<View>(header), Dumpable { - private val insetsProvider = insetsProviderStore.defaultDisplay + private val statusBarContentInsetsProvider + get() = + statusBarContentInsetsProviderStore.forDisplay(shadeDisplaysRepository.displayId.value) companion object { /** IDs for transitions and constraints for the [MotionLayout]. */ @@ -222,10 +226,14 @@ constructor( private val insetListener = View.OnApplyWindowInsetsListener { view, insets -> - updateConstraintsForInsets(view as MotionLayout, insets) - lastInsets = WindowInsets(insets) - - view.onApplyWindowInsets(insets) + val windowInsets = WindowInsets(insets) + if (windowInsets != lastInsets) { + updateConstraintsForInsets(view as MotionLayout, insets) + lastInsets = windowInsets + view.onApplyWindowInsets(insets) + } else { + insets + } } private var singleCarrier = false @@ -414,6 +422,7 @@ constructor( } private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) { + val insetsProvider = statusBarContentInsetsProvider ?: return val cutout = insets.displayCutout.also { this.cutout = it } val sbInsets: Insets = insetsProvider.getStatusBarContentInsetsForCurrentRotation() @@ -508,6 +517,9 @@ constructor( systemIconsHoverContainer.setOnClickListener(null) systemIconsHoverContainer.isClickable = false } + + lastInsets?.let { updateConstraintsForInsets(header, it) } + header.jumpToState(header.startState) updatePosition() updateScrollY() diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt index edf503d03f3e..59d812403777 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt @@ -16,17 +16,20 @@ package com.android.systemui.shade.domain.interactor +import android.provider.Settings import androidx.annotation.FloatRange import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn /** @@ -76,29 +79,53 @@ interface ShadeModeInteractor { class ShadeModeInteractorImpl @Inject -constructor(@Application applicationScope: CoroutineScope, repository: ShadeRepository) : - ShadeModeInteractor { +constructor( + @Application applicationScope: CoroutineScope, + repository: ShadeRepository, + secureSettingsRepository: SecureSettingsRepository, +) : ShadeModeInteractor { + + private val isDualShadeEnabled: Flow<Boolean> = + secureSettingsRepository.boolSetting( + Settings.Secure.DUAL_SHADE, + defaultValue = DUAL_SHADE_ENABLED_DEFAULT, + ) override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide override val shadeMode: StateFlow<ShadeMode> = - isShadeLayoutWide - .map(this::determineShadeMode) + combine(isDualShadeEnabled, repository.isShadeLayoutWide, ::determineShadeMode) .stateIn( applicationScope, SharingStarted.Eagerly, - initialValue = determineShadeMode(isShadeLayoutWide.value), + initialValue = + determineShadeMode( + isDualShadeEnabled = DUAL_SHADE_ENABLED_DEFAULT, + isShadeLayoutWide = repository.isShadeLayoutWide.value, + ), ) @FloatRange(from = 0.0, to = 1.0) override fun getTopEdgeSplitFraction(): Float = 0.5f - private fun determineShadeMode(isShadeLayoutWide: Boolean): ShadeMode { + private fun determineShadeMode( + isDualShadeEnabled: Boolean, + isShadeLayoutWide: Boolean, + ): ShadeMode { return when { - DualShade.isEnabled -> ShadeMode.Dual + isDualShadeEnabled || + // TODO(b/388793191): This ensures that the dual_shade aconfig flag can also enable + // Dual Shade, to avoid breaking unit tests. Remove this once all references to the + // flag are removed. + DualShade.isEnabled -> ShadeMode.Dual isShadeLayoutWide -> ShadeMode.Split else -> ShadeMode.Single } } + + companion object { + /* Whether the Dual Shade setting is enabled by default. */ + private const val DUAL_SHADE_ENABLED_DEFAULT = false + } } class ShadeModeInteractorEmptyImpl @Inject constructor() : ShadeModeInteractor { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 7fc1510f1136..dcea8d85e10d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -273,12 +273,12 @@ public class CommandQueue extends IStatusBar.Stub implements default void toggleQuickSettingsPanel() { } /** - * Called to notify IME window status changes. + * Sets the new IME window status. * - * @param displayId The id of the display to notify. - * @param vis IME visibility. - * @param backDisposition Disposition mode of back button. - * @param showImeSwitcher {@code true} to show IME switch button. + * @param displayId The id of the display to which the IME is bound. + * @param vis The IME window visibility. + * @param backDisposition The IME back disposition mode. + * @param showImeSwitcher Whether the IME Switcher button should be shown. */ default void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, @BackDispositionMode int backDisposition, boolean showImeSwitcher) { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index a5595edcbb95..4269f60e1c2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -88,6 +88,7 @@ import com.android.keyguard.TrustGrantFlags; import com.android.keyguard.logging.KeyguardLogger; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; +import com.android.systemui.Flags; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FaceHelpMessageDeferral; import com.android.systemui.biometrics.FaceHelpMessageDeferralFactory; @@ -199,7 +200,7 @@ public class KeyguardIndicationController { private CharSequence mBiometricMessage; private CharSequence mBiometricMessageFollowUp; private BiometricSourceType mBiometricMessageSource; - protected ColorStateList mInitialTextColorState; + private ColorStateList mInitialTextColorState; private boolean mVisible; private boolean mOrganizationOwnedDevice; @@ -393,13 +394,27 @@ public class KeyguardIndicationController { return mIndicationArea; } + /** + * Notify controller about configuration changes. + */ + public void onConfigurationChanged() { + // Get new text color in case theme has changed + if (Flags.indicationTextA11yFix()) { + setIndicationColorToThemeColor(); + } + } + public void setIndicationArea(ViewGroup indicationArea) { mIndicationArea = indicationArea; mTopIndicationView = indicationArea.findViewById(R.id.keyguard_indication_text); mLockScreenIndicationView = indicationArea.findViewById( R.id.keyguard_indication_text_bottom); - mInitialTextColorState = mTopIndicationView != null - ? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE); + if (Flags.indicationTextA11yFix()) { + setIndicationColorToThemeColor(); + } else { + setIndicationTextColor(mTopIndicationView != null + ? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE)); + } if (mRotateTextViewController != null) { mRotateTextViewController.destroy(); } @@ -436,6 +451,12 @@ public class KeyguardIndicationController { mIsLogoutEnabledCallback); } + @NonNull + private ColorStateList wallpaperTextColor() { + return ColorStateList.valueOf( + Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor)); + } + /** * Cleanup */ @@ -513,7 +534,7 @@ public class KeyguardIndicationController { .setMessage(mContext.getResources().getString( com.android.systemui.res.R.string.dismissible_keyguard_swipe) ) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), /* updateImmediately */ true); } else { @@ -533,7 +554,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_DISCLOSURE, new KeyguardIndication.Builder() .setMessage(disclosure) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), /* updateImmediately */ false); } @@ -602,7 +623,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_OWNER_INFO, new KeyguardIndication.Builder() .setMessage(finalInfo) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), false); } else { @@ -624,7 +645,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_BATTERY, new KeyguardIndication.Builder() .setMessage(powerIndication) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), animate); } else { @@ -645,7 +666,7 @@ public class KeyguardIndicationController { new KeyguardIndication.Builder() .setMessage(mContext.getResources().getText( com.android.internal.R.string.lockscreen_storage_locked)) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), false); } else { @@ -666,7 +687,7 @@ public class KeyguardIndicationController { .setMessage(mBiometricMessage) .setForceAccessibilityLiveRegionAssertive() .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), true ); @@ -680,7 +701,7 @@ public class KeyguardIndicationController { new KeyguardIndication.Builder() .setMessage(mBiometricMessageFollowUp) .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), true ); @@ -711,7 +732,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_TRUST, new KeyguardIndication.Builder() .setMessage(trustGrantedIndication) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), true); hideBiometricMessage(); @@ -722,7 +743,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_TRUST, new KeyguardIndication.Builder() .setMessage(trustManagedIndication) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), false); } else { @@ -751,7 +772,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE, new KeyguardIndication.Builder() .setMessage(mPersistentUnlockMessage) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), true); } else { @@ -792,7 +813,7 @@ public class KeyguardIndicationController { new KeyguardIndication.Builder() .setMessage(mContext.getString( R.string.keyguard_indication_after_adaptive_auth_lock)) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), true); } else { @@ -1179,7 +1200,8 @@ public class KeyguardIndicationController { } else { message = mContext.getString(R.string.keyguard_retry); } - mStatusBarKeyguardViewManager.setKeyguardMessage(message, mInitialTextColorState, + mStatusBarKeyguardViewManager.setKeyguardMessage(message, + getInitialTextColorState(), null); } } else { @@ -1232,7 +1254,7 @@ public class KeyguardIndicationController { public void dump(PrintWriter pw, String[] args) { pw.println("KeyguardIndicationController:"); - pw.println(" mInitialTextColorState: " + mInitialTextColorState); + pw.println(" mInitialTextColorState: " + getInitialTextColorState()); pw.println(" mPowerPluggedInWired: " + mPowerPluggedInWired); pw.println(" mPowerPluggedIn: " + mPowerPluggedIn); pw.println(" mPowerCharged: " + mPowerCharged); @@ -1253,6 +1275,22 @@ public class KeyguardIndicationController { mRotateTextViewController.dump(pw, args); } + protected ColorStateList getInitialTextColorState() { + return mInitialTextColorState; + } + + private void setIndicationColorToThemeColor() { + mInitialTextColorState = wallpaperTextColor(); + } + + /** + * @deprecated Use {@link #setIndicationColorToThemeColor} + */ + @Deprecated + private void setIndicationTextColor(ColorStateList color) { + mInitialTextColorState = color; + } + protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback { @Override public void onTimeChanged() { @@ -1358,7 +1396,7 @@ public class KeyguardIndicationController { mBouncerMessageInteractor.setFaceAcquisitionMessage(helpString); } mStatusBarKeyguardViewManager.setKeyguardMessage(helpString, - mInitialTextColorState, biometricSourceType); + getInitialTextColorState(), biometricSourceType); } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { if (isCoExFaceAcquisitionMessage && msgId == FACE_ACQUIRED_TOO_DARK) { showBiometricMessage( @@ -1655,7 +1693,7 @@ public class KeyguardIndicationController { private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg, BiometricSourceType biometricSourceType) { if (mStatusBarKeyguardViewManager.isBouncerShowing()) { - mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState, + mStatusBarKeyguardViewManager.setKeyguardMessage(errString, getInitialTextColorState(), biometricSourceType); } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { showBiometricMessage(errString, followUpMsg, biometricSourceType); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index ccea254defaa..4c6fa4839e5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -284,7 +284,8 @@ public class NotificationListener extends NotificationListenerWithPlugins implem /* rankingAdjustment= */ 0, /* isBubble= */ false, /* proposedImportance= */ 0, - /* sensitiveContent= */ false + /* sensitiveContent= */ false, + /* summarization = */ null ); } return ranking; 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 2ae54d7c6c83..c9024d934068 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 @@ -33,7 +33,7 @@ object StatusBarNoHunBehavior { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.statusBarNoHunBehavior() && android.app.Flags.notificationsRedesignAppIcons() + get() = Flags.statusBarNoHunBehavior() /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java index 0171fb72e158..be61dc95fe20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java @@ -706,7 +706,7 @@ public class HeadsUpManagerImpl } private void updateHeadsUpFlow() { - mHeadsUpNotificationRows.setValue(new HashSet<>(getHeadsUpEntryPhoneMap().values())); + mHeadsUpNotificationRows.setValue(new HashSet<>(mHeadsUpEntryMap.values())); } @Override @@ -732,11 +732,6 @@ public class HeadsUpManagerImpl return mHeadsUpAnimatingAway.getValue(); } - @NonNull - private ArrayMap<String, HeadsUpEntry> getHeadsUpEntryPhoneMap() { - return mHeadsUpEntryMap; - } - /** * Called to notify the listeners that the HUN animating away animation has ended. */ @@ -1014,7 +1009,7 @@ public class HeadsUpManagerImpl @Override public void setRemoteInputActive( @NonNull NotificationEntry entry, boolean remoteInputActive) { - HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(entry.getKey()); + HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(entry.getKey()); if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) { headsUpEntry.mRemoteInputActive = remoteInputActive; if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) { @@ -1029,11 +1024,6 @@ public class HeadsUpManagerImpl } } - @Nullable - private HeadsUpEntry getHeadsUpEntryPhone(@NonNull String key) { - return mHeadsUpEntryMap.get(key); - } - @Override public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) { HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey()); @@ -1125,7 +1115,7 @@ public class HeadsUpManagerImpl return true; } - HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(key); + HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); HeadsUpEntry topEntry = getTopHeadsUpEntryPhone(); if (headsUpEntry == null || headsUpEntry != topEntry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java index aad618d50067..9c6e41c482b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java @@ -64,11 +64,11 @@ public class BundleNotificationInfo extends NotificationInfo { boolean isNonblockable, boolean wasShownHighPriority, AssistantFeedbackController assistantFeedbackController, - MetricsLogger metricsLogger) throws RemoteException { + MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException { super.bindNotification(pm, iNotificationManager, onUserInteractionCallback, channelEditorDialogController, pkg, notificationChannel, entry, onSettingsClick, onAppSettingsClick, uiEventLogger, isDeviceProvisioned, isNonblockable, - wasShownHighPriority, assistantFeedbackController, metricsLogger); + wasShownHighPriority, assistantFeedbackController, metricsLogger, onCloseClick); // Additionally, bind the feedback button. ComponentName assistant = iNotificationManager.getAllowedNotificationAssistant(); 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 793b3b8b1e42..6aa5e405f29c 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 @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row +import android.animation.ValueAnimator import android.content.Context import android.graphics.BlendMode import android.graphics.Canvas @@ -38,8 +39,8 @@ import androidx.compose.ui.viewinterop.AndroidView import com.android.internal.graphics.ColorUtils import com.android.systemui.res.R import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary -import kotlin.math.max -import kotlin.math.roundToInt +import com.android.wm.shell.shared.animation.Interpolators +import kotlin.math.min /** * A background style for smarter-smart-actions. The style is composed by a simplex3d noise, @@ -48,7 +49,7 @@ import kotlin.math.roundToInt class MagicActionBackgroundDrawable( context: Context, primaryContainer: Int? = null, - private val seed: Float = 0f, + seed: Float = 0f, ) : Drawable() { private val pixelDensity = context.resources.displayMetrics.density @@ -60,17 +61,15 @@ class MagicActionBackgroundDrawable( .toFloat() private val buttonShape = Path() private val paddingVertical = - context.resources - .getDimensionPixelSize(R.dimen.smart_reply_button_padding_vertical) - .toFloat() + context.resources.getDimensionPixelSize(R.dimen.smart_action_button_icon_padding).toFloat() /** The color of the button background. */ private val mainColor = primaryContainer ?: context.getColor(com.android.internal.R.color.materialColorPrimaryContainer) - /** Slightly dimmed down version of [mainColor] used on the simplex noise. */ - private val dimColor: Int + /** Slightly brighter version of [mainColor] used on the simplex noise. */ + private val effectColor: Int get() { val labColor = arrayOf(0.0, 0.0, 0.0).toDoubleArray() ColorUtils.colorToLAB(mainColor, labColor) @@ -78,56 +77,97 @@ class MagicActionBackgroundDrawable( return ColorUtils.CAMToColor( camColor.hue, camColor.chroma, - max(0f, (labColor[0] - 20).toFloat()), + min(100f, (labColor[0] + 10).toFloat()), ) } private val bgShader = MagicActionBackgroundShader() private val bgPaint = Paint() private val outlinePaint = Paint() + private val gradientAnimator = + ValueAnimator.ofFloat(0f, 1f).apply { + duration = 2500 + interpolator = Interpolators.LINEAR + addUpdateListener { invalidateSelf() } + } + private val turbulenceAnimator = + ValueAnimator.ofFloat(seed, seed + TURBULENCE_MOVEMENT).apply { + duration = ANIMATION_DURATION + interpolator = Interpolators.LINEAR + addUpdateListener { invalidateSelf() } + start() + } + private val effectFadeAnimation = + ValueAnimator.ofFloat(0f, 1f).apply { + duration = 1000 + startDelay = ANIMATION_DURATION - 1000L + interpolator = Interpolators.STANDARD_DECELERATE + addUpdateListener { invalidateSelf() } + } init { bgShader.setColorUniform("in_color", mainColor) - bgShader.setColorUniform("in_dimColor", dimColor) + bgShader.setColorUniform("in_effectColor", effectColor) bgPaint.shader = bgShader outlinePaint.style = Paint.Style.STROKE // Stroke is doubled in width and then clipped, to avoid anti-aliasing artifacts at the edge // of the rectangle. outlinePaint.strokeWidth = outlineStrokeWidth * 2 outlinePaint.blendMode = BlendMode.SCREEN - outlinePaint.alpha = (255 * 0.32f).roundToInt() + outlinePaint.alpha = OUTLINE_ALPHA + + animate() + } + + private fun animate() { + turbulenceAnimator.start() + gradientAnimator.start() + effectFadeAnimation.start() } override fun draw(canvas: Canvas) { + updateShaders() + // We clip instead of drawing 2 rounded rects, otherwise there will be artifacts where // around the button background and the outline. + canvas.save() canvas.clipPath(buttonShape) + canvas.drawPath(buttonShape, bgPaint) + canvas.drawPath(buttonShape, outlinePaint) + canvas.restore() + } - canvas.drawRect(bounds, bgPaint) - canvas.drawRoundRect( - bounds.left.toFloat(), - bounds.top + paddingVertical, - bounds.right.toFloat(), - bounds.bottom - paddingVertical, - cornerRadius, - cornerRadius, - outlinePaint, - ) + private fun updateShaders() { + val effectAlpha = 1f - effectFadeAnimation.animatedValue as Float + val turbulenceZ = turbulenceAnimator.animatedValue as Float + bgShader.setFloatUniform("in_sparkleMove", turbulenceZ * 1000) + bgShader.setFloatUniform("in_noiseMove", 0f, 0f, turbulenceZ) + bgShader.setFloatUniform("in_turbulenceAlpha", effectAlpha) + bgShader.setFloatUniform("in_spkarkleAlpha", SPARKLE_ALPHA * effectAlpha) + val gradientOffset = gradientAnimator.animatedValue as Float * bounds.width() + val outlineGradient = + LinearGradient( + gradientOffset + bounds.left.toFloat(), + 0f, + gradientOffset + bounds.right.toFloat(), + 0f, + mainColor, + ColorUtils.setAlphaComponent(mainColor, 0), + Shader.TileMode.MIRROR, + ) + outlinePaint.shader = outlineGradient } override fun onBoundsChange(bounds: Rect) { super.onBoundsChange(bounds) val width = bounds.width().toFloat() - val height = bounds.height() - paddingVertical * 2 + val height = bounds.height().toFloat() if (width == 0f || height == 0f) return bgShader.setFloatUniform("in_gridNum", NOISE_SIZE) - bgShader.setFloatUniform("in_spkarkleAlpha", SPARKLE_ALPHA) - bgShader.setFloatUniform("in_noiseMove", 0f, 0f, 0f) bgShader.setFloatUniform("in_size", width, height) bgShader.setFloatUniform("in_aspectRatio", width / height) - bgShader.setFloatUniform("in_time", seed) bgShader.setFloatUniform("in_pixelDensity", pixelDensity) buttonShape.reset() @@ -140,18 +180,6 @@ class MagicActionBackgroundDrawable( cornerRadius, Path.Direction.CW, ) - - val outlineGradient = - LinearGradient( - bounds.left.toFloat(), - 0f, - bounds.right.toFloat(), - 0f, - mainColor, - ColorUtils.setAlphaComponent(mainColor, 0), - Shader.TileMode.CLAMP, - ) - outlinePaint.shader = outlineGradient } override fun setAlpha(alpha: Int) { @@ -168,10 +196,15 @@ class MagicActionBackgroundDrawable( companion object { /** Smoothness of the turbulence. Larger numbers yield more detail. */ - private const val NOISE_SIZE = 0.7f - + private const val NOISE_SIZE = 0.57f /** Strength of the sparkles overlaid on the turbulence. */ private const val SPARKLE_ALPHA = 0.15f + /** Alpha (0..255) of the button outline */ + private const val OUTLINE_ALPHA = 82 + /** Turbulence grid size */ + private const val TURBULENCE_MOVEMENT = 4.3f + /** Total animation duration in millis */ + private const val ANIMATION_DURATION = 5000L } } @@ -183,24 +216,25 @@ private class MagicActionBackgroundShader : RuntimeShader(SHADER) { """ uniform float in_gridNum; uniform vec3 in_noiseMove; + uniform half in_sparkleMove; uniform vec2 in_size; uniform float in_aspectRatio; - uniform half in_time; uniform half in_pixelDensity; + uniform float in_turbulenceAlpha; uniform float in_spkarkleAlpha; layout(color) uniform vec4 in_color; - layout(color) uniform vec4 in_dimColor; + layout(color) uniform vec4 in_effectColor; """ private const val MAIN_SHADER = """vec4 main(vec2 p) { vec2 uv = p / in_size.xy; uv.x *= in_aspectRatio; vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - half luma = 1.0 - getLuminosity(half3(simplex3d(noiseP))); - half4 turbulenceColor = mix(in_color, in_dimColor, luma); - float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time); + half luma = getLuminosity(half3(simplex3d(noiseP))); + half4 turbulenceColor = mix(in_color, in_effectColor, luma * in_turbulenceAlpha); + float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_sparkleMove); sparkle = min(sparkle * in_spkarkleAlpha, in_spkarkleAlpha); - return turbulenceColor + half4(half3(sparkle), 1.0); + return saturate(turbulenceColor + half4(sparkle)); } """ private const val SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 9712db8a1812..b1e5b22f9b1a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -70,10 +70,10 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener; import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.wmshell.BubblesManager; @@ -422,7 +422,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta row.getIsNonblockable(), mHighPriorityProvider.isHighPriority(row.getEntry()), mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + row.getCloseButtonOnClickListener(row)); } /** @@ -476,7 +477,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta row.getIsNonblockable(), mHighPriorityProvider.isHighPriority(row.getEntry()), mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + row.getCloseButtonOnClickListener(row)); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index 20120991b5ac..8d26f94ced21 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -202,7 +202,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G boolean isNonblockable, boolean wasShownHighPriority, AssistantFeedbackController assistantFeedbackController, - MetricsLogger metricsLogger) + MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException { mINotificationManager = iNotificationManager; mMetricsLogger = metricsLogger; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index 6e8ec9576f80..bf738aa1128f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -42,6 +42,7 @@ import android.widget.FrameLayout.LayoutParams; import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Flags; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.res.R; import com.android.systemui.statusbar.AlphaOptimizedImageView; @@ -270,6 +271,10 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl mInfoItem = createPartialConversationItem(mContext); } else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) { mInfoItem = createConversationItem(mContext); + } else if (android.app.Flags.uiRichOngoing() + && Flags.permissionHelperUiRichOngoing() + && entry.getSbn().getNotification().isPromotedOngoing()) { + mInfoItem = createPromotedItem(mContext); } else { mInfoItem = createInfoItem(mContext); } @@ -682,6 +687,16 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl R.drawable.ic_settings); } + static NotificationMenuItem createPromotedItem(Context context) { + Resources res = context.getResources(); + String infoDescription = res.getString(R.string.notification_menu_gear_description); + PromotedNotificationInfo infoContent = + (PromotedNotificationInfo) LayoutInflater.from(context).inflate( + R.layout.promoted_notification_info, null, false); + return new NotificationMenuItem(context, infoDescription, infoContent, + R.drawable.ic_settings); + } + static NotificationMenuItem createBundleItem(Context context) { Resources res = context.getResources(); String infoDescription = res.getString(R.string.notification_menu_gear_description); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java new file mode 100644 index 000000000000..e4a0fa5b534e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import android.app.INotificationManager; +import android.app.NotificationChannel; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.service.notification.StatusBarNotification; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; +import com.android.systemui.res.R; +import com.android.systemui.statusbar.notification.AssistantFeedbackController; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +/** + * The guts of a notification revealed when performing a long press, specifically + * for notifications that are shown as promoted. Contains extra controls to allow user to revoke + * app permissions for sending promoted notifications. + */ +public class PromotedNotificationInfo extends NotificationInfo { + private static final String TAG = "PromotedNotifInfoGuts"; + private INotificationManager mNotificationManager; + + public PromotedNotificationInfo(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void bindNotification( + PackageManager pm, + INotificationManager iNotificationManager, + OnUserInteractionCallback onUserInteractionCallback, + ChannelEditorDialogController channelEditorDialogController, + String pkg, + NotificationChannel notificationChannel, + NotificationEntry entry, + OnSettingsClickListener onSettingsClick, + OnAppSettingsClickListener onAppSettingsClick, + UiEventLogger uiEventLogger, + boolean isDeviceProvisioned, + boolean isNonblockable, + boolean wasShownHighPriority, + AssistantFeedbackController assistantFeedbackController, + MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException { + super.bindNotification(pm, iNotificationManager, onUserInteractionCallback, + channelEditorDialogController, pkg, notificationChannel, entry, onSettingsClick, + onAppSettingsClick, uiEventLogger, isDeviceProvisioned, isNonblockable, + wasShownHighPriority, assistantFeedbackController, metricsLogger, onCloseClick); + + mNotificationManager = iNotificationManager; + + bindDismiss(entry.getSbn(), onCloseClick); + bindDemote(entry.getSbn(), pkg); + } + + + protected void bindDismiss(StatusBarNotification sbn, + View.OnClickListener onCloseClick) { + View dismissButton = findViewById(R.id.promoted_dismiss); + + dismissButton.setOnClickListener(onCloseClick); + dismissButton.setVisibility(!sbn.isNonDismissable() + && dismissButton.hasOnClickListeners() ? VISIBLE : GONE); + + } + + protected void bindDemote(StatusBarNotification sbn, String packageName) { + View demoteButton = findViewById(R.id.promoted_demote); + demoteButton.setOnClickListener(getDemoteClickListener(sbn, packageName)); + demoteButton.setVisibility(demoteButton.hasOnClickListeners() ? VISIBLE : GONE); + } + + private OnClickListener getDemoteClickListener(StatusBarNotification sbn, String packageName) { + return ((View unusedView) -> { + try { + // TODO(b/391661009): Signal AutomaticPromotionCoordinator here + mNotificationManager.setCanBePromoted(packageName, sbn.getUid(), false, true); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't revoke live update permission", e); + } + }); + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index 1965b9538df0..f7401440cfcb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -36,6 +36,8 @@ import com.android.systemui.util.kotlin.DisposableHandles import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map /** Binds the shared notification container to its view-model. */ @SysUISingleton @@ -143,21 +145,25 @@ constructor( if (!SceneContainerFlag.isEnabled) { if (Flags.magicPortraitWallpapers()) { launch { - viewModel - .getNotificationStackAbsoluteBottom( - calculateMaxNotifications = calculateMaxNotifications, - calculateHeight = { maxNotifications -> - notificationStackSizeCalculator.computeHeight( - maxNotifs = maxNotifications, - shelfHeight = controller.getShelfHeight().toFloat(), - stack = controller.view, - ) - }, - controller.getShelfHeight().toFloat(), + combine( + viewModel.getNotificationStackAbsoluteBottom( + calculateMaxNotifications = calculateMaxNotifications, + calculateHeight = { maxNotifications -> + notificationStackSizeCalculator.computeHeight( + maxNotifs = maxNotifications, + shelfHeight = + controller.getShelfHeight().toFloat(), + stack = controller.view, + ) + }, + controller.getShelfHeight().toFloat(), + ), + viewModel.configurationBasedDimensions.map { it.marginTop }, + ::Pair, ) - .collect { bottom -> + .collect { (bottom: Float, marginTop: Int) -> keyguardInteractor.setNotificationStackAbsoluteBottom( - bottom + marginTop + bottom ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java index 16e9c717935c..a2f1ded042f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java @@ -26,24 +26,31 @@ import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; -import android.widget.TextView; import androidx.annotation.StyleRes; +import androidx.core.graphics.ColorUtils; import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Flags; import com.android.systemui.keyguard.KeyguardIndication; import com.android.systemui.res.R; +import com.android.systemui.shared.shadow.DoubleShadowTextView; /** * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open"). */ -public class KeyguardIndicationTextView extends TextView { +public class KeyguardIndicationTextView extends DoubleShadowTextView { + // Minimum luminance for texts to receive shadows. + private static final float MIN_TEXT_SHADOW_LUMINANCE = 0.5f; public static final long Y_IN_DURATION = 600L; @StyleRes private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea; @StyleRes + private static int sStyleWithDoubleShadowTextId = + R.style.TextAppearance_Keyguard_BottomArea_DoubleShadow; + @StyleRes private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button; private boolean mAnimationsEnabled = true; @@ -226,7 +233,14 @@ public class KeyguardIndicationTextView extends TextView { if (mKeyguardIndicationInfo.getBackground() != null) { setTextAppearance(sButtonStyleId); } else { - setTextAppearance(sStyleId); + // If text is transparent or dark color, don't draw any shadow + if (Flags.indicationTextA11yFix() && ColorUtils.calculateLuminance( + mKeyguardIndicationInfo.getTextColor().getDefaultColor()) + > MIN_TEXT_SHADOW_LUMINANCE) { + setTextAppearance(sStyleWithDoubleShadowTextId); + } else { + setTextAppearance(sStyleId); + } } setBackground(mKeyguardIndicationInfo.getBackground()); setTextColor(mKeyguardIndicationInfo.getTextColor()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 4c2bfe5ca257..40245aef4f67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -23,6 +23,7 @@ import static com.android.systemui.Flags.updateUserSwitcherBackground; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; @@ -56,6 +57,7 @@ import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; +import com.android.systemui.shade.ShadeDisplayAware; import com.android.systemui.shade.ShadeViewStateProvider; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; @@ -114,6 +116,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat R.id.keyguard_hun_animator_start_tag); private final CoroutineDispatcher mCoroutineDispatcher; + private final Context mContext; private final CarrierTextController mCarrierTextController; private final ConfigurationController mConfigurationController; private final SystemStatusAnimationScheduler mAnimationScheduler; @@ -129,7 +132,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private final KeyguardStatusBarViewModel mKeyguardStatusBarViewModel; private final BiometricUnlockController mBiometricUnlockController; private final SysuiStatusBarStateController mStatusBarStateController; - private final StatusBarContentInsetsProvider mInsetsProvider; + private final StatusBarContentInsetsProviderStore mInsetsProviderStore; private final UserManager mUserManager; private final StatusBarUserChipViewModel mStatusBarUserChipViewModel; private final SecureSettings mSecureSettings; @@ -314,6 +317,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat @Inject public KeyguardStatusBarViewController( @Main CoroutineDispatcher dispatcher, + @ShadeDisplayAware Context context, KeyguardStatusBarView view, CarrierTextController carrierTextController, ConfigurationController configurationController, @@ -347,6 +351,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat ) { super(view); mCoroutineDispatcher = dispatcher; + mContext = context; mCarrierTextController = carrierTextController; mConfigurationController = configurationController; mAnimationScheduler = animationScheduler; @@ -362,7 +367,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mKeyguardStatusBarViewModel = keyguardStatusBarViewModel; mBiometricUnlockController = biometricUnlockController; mStatusBarStateController = statusBarStateController; - mInsetsProvider = statusBarContentInsetsProviderStore.getDefaultDisplay(); + mInsetsProviderStore = statusBarContentInsetsProviderStore; mUserManager = userManager; mStatusBarUserChipViewModel = userChipViewModel; mSecureSettings = secureSettings; @@ -404,6 +409,10 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mStatusOverlayHoverListenerFactory = statusOverlayHoverListenerFactory; } + private StatusBarContentInsetsProvider insetsProvider() { + return mInsetsProviderStore.forDisplay(mContext.getDisplayId()); + } + @Override protected void onInit() { super.onInit(); @@ -446,7 +455,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat .createDarkAwareListener(mSystemIconsContainer, mView.darkChangeFlow()); mSystemIconsContainer.setOnHoverListener(hoverListener); mView.setOnApplyWindowInsetsListener( - (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider)); + (view, windowInsets) -> mView.updateWindowInsets(windowInsets, insetsProvider())); mSecureSettings.registerContentObserverForUserSync( Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, false, @@ -645,7 +654,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat * {@code OnApplyWindowInsetsListener}s. */ public void setDisplayCutout(@Nullable DisplayCutout displayCutout) { - mView.setDisplayCutout(displayCutout, mInsetsProvider); + mView.setDisplayCutout(displayCutout, insetsProvider()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 3f44f7bdef90..caf8a43b2aaf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -46,7 +46,6 @@ import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore -import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider import com.android.systemui.statusbar.policy.Clock import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.window.StatusBarWindowStateController @@ -84,7 +83,7 @@ private constructor( private val configurationController: ConfigurationController, private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, private val darkIconDispatcher: DarkIconDispatcher, - private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider, + private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore, private val lazyStatusBarShadeDisplayPolicy: Lazy<StatusBarTouchShadeDisplayPolicy>, ) : ViewController<PhoneStatusBarView>(view) { @@ -92,6 +91,8 @@ private constructor( private lateinit var clock: Clock private lateinit var startSideContainer: View private lateinit var endSideContainer: View + private val statusBarContentInsetsProvider + get() = statusBarContentInsetsProviderStore.forDisplay(context.displayId) private val iconsOnTouchListener = object : View.OnTouchListener { @@ -189,11 +190,9 @@ private constructor( init { // These should likely be done in `onInit`, not `init`. mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler()) - mView.setHasCornerCutoutFetcher { - statusBarContentInsetsProvider.currentRotationHasCornerCutout() - } - mView.setInsetsFetcher { - statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation() + statusBarContentInsetsProvider?.let { + mView.setHasCornerCutoutFetcher { it.currentRotationHasCornerCutout() } + mView.setInsetsFetcher { it.getStatusBarContentInsetsForCurrentRotation() } } mView.init(userChipViewModel) } @@ -393,7 +392,7 @@ private constructor( configurationController, statusOverlayHoverListenerFactory, darkIconDispatcher, - statusBarContentInsetsProviderStore.defaultDisplay, + statusBarContentInsetsProviderStore, lazyStatusBarShadeDisplayPolicy, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 0f6c3069609e..be48c3d928f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -66,6 +66,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.ScrimAlpha; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; +import com.android.systemui.keyguard.ui.transitions.BlurConfig; import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.res.R; @@ -258,6 +259,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private int mScrimsVisibility; private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener; private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; + private final BlurConfig mBlurConfig; private Consumer<Integer> mScrimVisibleListener; private boolean mBlankScreen; private boolean mScreenBlankingCallbackCalled; @@ -339,9 +341,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump KeyguardTransitionInteractor keyguardTransitionInteractor, KeyguardInteractor keyguardInteractor, @Main CoroutineDispatcher mainDispatcher, - LargeScreenShadeInterpolator largeScreenShadeInterpolator) { + LargeScreenShadeInterpolator largeScreenShadeInterpolator, + BlurConfig blurConfig) { mScrimStateListener = lightBarController::setScrimState; mLargeScreenShadeInterpolator = largeScreenShadeInterpolator; + mBlurConfig = blurConfig; // All scrims default alpha need to match bouncer background alpha to make sure the // transitions involving the bouncer are smooth and don't overshoot the bouncer alpha. mDefaultScrimAlpha = @@ -406,7 +410,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump final ScrimState[] states = ScrimState.values(); for (int i = 0; i < states.length; i++) { - states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager); + states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager, mBlurConfig); states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard); states[i].setDefaultScrimAlpha(mDefaultScrimAlpha); } @@ -868,7 +872,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump * bounds instead. */ public void setClipsQsScrim(boolean clipScrim) { - if (Flags.notificationShadeBlur()) { + if (Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()) { // Never clip scrims when blur is enabled, colors of UI elements are supposed to "add" // up across the scrims. mClipsQsScrim = false; @@ -1210,6 +1214,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump dispatchBackScrimState(mScrimBehind.getViewAlpha()); } + if (Flags.bouncerUiRevamp()) { + // Blur the notification scrim as needed. The blur is needed only when we show the + // expanded shade behind the bouncer. Without it, the notification scrim outline is + // visible behind the bouncer. + mNotificationsScrim.setBlurRadius(mState.getNotifBlurRadius()); + } // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim. boolean hideFlagShowWhenLockedActivities = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 8170e6d91a0f..5f423cf35edd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -24,6 +24,7 @@ import android.graphics.Color; import com.android.app.tracing.coroutines.TrackTracer; import com.android.systemui.Flags; import com.android.systemui.dock.DockManager; +import com.android.systemui.keyguard.ui.transitions.BlurConfig; import com.android.systemui.res.R; import com.android.systemui.scrim.ScrimView; import com.android.systemui.shade.ui.ShadeColors; @@ -149,7 +150,14 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { if (Flags.bouncerUiRevamp()) { - mBehindAlpha = 0f; + if (previousState == SHADE_LOCKED) { + mBehindAlpha = previousState.getBehindAlpha(); + mNotifAlpha = previousState.getNotifAlpha(); + mNotifBlurRadius = mBlurConfig.getMaxBlurRadiusPx(); + } else { + mNotifAlpha = 0f; + mBehindAlpha = 0f; + } mFrontAlpha = TRANSPARENT_BOUNCER_SCRIM_ALPHA; mFrontTint = mSurfaceColor; return; @@ -395,6 +403,7 @@ public enum ScrimState { DozeParameters mDozeParameters; DockManager mDockManager; boolean mDisplayRequiresBlanking; + protected BlurConfig mBlurConfig; boolean mLaunchingAffordanceWithPreview; boolean mOccludeAnimationPlaying; boolean mWakeLockScreenSensorActive; @@ -403,8 +412,12 @@ public enum ScrimState { boolean mClipQsScrim; int mBackgroundColor; + // This is needed to blur the scrim behind the scrimmed bouncer to avoid showing + // the notification section border + protected float mNotifBlurRadius = 0.0f; + public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters, - DockManager dockManager) { + DockManager dockManager, BlurConfig blurConfig) { mBackgroundColor = scrimBehind.getContext().getColor(R.color.shade_scrim_background_dark); mScrimInFront = scrimInFront; mScrimBehind = scrimBehind; @@ -412,6 +425,7 @@ public enum ScrimState { mDozeParameters = dozeParameters; mDockManager = dockManager; mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking(); + mBlurConfig = blurConfig; } /** Prepare state for transition. */ @@ -518,4 +532,8 @@ public enum ScrimState { public void setClipQsScrim(boolean clipsQsScrim) { mClipQsScrim = clipsQsScrim; } + + public float getNotifBlurRadius() { + return mNotifBlurRadius; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt index 705a11df83fc..e12b21eb2c4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt @@ -1,6 +1,7 @@ package com.android.systemui.statusbar.phone import android.view.View +import com.android.systemui.Flags import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.TransitionAnimator import com.android.systemui.animation.TransitionAnimator.Companion.getProgress @@ -22,7 +23,7 @@ class StatusBarTransitionAnimatorController( private val notificationShadeWindowController: NotificationShadeWindowController, private val commandQueue: CommandQueue, @DisplayId private val displayId: Int, - private val isLaunchForActivity: Boolean = true + private val isLaunchForActivity: Boolean = true, ) : ActivityTransitionAnimator.Controller by delegate { private var hideIconsDuringLaunchAnimation: Boolean = true @@ -41,8 +42,16 @@ class StatusBarTransitionAnimatorController( } override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - delegate.onTransitionAnimationStart(isExpandingFullyAbove) - shadeAnimationInteractor.setIsLaunchingActivity(true) + if (Flags.shadeLaunchAccessibility()) { + // We set this before calling the delegate to make sure that accessibility is disabled + // for the whole duration of the transition, so that we don't have stray TalkBack events + // once the animating view becomes invisible. + shadeAnimationInteractor.setIsLaunchingActivity(true) + delegate.onTransitionAnimationStart(isExpandingFullyAbove) + } else { + delegate.onTransitionAnimationStart(isExpandingFullyAbove) + shadeAnimationInteractor.setIsLaunchingActivity(true) + } if (!isExpandingFullyAbove) { shadeController.collapseWithDuration( ActivityTransitionAnimator.TIMINGS.totalDuration.toInt() @@ -59,7 +68,7 @@ class StatusBarTransitionAnimatorController( override fun onTransitionAnimationProgress( state: TransitionAnimator.State, progress: Float, - linearProgress: Float + linearProgress: Float, ) { delegate.onTransitionAnimationProgress(state, progress, linearProgress) val hideIcons = @@ -67,7 +76,7 @@ class StatusBarTransitionAnimatorController( ActivityTransitionAnimator.TIMINGS, linearProgress, ANIMATION_DELAY_ICON_FADE_IN, - 100 + 100, ) == 0.0f if (hideIcons != hideIconsDuringLaunchAnimation) { hideIconsDuringLaunchAnimation = hideIcons 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 39b434ad65f1..83b7c1818341 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt @@ -16,7 +16,6 @@ package com.android.systemui.volume.dialog -import android.app.Dialog import android.content.Context import android.graphics.PixelFormat import android.os.Bundle @@ -24,6 +23,7 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.WindowManager +import androidx.activity.ComponentDialog import com.android.app.tracing.coroutines.coroutineScopeTraced import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.lifecycle.repeatWhenAttached @@ -40,7 +40,7 @@ constructor( @Application context: Context, private val componentFactory: VolumeDialogComponent.Factory, private val visibilityInteractor: VolumeDialogVisibilityInteractor, -) : Dialog(context, R.style.Theme_SystemUI_Dialog_Volume) { +) : ComponentDialog(context, R.style.Theme_SystemUI_Dialog_Volume) { init { with(window!!) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt index 8c018606ebda..e261ceebf33e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt @@ -17,6 +17,8 @@ package com.android.systemui.volume.dialog.domain.interactor import android.annotation.SuppressLint +import android.media.AudioManager.RINGER_MODE_NORMAL +import android.media.AudioManager.RINGER_MODE_SILENT import android.os.Handler import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.VolumeDialogController @@ -60,10 +62,10 @@ constructor( awaitClose { volumeDialogController.removeCallback(producer) } } .buffer(capacity = BUFFER_CAPACITY, onBufferOverflow = BufferOverflow.DROP_OLDEST) - .shareIn(replay = 0, scope = coroutineScope, started = SharingStarted.WhileSubscribed()) + .shareIn(replay = 0, scope = coroutineScope, started = SharingStarted.Eagerly) .onStart { emit(VolumeDialogEventModel.SubscribedToEvents) } - private class VolumeDialogEventModelProducer( + private inner class VolumeDialogEventModelProducer( private val scope: ProducerScope<VolumeDialogEventModel> ) : VolumeDialogController.Callbacks { override fun onShowRequested(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int) { @@ -93,14 +95,6 @@ constructor( // Configuration change is never emitted by the VolumeDialogControllerImpl now. override fun onConfigurationChanged() = Unit - override fun onShowVibrateHint() { - scope.trySend(VolumeDialogEventModel.ShowVibrateHint) - } - - override fun onShowSilentHint() { - scope.trySend(VolumeDialogEventModel.ShowSilentHint) - } - override fun onScreenOff() { scope.trySend(VolumeDialogEventModel.ScreenOff) } @@ -113,16 +107,6 @@ constructor( scope.trySend(VolumeDialogEventModel.AccessibilityModeChanged(showA11yStream == true)) } - // Captions button is remove from the Volume Dialog - override fun onCaptionComponentStateChanged( - isComponentEnabled: Boolean, - fromTooltip: Boolean, - ) = Unit - - // Captions button is remove from the Volume Dialog - override fun onCaptionEnabledStateChanged(isEnabled: Boolean, checkBeforeSwitch: Boolean) = - Unit - override fun onShowCsdWarning(csdWarning: Int, durationMs: Int) { scope.trySend( VolumeDialogEventModel.ShowCsdWarning( @@ -135,5 +119,25 @@ constructor( override fun onVolumeChangedFromKey() { scope.trySend(VolumeDialogEventModel.VolumeChangedFromKey) } + + // This should've been handled in side the controller itself. + override fun onShowVibrateHint() { + volumeDialogController.setRingerMode(RINGER_MODE_SILENT, false) + } + + // This should've been handled in side the controller itself. + override fun onShowSilentHint() { + volumeDialogController.setRingerMode(RINGER_MODE_NORMAL, false) + } + + // Captions button is remove from the Volume Dialog + override fun onCaptionComponentStateChanged( + isComponentEnabled: Boolean, + fromTooltip: Boolean, + ) = Unit + + // Captions button is remove from the Volume Dialog + override fun onCaptionEnabledStateChanged(isEnabled: Boolean, checkBeforeSwitch: Boolean) = + Unit } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt index 9793d2be6b98..a0214dc957a4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt @@ -38,10 +38,6 @@ sealed interface VolumeDialogEventModel { data class LayoutDirectionChanged(val layoutDirection: Int) : VolumeDialogEventModel - data object ShowVibrateHint : VolumeDialogEventModel - - data object ShowSilentHint : VolumeDialogEventModel - data object ScreenOff : VolumeDialogEventModel data class ShowSafetyWarning(val flags: Int) : VolumeDialogEventModel diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt index 40719185e290..73f6236393b2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt @@ -47,7 +47,6 @@ constructor( private val audioSystemRepository: AudioSystemRepository, private val ringerFeedbackRepository: VolumeDialogRingerFeedbackRepository, ) { - val ringerModel: Flow<VolumeDialogRingerModel> = volumeDialogStateInteractor.volumeDialogState .mapNotNull { toRingerModel(it) } 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 940c79c78d76..577e47bb3b83 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 @@ -18,7 +18,6 @@ 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.VolumeDialogSliderHapticsViewBinder import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder import dagger.BindsInstance import dagger.Subcomponent @@ -33,8 +32,6 @@ interface VolumeDialogSliderComponent { fun sliderViewBinder(): VolumeDialogSliderViewBinder - fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder - fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder @Subcomponent.Factory diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt index 82885d65c513..07954f850286 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt @@ -16,8 +16,8 @@ package com.android.systemui.volume.dialog.sliders.data.repository -import android.view.MotionEvent import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope +import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -26,10 +26,11 @@ import kotlinx.coroutines.flow.filterNotNull @VolumeDialogSliderScope class VolumeDialogSliderTouchEventsRepository @Inject constructor() { - private val mutableSliderTouchEvents: MutableStateFlow<MotionEvent?> = MutableStateFlow(null) - val sliderTouchEvent: Flow<MotionEvent> = mutableSliderTouchEvents.filterNotNull() + private val mutableSliderTouchEvents: MutableStateFlow<SliderInputEvent.Touch?> = + MutableStateFlow(null) + val sliderTouchEvent: Flow<SliderInputEvent.Touch> = mutableSliderTouchEvents.filterNotNull() - fun update(event: MotionEvent) { - mutableSliderTouchEvents.tryEmit(MotionEvent.obtain(event)) + fun update(touch: SliderInputEvent.Touch) { + mutableSliderTouchEvents.value = touch } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt index c7b4184a9f2f..351832bd275a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt @@ -16,7 +16,6 @@ package com.android.systemui.volume.dialog.sliders.domain.interactor -import android.view.MotionEvent import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogCallbacksInteractor import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor @@ -45,7 +44,7 @@ constructor( val event: Flow<SliderInputEvent> = merge( - repository.sliderTouchEvent.map { SliderInputEvent.Touch(it) }, + repository.sliderTouchEvent, volumeDialogCallbacksInteractor.event .filterIsInstance(VolumeDialogEventModel.VolumeChangedFromKey::class) .map { SliderInputEvent.Button }, @@ -55,7 +54,7 @@ constructor( event.onEach { visibilityInteractor.resetDismissTimeout() }.launchIn(coroutineScope) } - fun onTouchEvent(newEvent: MotionEvent) { - repository.update(newEvent) + fun onTouchEvent(pointerEvent: SliderInputEvent.Touch) { + repository.update(pointerEvent) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt index 37dbb4b3a81d..841730857d71 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt @@ -16,12 +16,20 @@ package com.android.systemui.volume.dialog.sliders.shared.model -import android.view.MotionEvent - /** Models input event happened on the Volume Slider */ sealed interface SliderInputEvent { - data class Touch(val event: MotionEvent) : SliderInputEvent + interface Touch : SliderInputEvent { + + val x: Float + val y: Float + + data class Start(override val x: Float, override val y: Float) : Touch + + data class Move(override val x: Float, override val y: Float) : Touch + + data class End(override val x: Float, override val y: Float) : Touch + } data object Button : SliderInputEvent } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt index 8109b50aa34a..38feb69aad7b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt @@ -20,11 +20,9 @@ import android.view.View import androidx.dynamicanimation.animation.FloatValueHolder import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce -import com.android.systemui.res.R import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel.OverscrollEventModel -import com.google.android.material.slider.Slider import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn @@ -51,10 +49,6 @@ constructor(private val viewModel: VolumeDialogOverscrollViewModel) { ) .addUpdateListener { _, value, _ -> viewsToAnimate.setTranslationY(value) } - view.requireViewById<Slider>(R.id.volume_dialog_slider).addOnChangeListener { s, value, _ -> - viewModel.setSlider(value = value, min = s.valueFrom, max = s.valueTo) - } - viewModel.overscrollEvent .onEach { event -> when (event) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt deleted file mode 100644 index 5a7fbc6341f2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume.dialog.sliders.ui - -import android.view.View -import com.android.systemui.haptics.slider.HapticSlider -import com.android.systemui.haptics.slider.HapticSliderPlugin -import com.android.systemui.res.R -import com.android.systemui.statusbar.VibratorHelper -import com.android.systemui.util.time.SystemClock -import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope -import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel -import com.google.android.material.slider.Slider -import com.google.android.msdl.domain.MSDLPlayer -import javax.inject.Inject -import kotlin.math.roundToInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach - -@VolumeDialogSliderScope -class VolumeDialogSliderHapticsViewBinder -@Inject -constructor( - private val inputEventsViewModel: VolumeDialogSliderInputEventsViewModel, - private val vibratorHelper: VibratorHelper, - private val msdlPlayer: MSDLPlayer, - private val systemClock: SystemClock, -) { - - fun CoroutineScope.bind(view: View) { - val sliderView = view.requireViewById<Slider>(R.id.volume_dialog_slider) - val hapticSliderPlugin = - HapticSliderPlugin( - slider = HapticSlider.Slider(sliderView), - vibratorHelper = vibratorHelper, - msdlPlayer = msdlPlayer, - systemClock = systemClock, - ) - hapticSliderPlugin.startInScope(this) - - sliderView.addOnChangeListener { _, value, fromUser -> - hapticSliderPlugin.onProgressChanged(value.roundToInt(), fromUser) - } - sliderView.addOnSliderTouchListener( - object : Slider.OnSliderTouchListener { - - override fun onStartTrackingTouch(slider: Slider) { - hapticSliderPlugin.onStartTrackingTouch() - } - - override fun onStopTrackingTouch(slider: Slider) { - hapticSliderPlugin.onStopTrackingTouch() - } - } - ) - - inputEventsViewModel.event - .onEach { - when (it) { - is SliderInputEvent.Button -> hapticSliderPlugin.onKeyDown() - is SliderInputEvent.Touch -> hapticSliderPlugin.onTouchEvent(it.event) - } - } - .launchIn(this) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index d40302408dd6..21a392776235 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -16,96 +16,211 @@ package com.android.systemui.volume.dialog.sliders.ui -import android.annotation.SuppressLint +import android.graphics.drawable.Drawable import android.view.View -import androidx.dynamicanimation.animation.FloatPropertyCompat -import androidx.dynamicanimation.animation.SpringAnimation -import androidx.dynamicanimation.animation.SpringForce +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.SliderState +import androidx.compose.material3.VerticalSlider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.ui.graphics.painter.DrawablePainter +import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel +import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.res.R import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel +import com.android.systemui.volume.dialog.sliders.ui.compose.VolumeDialogSliderTrack +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel -import com.google.android.material.slider.Slider -import com.google.android.material.slider.Slider.OnSliderTouchListener +import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider import javax.inject.Inject +import kotlin.math.round import kotlin.math.roundToInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.isActive @VolumeDialogSliderScope class VolumeDialogSliderViewBinder @Inject constructor( private val viewModel: VolumeDialogSliderViewModel, - private val inputViewModel: VolumeDialogSliderInputEventsViewModel, + private val overscrollViewModel: VolumeDialogOverscrollViewModel, + private val hapticsViewModelFactory: SliderHapticsViewModel.Factory, ) { + fun bind(view: View) { + val sliderComposeView: ComposeView = view.requireViewById(R.id.volume_dialog_slider) + sliderComposeView.setContent { + VolumeDialogSlider( + viewModel = viewModel, + overscrollViewModel = overscrollViewModel, + hapticsViewModelFactory = + if (com.android.systemui.Flags.hapticsForComposeSliders()) { + hapticsViewModelFactory + } else { + null + }, + ) + } + } +} - private val sliderValueProperty = - object : FloatPropertyCompat<Slider>("value") { - override fun getValue(slider: Slider): Float = slider.value +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +private fun VolumeDialogSlider( + viewModel: VolumeDialogSliderViewModel, + overscrollViewModel: VolumeDialogOverscrollViewModel, + hapticsViewModelFactory: SliderHapticsViewModel.Factory?, + modifier: Modifier = Modifier, +) { + + val colors = + SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.primary, + activeTickColor = MaterialTheme.colorScheme.surfaceContainerHighest, + inactiveTickColor = MaterialTheme.colorScheme.primary, + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest, + ) + val collectedSliderState by viewModel.state.collectAsStateWithLifecycle(null) + val sliderState = collectedSliderState ?: return - override fun setValue(slider: Slider, value: Float) { - slider.value = value + val interactionSource = remember { MutableInteractionSource() } + val hapticsViewModel: SliderHapticsViewModel? = + hapticsViewModelFactory?.let { + rememberViewModel(traceName = "SliderHapticsViewModel") { + it.create( + interactionSource, + sliderState.valueRange, + Orientation.Vertical, + VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(sliderState.valueRange), + VolumeHapticsConfigsProvider.seekableSliderTrackerConfig, + ) } } - private val springForce = - SpringForce().apply { - stiffness = SpringForce.STIFFNESS_MEDIUM - dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY - } - @SuppressLint("ClickableViewAccessibility") - fun CoroutineScope.bind(view: View) { - var isInitialUpdate = true - val sliderView: Slider = view.requireViewById(R.id.volume_dialog_slider) - val animation = SpringAnimation(sliderView, sliderValueProperty) - animation.spring = springForce - sliderView.setOnTouchListener { _, event -> - inputViewModel.onTouchEvent(event) - false - } - sliderView.addOnChangeListener { _, value, fromUser -> - viewModel.setStreamVolume(value.roundToInt(), fromUser) + val state = + remember(sliderState.valueRange) { + SliderState( + value = sliderState.value, + valueRange = sliderState.valueRange, + steps = + (sliderState.valueRange.endInclusive - sliderState.valueRange.start - 1) + .toInt(), + ) + .apply { + onValueChangeFinished = { + viewModel.onStreamChangeFinished(value.roundToInt()) + hapticsViewModel?.onValueChangeEnded() + } + setOnValueChangeListener { + value = it + hapticsViewModel?.addVelocityDataPoint(it) + overscrollViewModel.setSlider( + value = value, + min = valueRange.start, + max = valueRange.endInclusive, + ) + viewModel.setStreamVolume(it, true) + } + } } - sliderView.addOnSliderTouchListener( - object : OnSliderTouchListener { - override fun onStartTrackingTouch(slider: Slider) {} + var lastDiscreteStep by remember { mutableFloatStateOf(round(sliderState.value)) } + LaunchedEffect(sliderState.value) { + state.value = sliderState.value + snapshotFlow { sliderState.value } + .map { round(it) } + .filter { it != lastDiscreteStep } + .distinctUntilChanged() + .collect { discreteStep -> + lastDiscreteStep = discreteStep + hapticsViewModel?.onValueChange(discreteStep) + } + } - override fun onStopTrackingTouch(slider: Slider) { - viewModel.onStreamChangeFinished(slider.value.roundToInt()) + VerticalSlider( + state = state, + enabled = !sliderState.isDisabled, + reverseDirection = true, + colors = colors, + interactionSource = interactionSource, + modifier = + modifier.pointerInput(Unit) { + coroutineScope { + val currentContext = currentCoroutineContext() + awaitPointerEventScope { + while (currentContext.isActive) { + viewModel.onTouchEvent(awaitPointerEvent()) + } + } } - } - ) + }, + track = { + VolumeDialogSliderTrack( + state, + colors = colors, + isEnabled = !sliderState.isDisabled, + activeTrackEndIcon = { iconsState -> + VolumeIcon(sliderState.icon, iconsState.isActiveTrackEndIconVisible) + }, + inactiveTrackEndIcon = { iconsState -> + VolumeIcon(sliderState.icon, !iconsState.isActiveTrackEndIconVisible) + }, + ) + }, + ) +} - viewModel.isDisabledByZenMode.onEach { sliderView.isEnabled = !it }.launchIn(this) - viewModel.state - .onEach { - sliderView.setModel(it, animation, isInitialUpdate) - isInitialUpdate = false - } - .launchIn(this) +@Composable +private fun BoxScope.VolumeIcon( + drawable: Drawable, + isVisible: Boolean, + modifier: Modifier = Modifier, +) { + AnimatedVisibility( + visible = isVisible, + enter = fadeIn(animationSpec = tween(durationMillis = 50)), + exit = fadeOut(animationSpec = tween(durationMillis = 50)), + modifier = modifier.align(Alignment.Center).size(40.dp).padding(10.dp), + ) { + Icon(painter = DrawablePainter(drawable), contentDescription = null) } +} - @SuppressLint("UseCompatLoadingForDrawables") - private fun Slider.setModel( - model: VolumeDialogSliderStateModel, - animation: SpringAnimation, - isInitialUpdate: Boolean, - ) { - valueFrom = model.minValue - animation.setMinValue(model.minValue) - valueTo = model.maxValue - animation.setMaxValue(model.maxValue) - // coerce the current value to the new value range before animating it. This prevents - // animating from the value that is outside of current [valueFrom, valueTo]. - value = value.coerceIn(valueFrom, valueTo) - trackIconActiveStart = model.icon - if (isInitialUpdate) { - value = model.value - } else { - animation.animateToFinalPosition(model.value) - } +@OptIn(ExperimentalMaterial3Api::class) +fun SliderState.setOnValueChangeListener(onValueChange: ((Float) -> Unit)?) { + with(javaClass.getDeclaredField("onValueChange")) { + val oldIsAccessible = isAccessible + AutoCloseable { isAccessible = oldIsAccessible } + .use { + isAccessible = true + set(this@setOnValueChangeListener, onValueChange) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt index 75d427acc05b..c66955a0c187 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt @@ -71,7 +71,6 @@ constructor(private val viewModel: VolumeDialogSlidersViewModel) { viewsToAnimate: Array<View>, ) { with(component.sliderViewBinder()) { bind(sliderContainer) } - with(component.sliderHapticsViewBinder()) { bind(sliderContainer) } with(component.overscrollViewBinder()) { bind(sliderContainer, viewsToAnimate) } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt new file mode 100644 index 000000000000..1dd9ddac79be --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt @@ -0,0 +1,347 @@ +/* + * 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.sliders.ui.compose + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.SliderColors +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.SliderState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasurePolicy +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.layout.Placeable +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastFilter +import androidx.compose.ui.util.fastFirst +import kotlin.math.min + +@Composable +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +fun VolumeDialogSliderTrack( + sliderState: SliderState, + colors: SliderColors, + isEnabled: Boolean, + modifier: Modifier = Modifier, + thumbTrackGapSize: Dp = 6.dp, + trackCornerSize: Dp = 12.dp, + trackInsideCornerSize: Dp = 2.dp, + trackSize: Dp = 40.dp, + activeTrackStartIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null, + activeTrackEndIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null, + inactiveTrackStartIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null, + inactiveTrackEndIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null, +) { + val measurePolicy = remember(sliderState) { TrackMeasurePolicy(sliderState) } + Layout( + measurePolicy = measurePolicy, + content = { + SliderDefaults.Track( + sliderState = sliderState, + colors = colors, + enabled = isEnabled, + trackCornerSize = trackCornerSize, + trackInsideCornerSize = trackInsideCornerSize, + drawStopIndicator = null, + thumbTrackGapSize = thumbTrackGapSize, + drawTick = { _, _ -> }, + modifier = Modifier.width(trackSize).layoutId(Contents.Track), + ) + + TrackIcon( + icon = activeTrackStartIcon, + contentsId = Contents.Active.TrackStartIcon, + isEnabled = isEnabled, + colors = colors, + state = measurePolicy, + ) + TrackIcon( + icon = activeTrackEndIcon, + contentsId = Contents.Active.TrackEndIcon, + isEnabled = isEnabled, + colors = colors, + state = measurePolicy, + ) + TrackIcon( + icon = inactiveTrackStartIcon, + contentsId = Contents.Inactive.TrackStartIcon, + isEnabled = isEnabled, + colors = colors, + state = measurePolicy, + ) + TrackIcon( + icon = inactiveTrackEndIcon, + contentsId = Contents.Inactive.TrackEndIcon, + isEnabled = isEnabled, + colors = colors, + state = measurePolicy, + ) + }, + modifier = modifier, + ) +} + +@Composable +private fun TrackIcon( + icon: (@Composable BoxScope.(sliderIconsState: SliderIconsState) -> Unit)?, + isEnabled: Boolean, + contentsId: Contents, + state: SliderIconsState, + colors: SliderColors, + modifier: Modifier = Modifier, +) { + icon ?: return + Box(modifier = modifier.layoutId(contentsId).fillMaxSize()) { + CompositionLocalProvider( + LocalContentColor provides contentsId.getColor(colors, isEnabled) + ) { + icon(state) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +private class TrackMeasurePolicy(private val sliderState: SliderState) : + MeasurePolicy, SliderIconsState { + + private val isVisible: Map<Contents, MutableState<Boolean>> = + mutableMapOf( + Contents.Active.TrackStartIcon to mutableStateOf(false), + Contents.Active.TrackEndIcon to mutableStateOf(false), + Contents.Inactive.TrackStartIcon to mutableStateOf(false), + Contents.Inactive.TrackEndIcon to mutableStateOf(false), + ) + + override val isActiveTrackStartIconVisible: Boolean + get() = isVisible.getValue(Contents.Active.TrackStartIcon).value + + override val isActiveTrackEndIconVisible: Boolean + get() = isVisible.getValue(Contents.Active.TrackEndIcon).value + + override val isInactiveTrackStartIconVisible: Boolean + get() = isVisible.getValue(Contents.Inactive.TrackStartIcon).value + + override val isInactiveTrackEndIconVisible: Boolean + get() = isVisible.getValue(Contents.Inactive.TrackEndIcon).value + + override fun MeasureScope.measure( + measurables: List<Measurable>, + constraints: Constraints, + ): MeasureResult { + val track = measurables.fastFirst { it.layoutId == Contents.Track }.measure(constraints) + + val iconSize = min(track.width, track.height) + val iconConstraints = constraints.copy(maxWidth = iconSize, maxHeight = iconSize) + + val icons = + measurables + .fastFilter { it.layoutId != Contents.Track } + .associateBy( + keySelector = { it.layoutId as Contents }, + valueTransform = { it.measure(iconConstraints) }, + ) + + return layout(track.width, track.height) { + with(Contents.Track) { + performPlacing( + placeable = track, + width = track.width, + height = track.height, + sliderState = sliderState, + ) + } + + for (iconLayoutId in icons.keys) { + with(iconLayoutId) { + performPlacing( + placeable = icons.getValue(iconLayoutId), + width = track.width, + height = track.height, + sliderState = sliderState, + ) + + isVisible.getValue(iconLayoutId).value = + isVisible( + placeable = icons.getValue(iconLayoutId), + width = track.width, + height = track.height, + sliderState = sliderState, + ) + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +private sealed interface Contents { + + data object Track : Contents { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) = placeable.place(x = 0, y = 0) + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) = true + + override fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color = + error("Unsupported") + } + + interface Active : Contents { + override fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color { + return if (isEnabled) { + sliderColors.activeTickColor + } else { + sliderColors.disabledActiveTickColor + } + } + + data object TrackStartIcon : Active { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) = + placeable.place( + x = 0, + y = (height * (1 - sliderState.coercedValueAsFraction)).toInt(), + ) + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ): Boolean = (height * (sliderState.coercedValueAsFraction)).toInt() > placeable.height + } + + data object TrackEndIcon : Active { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) = placeable.place(x = 0, y = (height - placeable.height)) + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ): Boolean = (height * (sliderState.coercedValueAsFraction)).toInt() > placeable.height + } + } + + interface Inactive : Contents { + + override fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color { + return if (isEnabled) { + sliderColors.inactiveTickColor + } else { + sliderColors.disabledInactiveTickColor + } + } + + data object TrackStartIcon : Inactive { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) { + placeable.place(x = 0, y = 0) + } + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ): Boolean = + (height * (1 - sliderState.coercedValueAsFraction)).toInt() > placeable.height + } + + data object TrackEndIcon : Inactive { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) { + placeable.place( + x = 0, + y = + (height * (1 - sliderState.coercedValueAsFraction)).toInt() - + placeable.height, + ) + } + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ): Boolean = + (height * (1 - sliderState.coercedValueAsFraction)).toInt() > placeable.height + } + } + + fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) + + fun isVisible(placeable: Placeable, width: Int, height: Int, sliderState: SliderState): Boolean + + fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color +} + +/** Provides visibility state for each of the Slider's icons. */ +interface SliderIconsState { + val isActiveTrackStartIconVisible: Boolean + val isActiveTrackEndIconVisible: Boolean + val isInactiveTrackStartIconVisible: Boolean + val isInactiveTrackEndIconVisible: Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt index 0d41860d9f57..0fdf5d6266d0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt @@ -95,18 +95,17 @@ constructor( private fun overscrollEvents(direction: Float): Flow<OverscrollEventModel> { var startPosition: Float? = null return inputEventsInteractor.event - .mapNotNull { (it as? SliderInputEvent.Touch)?.event } + .mapNotNull { it as? SliderInputEvent.Touch } .transform { touchEvent -> // Skip events from inside the slider bounds for the case when the user adjusts - // slider - // towards max when the slider is already on max value. - if (touchEvent.isFinalEvent()) { + // slider towards max when the slider is already on max value. + if (touchEvent is SliderInputEvent.Touch.End) { startPosition = null emit(OverscrollEventModel.Animate(0f)) return@transform } val currentStartPosition = startPosition - val newPosition: Float = touchEvent.rawY + val newPosition: Float = touchEvent.y if (currentStartPosition == null) { startPosition = newPosition } else { @@ -126,11 +125,6 @@ constructor( } } - /** @return true when the [MotionEvent] indicates the end of the gesture. */ - private fun MotionEvent.isFinalEvent(): Boolean { - return actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL - } - /** Models overscroll event */ sealed interface OverscrollEventModel { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt deleted file mode 100644 index 755776ac9723..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume.dialog.sliders.ui.viewmodel - -import android.view.MotionEvent -import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog -import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope -import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInputEventsInteractor -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.stateIn - -@VolumeDialogSliderScope -class VolumeDialogSliderInputEventsViewModel -@Inject -constructor( - @VolumeDialog private val coroutineScope: CoroutineScope, - private val interactor: VolumeDialogSliderInputEventsInteractor, -) { - - val event = - interactor.event.stateIn(coroutineScope, SharingStarted.Eagerly, null).filterNotNull() - - fun onTouchEvent(event: MotionEvent) { - interactor.onTouchEvent(event) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt index 8df9e788905c..b01046b377b0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt @@ -20,17 +20,20 @@ import android.graphics.drawable.Drawable import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel data class VolumeDialogSliderStateModel( - val minValue: Float, - val maxValue: Float, val value: Float, + val isDisabled: Boolean, + val valueRange: ClosedFloatingPointRange<Float>, val icon: Drawable, ) -fun VolumeDialogStreamModel.toStateModel(icon: Drawable): VolumeDialogSliderStateModel { +fun VolumeDialogStreamModel.toStateModel( + isDisabled: Boolean, + icon: Drawable, +): VolumeDialogSliderStateModel { return VolumeDialogSliderStateModel( - minValue = levelMin.toFloat(), value = level.toFloat(), - maxValue = levelMax.toFloat(), + isDisabled = isDisabled, + valueRange = levelMin.toFloat()..levelMax.toFloat(), icon = icon, ) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt index a752f1f78e74..e89d5ab53560 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt @@ -16,6 +16,9 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.PointerEvent +import androidx.compose.ui.input.pointer.PointerEventType import com.android.systemui.util.time.SystemClock import com.android.systemui.volume.Events import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog @@ -23,20 +26,23 @@ import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibili import com.android.systemui.volume.dialog.shared.VolumeDialogLogger import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope +import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInputEventsInteractor import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType +import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent import javax.inject.Inject +import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn @@ -62,6 +68,7 @@ constructor( private val visibilityInteractor: VolumeDialogVisibilityInteractor, @VolumeDialog private val coroutineScope: CoroutineScope, private val volumeDialogSliderIconProvider: VolumeDialogSliderIconProvider, + private val inputEventsInteractor: VolumeDialogSliderInputEventsInteractor, private val systemClock: SystemClock, private val logger: VolumeDialogLogger, ) { @@ -77,11 +84,12 @@ constructor( .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() - val isDisabledByZenMode: Flow<Boolean> = interactor.isDisabledByZenMode val state: Flow<VolumeDialogSliderStateModel> = - model - .flatMapLatest { streamModel -> - with(streamModel) { + combine( + interactor.isDisabledByZenMode, + model, + model.flatMapLatest { streamModel -> + with(streamModel) { val isMuted = muteSupported && muted when (sliderType) { is VolumeDialogSliderType.Stream -> @@ -101,7 +109,9 @@ constructor( } } } - .map { icon -> streamModel.toStateModel(icon) } + }, + ) { isDisabledByZenMode, model, icon -> + model.toStateModel(icon = icon, isDisabled = isDisabledByZenMode) } .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() @@ -116,11 +126,14 @@ constructor( .launchIn(coroutineScope) } - fun setStreamVolume(volume: Int, fromUser: Boolean) { + fun setStreamVolume(volume: Float, fromUser: Boolean) { if (fromUser) { visibilityInteractor.resetDismissTimeout() userVolumeUpdates.value = - VolumeUpdate(newVolumeLevel = volume, timestampMillis = getTimestampMillis()) + VolumeUpdate( + newVolumeLevel = volume.roundToInt(), + timestampMillis = getTimestampMillis(), + ) } } @@ -128,6 +141,28 @@ constructor( logger.onVolumeSliderAdjustmentFinished(volume = volume, stream = sliderType.audioStream) } + fun onTouchEvent(pointerEvent: PointerEvent) { + val position: Offset = pointerEvent.changes.first().position + when (pointerEvent.type) { + PointerEventType.Press -> + inputEventsInteractor.onTouchEvent( + SliderInputEvent.Touch.Start(position.x, position.y) + ) + PointerEventType.Move -> + inputEventsInteractor.onTouchEvent( + SliderInputEvent.Touch.Move(position.x, position.y) + ) + PointerEventType.Scroll -> + inputEventsInteractor.onTouchEvent( + SliderInputEvent.Touch.Move(position.x, position.y) + ) + PointerEventType.Release -> + inputEventsInteractor.onTouchEvent( + SliderInputEvent.Touch.End(position.x, position.y) + ) + } + } + private fun getTimestampMillis(): Long = systemClock.uptimeMillis() private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long) diff --git a/packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt new file mode 100644 index 000000000000..92e9bf2d1ffc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt @@ -0,0 +1,41 @@ +/* + * 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.haptics.ui + +import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig +import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig + +object VolumeHapticsConfigsProvider { + + fun sliderHapticFeedbackConfig( + valueRange: ClosedFloatingPointRange<Float> + ): SliderHapticFeedbackConfig { + val sliderStepSize = 1f / (valueRange.endInclusive - valueRange.start) + return SliderHapticFeedbackConfig( + lowerBookendScale = 0.2f, + progressBasedDragMinScale = 0.2f, + progressBasedDragMaxScale = 0.5f, + deltaProgressForDragThreshold = 0f, + additionalVelocityMaxBump = 0.2f, + maxVelocityToScale = 0.1f, /* slider progress(from 0 to 1) per sec */ + sliderStepSize = sliderStepSize, + ) + } + + val seekableSliderTrackerConfig = + SeekableSliderTrackerConfig(lowerBookendThreshold = 0f, upperBookendThreshold = 1f) +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt index 8487ee751948..ec74f4f47bc9 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt @@ -36,4 +36,5 @@ class NoopWallpaperRepository @Inject constructor() : WallpaperRepository { override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow() override val wallpaperSupportsAmbientMode = flowOf(false) override var rootView: View? = null + override val shouldSendFocalArea: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt index ed43f8323c31..9794c619041e 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt @@ -31,7 +31,6 @@ import com.android.systemui.Flags import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.keyguard.data.repository.KeyguardClockRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.shared.Flags.ambientAod import com.android.systemui.user.data.model.SelectedUserModel @@ -45,6 +44,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine @@ -65,6 +65,9 @@ interface WallpaperRepository { /** Set rootView to get its windowToken afterwards */ var rootView: View? + + /** when we use magic portrait wallpapers, we should always get its bounds from keyguard */ + val shouldSendFocalArea: StateFlow<Boolean> } @SysUISingleton @@ -76,7 +79,6 @@ constructor( broadcastDispatcher: BroadcastDispatcher, userRepository: UserRepository, keyguardRepository: KeyguardRepository, - keyguardClockRepository: KeyguardClockRepository, private val wallpaperManager: WallpaperManager, context: Context, ) : WallpaperRepository { @@ -97,27 +99,7 @@ constructor( // Only update the wallpaper status once the user selection has finished. .filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE } - /** The bottom of notification stack respect to the top of screen. */ - private val notificationStackAbsoluteBottom: StateFlow<Float> = - keyguardRepository.notificationStackAbsoluteBottom - - /** The top of shortcut respect to the top of screen. */ - private val shortcutAbsoluteTop: StateFlow<Float> = keyguardRepository.shortcutAbsoluteTop - - /** - * The top of notification stack to give a default state of lockscreen remaining space for - * states with notifications to compare with. It's the bottom of smartspace date and weather - * smartspace in small clock state, plus proper bottom margin. - */ - private val notificationStackDefaultTop = keyguardClockRepository.notificationDefaultTop @VisibleForTesting var sendLockscreenLayoutJob: Job? = null - private val lockscreenRemainingSpaceWithNotification: Flow<Triple<Float, Float, Float>> = - combine( - notificationStackAbsoluteBottom, - notificationStackDefaultTop, - shortcutAbsoluteTop, - ::Triple, - ) override val wallpaperInfo: StateFlow<WallpaperInfo?> = if (!wallpaperManager.isWallpaperSupported) { @@ -140,15 +122,16 @@ constructor( override var rootView: View? = null - val shouldSendNotificationLayout = + override val shouldSendFocalArea = wallpaperInfo .map { - val shouldSendNotificationLayout = shouldSendNotificationLayout(it) + val shouldSendNotificationLayout = + it?.component?.className == MAGIC_PORTRAIT_CLASSNAME if (shouldSendNotificationLayout) { sendLockscreenLayoutJob = scope.launch { - lockscreenRemainingSpaceWithNotification.collect { - (notificationBottom, notificationDefaultTop, shortcutTop) -> + keyguardRepository.wallpaperFocalAreaBounds.collect { + wallpaperFocalAreaBounds -> wallpaperManager.sendWallpaperCommand( /* windowToken = */ rootView?.windowToken, /* action = */ WallpaperManager @@ -157,14 +140,22 @@ constructor( /* y = */ 0, /* z = */ 0, /* extras = */ Bundle().apply { - putFloat("screenLeft", 0F) - putFloat("smartspaceBottom", notificationDefaultTop) - putFloat("notificationBottom", notificationBottom) putFloat( - "screenRight", - context.resources.displayMetrics.widthPixels.toFloat(), + "wallpaperFocalAreaLeft", + wallpaperFocalAreaBounds.left, + ) + putFloat( + "wallpaperFocalAreaRight", + wallpaperFocalAreaBounds.right, + ) + putFloat( + "wallpaperFocalAreaTop", + wallpaperFocalAreaBounds.top, + ) + putFloat( + "wallpaperFocalAreaBottom", + wallpaperFocalAreaBounds.bottom, ) - putFloat("shortCutTop", shortcutTop) }, ) } @@ -176,10 +167,9 @@ constructor( } .stateIn( scope, - // Always be listening for wallpaper changes. - if (Flags.magicPortraitWallpapers()) SharingStarted.Eagerly - else SharingStarted.Lazily, - initialValue = false, + // Always be listening for wallpaper changes when magic portrait flag is on + if (Flags.magicPortraitWallpapers()) SharingStarted.Eagerly else WhileSubscribed(), + initialValue = Flags.magicPortraitWallpapers(), ) private suspend fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? { @@ -188,14 +178,6 @@ constructor( } } - private fun shouldSendNotificationLayout(wallpaperInfo: WallpaperInfo?): Boolean { - return if (wallpaperInfo != null && wallpaperInfo.component != null) { - wallpaperInfo.component!!.className == MAGIC_PORTRAIT_CLASSNAME - } else { - false - } - } - companion object { const val MAGIC_PORTRAIT_CLASSNAME = "com.google.android.apps.magicportrait.service.MagicPortraitWallpaperService" diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 21519b0cb38a..ab691c630f97 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -304,9 +304,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { testScope = TestScope(testDispatcher) underTest = KeyguardQuickAffordanceInteractor( - keyguardInteractor = - KeyguardInteractorFactory.create(featureFlags = featureFlags) - .keyguardInteractor, + keyguardInteractor = kosmos.keyguardInteractor, shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index b5a227104900..051aba3d593f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -44,9 +44,10 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.GONE @@ -191,9 +192,8 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { dockManager = DockManagerFake() biometricSettingsRepository = FakeBiometricSettingsRepository() - val withDeps = KeyguardInteractorFactory.create() - keyguardInteractor = withDeps.keyguardInteractor - repository = withDeps.repository + keyguardInteractor = kosmos.keyguardInteractor + repository = kosmos.fakeKeyguardRepository whenever(userTracker.userHandle).thenReturn(mock()) whenever(lockPatternUtils.getStrongAuthForUser(ArgumentMatchers.anyInt())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index e68153ad2606..70450d29c74e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -57,7 +57,10 @@ import com.android.systemui.res.R import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler +import com.android.systemui.shade.data.repository.ShadeAnimationRepository +import com.android.systemui.shade.data.repository.ShadeRepositoryImpl import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.DragDownHelper import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -219,6 +222,10 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : notificationShadeDepthController, view, shadeViewController, + ShadeAnimationInteractorLegacyImpl( + ShadeAnimationRepository(), + ShadeRepositoryImpl(testScope), + ), panelExpansionInteractor, ShadeExpansionStateManager(), stackScrollLayoutController, @@ -521,6 +528,18 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area) } + @EnableFlags(Flags.FLAG_SHADE_LAUNCH_ACCESSIBILITY) + @Test + fun notifiesTheViewWhenLaunchAnimationIsRunning() { + testScope.runTest { + underTest.setExpandAnimationRunning(true) + verify(view).setAnimatingContentLaunch(true) + + underTest.setExpandAnimationRunning(false) + verify(view).setAnimatingContentLaunch(false) + } + } + @Test @DisableSceneContainer fun setsUpCommunalHubLayout_whenFlagEnabled() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt index eae828562223..a5cd81ff3116 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt @@ -53,6 +53,7 @@ import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CON import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT import com.android.systemui.shade.carrier.ShadeCarrierGroup import com.android.systemui.shade.carrier.ShadeCarrierGroupController +import com.android.systemui.shade.data.repository.shadeDisplaysRepository import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory @@ -96,7 +97,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { private val kosmos = testKosmos() private val insetsProviderStore = kosmos.fakeStatusBarContentInsetsProviderStore - private val insetsProvider = insetsProviderStore.defaultDisplay + private val insetsProvider = insetsProviderStore.forDisplay(context.displayId) @Mock(answer = Answers.RETURNS_MOCKS) private lateinit var view: MotionLayout @Mock private lateinit var statusIcons: StatusIconContainer @@ -196,6 +197,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { privacyIconsController, insetsProviderStore, configurationController, + kosmos.shadeDisplaysRepository, variableDateViewControllerFactory, batteryMeterViewController, dumpManager, @@ -809,6 +811,43 @@ class ShadeHeaderControllerTest : SysuiTestCase() { } @Test + fun sameInsetsTwice_listenerCallsOnApplyWindowInsetsOnlyOnce() { + val windowInsets = createWindowInsets() + + val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + verify(view).setOnApplyWindowInsetsListener(capture(captor)) + + val listener = captor.value + + listener.onApplyWindowInsets(view, windowInsets) + + verify(view, times(1)).onApplyWindowInsets(any()) + + listener.onApplyWindowInsets(view, windowInsets) + + verify(view, times(1)).onApplyWindowInsets(any()) + } + + @Test + fun twoDifferentInsets_listenerCallsOnApplyWindowInsetsTwice() { + val windowInsets1 = WindowInsets(Rect(1, 2, 3, 4)) + val windowInsets2 = WindowInsets(Rect(5, 6, 7, 8)) + + val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + verify(view).setOnApplyWindowInsetsListener(capture(captor)) + + val listener = captor.value + + listener.onApplyWindowInsets(view, windowInsets1) + + verify(view, times(1)).onApplyWindowInsets(any()) + + listener.onApplyWindowInsets(view, windowInsets2) + + verify(view, times(2)).onApplyWindowInsets(any()) + } + + @Test fun alarmIconNotIgnored() { verify(statusIcons, Mockito.never()) .addIgnoredSlot(context.getString(com.android.internal.R.string.status_bar_alarm_clock)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt index 61943f2283e0..8645a40319f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt @@ -444,6 +444,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { eq(true), /* wasShownHighPriority */ eq(assistantFeedbackController), any<MetricsLogger>(), + any<View.OnClickListener>(), ) } @@ -476,6 +477,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { eq(false), /* wasShownHighPriority */ eq(assistantFeedbackController), any<MetricsLogger>(), + any<View.OnClickListener>(), ) } @@ -508,6 +510,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { eq(false), /* wasShownHighPriority */ eq(assistantFeedbackController), any<MetricsLogger>(), + any<View.OnClickListener>(), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index 437ccb6a9821..68f66611c981 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -90,6 +90,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { private val kosmos = testKosmos() private val statusBarContentInsetsProviderStore = kosmos.fakeStatusBarContentInsetsProviderStore private val statusBarContentInsetsProvider = statusBarContentInsetsProviderStore.defaultDisplay + private val statusBarContentInsetsProviderForSecondaryDisplay = + statusBarContentInsetsProviderStore.forDisplay(SECONDARY_DISPLAY_ID) private val fakeDarkIconDispatcher = kosmos.fakeDarkIconDispatcher @Mock private lateinit var shadeViewController: ShadeViewController @@ -144,6 +146,12 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { controller = createAndInitController(view) } + `when`( + statusBarContentInsetsProviderForSecondaryDisplay + .getStatusBarContentInsetsForCurrentRotation() + ) + .thenReturn(Insets.NONE) + val contextForSecondaryDisplay = SysuiTestableContext( mContext.createDisplayContext( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index a02d333d1507..a7fe1ba76590 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -59,6 +59,7 @@ import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.DejankUtils; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; @@ -71,6 +72,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; +import com.android.systemui.keyguard.ui.transitions.BlurConfig; import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.kosmos.KosmosJavaAdapter; @@ -110,6 +112,9 @@ import java.util.Map; @RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest +// TODO(b/381263619) there are more changes and tweaks required to match the new bouncer/shade specs +// Disabling for now but it will be fixed before the flag is fully ramped up. +@DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP) public class ScrimControllerTest extends SysuiTestCase { @Rule public Expect mExpect = Expect.create(); @@ -286,7 +291,8 @@ public class ScrimControllerTest extends SysuiTestCase { mKeyguardTransitionInteractor, mKeyguardInteractor, mKosmos.getTestDispatcher(), - mLinearLargeScreenShadeInterpolator); + mLinearLargeScreenShadeInterpolator, + new BlurConfig(0.0f, 0.0f)); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); @@ -1251,7 +1257,8 @@ public class ScrimControllerTest extends SysuiTestCase { mKeyguardTransitionInteractor, mKeyguardInteractor, mKosmos.getTestDispatcher(), - mLinearLargeScreenShadeInterpolator); + mLinearLargeScreenShadeInterpolator, + new BlurConfig(0.0f, 0.0f)); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index fab7922f58e7..5d88f72b805b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -98,6 +98,7 @@ import android.view.View; import android.view.ViewTreeObserver; import android.view.WindowManager; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; @@ -2894,6 +2895,12 @@ public class BubblesTest extends SysuiTestCase { @Override public void animateBubbleBarLocation(BubbleBarLocation location) { } + + @Override + public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation location) {} + + @Override + public void onItemDraggedOutsideBubbleBarDropZone() {} } private static class FakeBubbleProperties implements BubbleProperties { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt index 0192fa47b434..739f6c2af2b4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt @@ -79,7 +79,7 @@ var Kosmos.shortcutHelperInputShortcutsSource: KeyboardShortcutGroupsSource by var Kosmos.shortcutHelperCurrentAppShortcutsSource: KeyboardShortcutGroupsSource by Kosmos.Fixture { CurrentAppShortcutsSource(windowManager) } -val Kosmos.shortcutHelperAccessibilityShortcutsSource: KeyboardShortcutGroupsSource by +var Kosmos.shortcutHelperAccessibilityShortcutsSource: KeyboardShortcutGroupsSource by Kosmos.Fixture { AccessibilityShortcutsSource(mainResources) } val Kosmos.shortcutHelperExclusions by diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 8489d8380041..8ea80081a871 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point +import android.graphics.RectF import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.BiometricUnlockModel @@ -129,6 +130,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { override val notificationStackAbsoluteBottom: StateFlow<Float> get() = _notificationStackAbsoluteBottom.asStateFlow() + private val _wallpaperFocalAreaBounds = MutableStateFlow(RectF(0f, 0f, 0f, 0f)) + override val wallpaperFocalAreaBounds: StateFlow<RectF> + get() = _wallpaperFocalAreaBounds.asStateFlow() + private val _isKeyguardEnabled = MutableStateFlow(true) override val isKeyguardEnabled: StateFlow<Boolean> = _isKeyguardEnabled.asStateFlow() @@ -287,6 +292,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _notificationStackAbsoluteBottom.value = bottom } + override fun setWallpaperFocalAreaBounds(bounds: RectF) { + _wallpaperFocalAreaBounds.value = bounds + } + override fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean) { _canIgnoreAuthAndReturnToGone.value = canWake } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractorKosmos.kt new file mode 100644 index 000000000000..8fd6f62b315f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractorKosmos.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.content.applicationContext +import com.android.systemui.keyguard.data.repository.keyguardClockRepository +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.wallpapers.data.repository.wallpaperRepository + +val Kosmos.wallpaperFocalAreaInteractor by + Kosmos.Fixture { + WallpaperFocalAreaInteractor( + applicationScope = applicationCoroutineScope, + context = applicationContext, + keyguardRepository = keyguardRepository, + shadeRepository = shadeRepository, + activeNotificationsInteractor = activeNotificationsInteractor, + keyguardClockRepository = keyguardClockRepository, + wallpaperRepository = wallpaperRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index 40b8e0e62b03..37df05b68f9e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor +import com.android.systemui.keyguard.domain.interactor.wallpaperFocalAreaInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope @@ -87,5 +88,6 @@ val Kosmos.keyguardRootViewModel by Fixture { screenOffAnimationController = screenOffAnimationController, aodBurnInViewModel = aodBurnInViewModel, shadeInteractor = shadeInteractor, + wallpaperFocalAreaInteractor = wallpaperFocalAreaInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt index 9d73ae3f176f..a1e7d5cede13 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt @@ -40,7 +40,8 @@ class FakeVolumeDialogController(private val audioManager: AudioManager) : Volum private val callbacks = CopyOnWriteArraySet<VolumeDialogController.Callbacks>() private var hasVibrator: Boolean = true - private var state = VolumeDialogController.State() + var state = VolumeDialogController.State() + private set override fun setActiveStream(stream: Int) { updateState { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt index 7892e962d63d..1b50094ec0b7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt @@ -16,14 +16,57 @@ package com.android.systemui.shade.domain.interactor +import android.provider.Settings import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shared.settings.data.repository.secureSettingsRepository +import kotlinx.coroutines.launch val Kosmos.shadeModeInteractor by Fixture { ShadeModeInteractorImpl( applicationScope = applicationCoroutineScope, repository = shadeRepository, + secureSettingsRepository = secureSettingsRepository, ) } + +// TODO(b/391578667): Make this user-aware once supported by FakeSecureSettingsRepository. +/** + * Enables the Dual Shade setting, and (optionally) sets the shade layout to be wide (`true`) + * or narrow (`false`). + * + * In a wide layout, notifications and quick settings shades each take up only half the screen + * width. In a narrow layout, they each take up the entire screen width. + */ +fun Kosmos.enableDualShade(wideLayout: Boolean? = null) { + testScope.launch { + secureSettingsRepository.setInt(Settings.Secure.DUAL_SHADE, 1) + + if (wideLayout != null) { + fakeShadeRepository.setShadeLayoutWide(wideLayout) + } + } +} + +// TODO(b/391578667): Make this user-aware once supported by FakeSecureSettingsRepository. +fun Kosmos.disableDualShade() { + testScope.launch { secureSettingsRepository.setInt(Settings.Secure.DUAL_SHADE, 0) } +} + +fun Kosmos.enableSingleShade() { + testScope.launch { + disableDualShade() + fakeShadeRepository.setShadeLayoutWide(false) + } +} + +fun Kosmos.enableSplitShade() { + testScope.launch { + disableDualShade() + fakeShadeRepository.setShadeLayoutWide(true) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java index 6cd6594c3404..c6daed1aa58f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java @@ -61,6 +61,7 @@ public class RankingBuilder { private boolean mIsBubble = false; private int mProposedImportance = IMPORTANCE_UNSPECIFIED; private boolean mSensitiveContent = false; + private String mSummarization = null; public RankingBuilder() { } @@ -92,6 +93,7 @@ public class RankingBuilder { mIsBubble = ranking.isBubble(); mProposedImportance = ranking.getProposedImportance(); mSensitiveContent = ranking.hasSensitiveContent(); + mSummarization = ranking.getSummarization(); } public Ranking build() { @@ -122,7 +124,8 @@ public class RankingBuilder { mRankingAdjustment, mIsBubble, mProposedImportance, - mSensitiveContent); + mSensitiveContent, + mSummarization); return ranking; } @@ -262,6 +265,11 @@ public class RankingBuilder { return this; } + public RankingBuilder setSummarization(String summary) { + mSummarization = summary; + return this; + } + private static <E> ArrayList<E> copyList(List<E> list) { if (list == null) { return null; 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 36fa82f82f0d..4ca044d60f3f 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 @@ -32,10 +32,8 @@ import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibility import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType 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.VolumeDialogSliderHapticsViewBinder import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder import com.android.systemui.volume.dialog.sliders.ui.volumeDialogOverscrollViewBinder -import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderHapticsViewBinder import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderViewBinder import com.android.systemui.volume.mediaControllerRepository import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaControllerInteractor @@ -65,9 +63,6 @@ fun Kosmos.volumeDialogSliderComponent(type: VolumeDialogSliderType): VolumeDial override fun sliderViewBinder(): VolumeDialogSliderViewBinder = localKosmos.volumeDialogSliderViewBinder - override fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder = - localKosmos.volumeDialogSliderHapticsViewBinder - override fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder = localKosmos.volumeDialogOverscrollViewBinder } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt deleted file mode 100644 index d6845b1ff7e3..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume.dialog.sliders.ui - -import com.android.systemui.haptics.msdl.msdlPlayer -import com.android.systemui.haptics.vibratorHelper -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.util.time.systemClock -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderInputEventsViewModel - -val Kosmos.volumeDialogSliderHapticsViewBinder by - Kosmos.Fixture { - VolumeDialogSliderHapticsViewBinder( - volumeDialogSliderInputEventsViewModel, - vibratorHelper, - msdlPlayer, - systemClock, - ) - } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt index c6db717e004f..484a7cc30152 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt @@ -16,14 +16,16 @@ package com.android.systemui.volume.dialog.sliders.ui +import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory import com.android.systemui.kosmos.Kosmos -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderInputEventsViewModel +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogOverscrollViewModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderViewModel val Kosmos.volumeDialogSliderViewBinder by Kosmos.Fixture { VolumeDialogSliderViewBinder( volumeDialogSliderViewModel, - volumeDialogSliderInputEventsViewModel, + volumeDialogOverscrollViewModel, + sliderHapticsViewModelFactory, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt deleted file mode 100644 index 2de0e8f76a4b..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume.dialog.sliders.ui.viewmodel - -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.applicationCoroutineScope -import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInputEventsInteractor - -val Kosmos.volumeDialogSliderInputEventsViewModel by - Kosmos.Fixture { - VolumeDialogSliderInputEventsViewModel( - applicationCoroutineScope, - volumeDialogSliderInputEventsInteractor, - ) - } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt index b26081c40c38..90bbb28ff519 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.util.time.systemClock import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.shared.volumeDialogLogger +import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInputEventsInteractor import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInteractor import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType @@ -29,6 +30,7 @@ val Kosmos.volumeDialogSliderViewModel by VolumeDialogSliderViewModel( sliderType = volumeDialogSliderType, interactor = volumeDialogSliderInteractor, + inputEventsInteractor = volumeDialogSliderInputEventsInteractor, visibilityInteractor = volumeDialogVisibilityInteractor, coroutineScope = applicationCoroutineScope, volumeDialogSliderIconProvider = volumeDialogSliderIconProvider, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt index ddb9a3ffee6d..f0c0d30e6db4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt @@ -19,7 +19,6 @@ package com.android.systemui.wallpapers.data.repository import android.content.applicationContext import com.android.app.wallpaperManager import com.android.systemui.broadcast.broadcastDispatcher -import com.android.systemui.keyguard.data.repository.keyguardClockRepository import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -34,8 +33,7 @@ val Kosmos.wallpaperRepository by Fixture { bgDispatcher = testDispatcher, broadcastDispatcher = broadcastDispatcher, userRepository = userRepository, - wallpaperManager = wallpaperManager, - keyguardClockRepository = keyguardClockRepository, keyguardRepository = keyguardRepository, + wallpaperManager = wallpaperManager, ) } diff --git a/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt b/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt index 7e27b24f749c..33287b7f3449 100644 --- a/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt +++ b/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt @@ -1,2 +1,3 @@ rule android.net.vcn.persistablebundleutils.** android.net.connectivity.android.net.vcn.persistablebundleutils.@1 -rule android.net.vcn.util.** android.net.connectivity.android.net.vcn.util.@1
\ No newline at end of file +rule android.net.vcn.util.** android.net.connectivity.android.net.vcn.util.@1 +rule android.util.IndentingPrintWriter android.net.connectivity.android.util.IndentingPrintWriter
\ No newline at end of file diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index adbb3afb4130..65550f2b4273 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -661,6 +661,9 @@ android_ravenwood_libgroup { // StatsD "framework-statsd.ravenwood", + // Graphics + "framework-graphics.ravenwood", + // Provide runtime versions of utils linked in below "junit", "truth", diff --git a/ravenwood/Framework.bp b/ravenwood/Framework.bp index 99fc31b258e9..71496b0d5766 100644 --- a/ravenwood/Framework.bp +++ b/ravenwood/Framework.bp @@ -399,3 +399,55 @@ java_genrule { "framework-statsd.ravenwood.jar", ], } + +////////////////////// +// framework-graphics +////////////////////// + +java_genrule { + name: "framework-graphics.ravenwood-base", + tools: ["hoststubgen"], + cmd: "$(location hoststubgen) " + + "@$(location :ravenwood-standard-options) " + + + "--debug-log $(location framework-graphics.log) " + + "--stats-file $(location framework-graphics_stats.csv) " + + "--supported-api-list-file $(location framework-graphics_apis.csv) " + + "--gen-keep-all-file $(location framework-graphics_keep_all.txt) " + + "--gen-input-dump-file $(location framework-graphics_dump.txt) " + + + "--out-impl-jar $(location ravenwood.jar) " + + "--in-jar $(location :framework-graphics.impl{.jar}) " + + + "--policy-override-file $(location :ravenwood-common-policies) ", + srcs: [ + ":framework-graphics.impl{.jar}", + + ":ravenwood-common-policies", + ":ravenwood-standard-options", + ], + out: [ + "ravenwood.jar", + + // Following files are created just as FYI. + "framework-graphics_keep_all.txt", + "framework-graphics_dump.txt", + + "framework-graphics.log", + "framework-graphics_stats.csv", + "framework-graphics_apis.csv", + ], + visibility: ["//visibility:private"], +} + +java_genrule { + name: "framework-graphics.ravenwood", + defaults: ["ravenwood-internal-only-visibility-genrule"], + cmd: "cp $(in) $(out)", + srcs: [ + ":framework-graphics.ravenwood-base{ravenwood.jar}", + ], + out: [ + "framework-graphics.ravenwood.jar", + ], +} diff --git a/ravenwood/scripts/pta-framework.sh b/ravenwood/scripts/pta-framework.sh index 224ab59e2e09..46c2c01c8ee8 100755 --- a/ravenwood/scripts/pta-framework.sh +++ b/ravenwood/scripts/pta-framework.sh @@ -79,6 +79,7 @@ run_pta() { $extra_args if ! [[ -f $OUT_SCRIPT ]] ; then + echo "No files need updating." # no operations generated. exit 0 fi @@ -88,4 +89,4 @@ run_pta() { return 0 } -run_pta "$extra_args"
\ No newline at end of file +run_pta "$extra_args" diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt index 4033782c607e..fff9e6ad41d5 100644 --- a/ravenwood/texts/ravenwood-framework-policies.txt +++ b/ravenwood/texts/ravenwood-framework-policies.txt @@ -62,4 +62,4 @@ class android.text.ClipboardManager keep # no-pta # Just enough to allow ResourcesManager to run class android.hardware.display.DisplayManagerGlobal keep # no-pta - method getInstance ()Landroid/hardware/display/DisplayManagerGlobal; ignore + method getInstance ()Landroid/hardware/display/DisplayManagerGlobal; ignore # no-pta diff --git a/ravenwood/texts/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt index 27223d8b72ff..91fd9283aff2 100644 --- a/ravenwood/texts/ravenwood-standard-options.txt +++ b/ravenwood/texts/ravenwood-standard-options.txt @@ -31,6 +31,9 @@ --remove-annotation android.ravenwood.annotation.RavenwoodRemove +--ignore-annotation + android.ravenwood.annotation.RavenwoodIgnore + --substitute-annotation android.ravenwood.annotation.RavenwoodReplace diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt index 4a11259a8ef7..ef1cb5dfca89 100644 --- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt +++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt @@ -45,7 +45,8 @@ class Annotations { "@android.ravenwood.annotation.RavenwoodRedirect" FilterPolicy.Throw -> "@android.ravenwood.annotation.RavenwoodThrow" - FilterPolicy.Ignore -> null // Ignore has no annotation. (because it's not very safe.) + FilterPolicy.Ignore -> + "@android.ravenwood.annotation.RavenwoodIgnore" FilterPolicy.Remove -> "@android.ravenwood.annotation.RavenwoodRemove" } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 875b655fe3d2..91775f8eed96 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -5084,39 +5084,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final List<String> permittedServices = dpm.getPermittedAccessibilityServices(userId); // permittedServices null means all accessibility services are allowed. - boolean allowed = permittedServices == null || permittedServices.contains(packageName); - if (allowed) { - if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() - && android.security.Flags.extendEcmToAllSettings()) { - try { - final EnhancedConfirmationManager userContextEcm = - mContext.createContextAsUser(UserHandle.of(userId), /* flags = */ 0) - .getSystemService(EnhancedConfirmationManager.class); - if (userContextEcm != null) { - return !userContextEcm.isRestricted(packageName, - AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); - } - return false; - } catch (PackageManager.NameNotFoundException e) { - Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e); - return false; - } - } else { - try { - final int mode = mContext.getSystemService(AppOpsManager.class) - .noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, - uid, packageName); - final boolean ecmEnabled = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enhancedConfirmationModeEnabled); - return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED - || mode == AppOpsManager.MODE_DEFAULT; - } catch (Exception e) { - // Fallback in case if app ops is not available in testing. - return false; - } - } - } - return false; + return permittedServices == null || permittedServices.contains(packageName); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index a37b2b926c9c..02a8f6218468 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -612,7 +612,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void enablePermissionsSync(int associationId) { - if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { throw new SecurityException("Caller must be system UID"); } mSystemDataTransferProcessor.enablePermissionsSync(associationId); @@ -620,7 +620,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void disablePermissionsSync(int associationId) { - if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { throw new SecurityException("Caller must be system UID"); } mSystemDataTransferProcessor.disablePermissionsSync(associationId); @@ -628,7 +628,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public PermissionSyncRequest getPermissionSyncRequest(int associationId) { - if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { throw new SecurityException("Caller must be system UID"); } return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); @@ -704,7 +704,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public byte[] getBackupPayload(int userId) { - if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { throw new SecurityException("Caller must be system"); } return mBackupRestoreProcessor.getBackupPayload(userId); @@ -712,7 +712,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void applyRestoredPayload(byte[] payload, int userId) { - if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { throw new SecurityException("Caller must be system"); } mBackupRestoreProcessor.applyRestoredPayload(payload, userId); diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java index 2b30c013235c..6f2ecd82a79c 100644 --- a/services/core/java/com/android/server/MasterClearReceiver.java +++ b/services/core/java/com/android/server/MasterClearReceiver.java @@ -130,7 +130,7 @@ public class MasterClearReceiver extends BroadcastReceiver { if (mWipeExternalStorage) { // thr will be started at the end of this task. Slog.i(TAG, "Wiping external storage on async task"); - new WipeDataTask(context, thr).execute(); + new WipeDataTask(context, thr).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { Slog.i(TAG, "NOT wiping external storage; starting thread " + thr.getName()); thr.start(); diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java index bfacfbba4e22..bec5db79ff9d 100644 --- a/services/core/java/com/android/server/am/BroadcastController.java +++ b/services/core/java/com/android/server/am/BroadcastController.java @@ -317,7 +317,7 @@ class BroadcastController { Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller); return null; } - if (callerApp.info.uid != SYSTEM_UID + if (!UserHandle.isCore(callerApp.info.uid) && !callerApp.getPkgList().containsKey(callerPackage)) { throw new SecurityException("Given caller package " + callerPackage + " is not running in process " + callerApp); diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 0b7890167c08..3817ba1a28b9 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -305,10 +305,6 @@ public final class PendingIntentRecord extends IIntentSender.Stub { this.stringName = null; } - @VisibleForTesting TempAllowListDuration getAllowlistDurationLocked(IBinder allowlistToken) { - return mAllowlistDuration.get(allowlistToken); - } - void setAllowBgActivityStarts(IBinder token, int flags) { if (token == null) return; if ((flags & FLAG_ACTIVITY_SENDER) != 0) { @@ -327,12 +323,6 @@ public final class PendingIntentRecord extends IIntentSender.Stub { mAllowBgActivityStartsForActivitySender.remove(token); mAllowBgActivityStartsForBroadcastSender.remove(token); mAllowBgActivityStartsForServiceSender.remove(token); - if (mAllowlistDuration != null) { - mAllowlistDuration.remove(token); - if (mAllowlistDuration.isEmpty()) { - mAllowlistDuration = null; - } - } } public void registerCancelListenerLocked(IResultReceiver receiver) { @@ -713,7 +703,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { return res; } - @VisibleForTesting BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender( + private BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender( IBinder allowlistToken) { return mAllowBgActivityStartsForActivitySender.contains(allowlistToken) ? BackgroundStartPrivileges.allowBackgroundActivityStarts(allowlistToken) diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 8e09e3b8e112..833599810210 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -604,7 +604,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } - /** Returned from {@link #verifyAndGetBypass(int, String, String, String, boolean)}. */ + /** Returned from {@link #verifyAndGetBypass(int, String, String, int, String, boolean)}. */ private static final class PackageVerificationResult { final RestrictionBypass bypass; @@ -3087,10 +3087,10 @@ public class AppOpsService extends IAppOpsService.Stub { public int checkPackage(int uid, String packageName) { Objects.requireNonNull(packageName); try { - verifyAndGetBypass(uid, packageName, null, null, true); + verifyAndGetBypass(uid, packageName, null, Process.INVALID_UID, null, true); // When the caller is the system, it's possible that the packageName is the special // one (e.g., "root") which isn't actually existed. - if (resolveUid(packageName) == uid + if (resolveNonAppUid(packageName) == uid || (isPackageExisted(packageName) && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) { return AppOpsManager.MODE_ALLOWED; @@ -3306,7 +3306,7 @@ public class AppOpsService extends IAppOpsService.Stub { boolean shouldCollectMessage, int notedCount) { PackageVerificationResult pvr; try { - pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); + pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName); if (!pvr.isAttributionTagValid) { attributionTag = null; } @@ -3930,7 +3930,7 @@ public class AppOpsService extends IAppOpsService.Stub { // Test if the proxied operation will succeed before starting the proxy operation final SyncNotedAppOp testProxiedOp = startOperationDryRun(code, proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, - proxiedVirtualDeviceId, resolvedProxyPackageName, proxiedFlags, + proxiedVirtualDeviceId, proxyUid, resolvedProxyPackageName, proxiedFlags, startIfModeDefault); if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) { @@ -3970,7 +3970,7 @@ public class AppOpsService extends IAppOpsService.Stub { int attributionChainId) { PackageVerificationResult pvr; try { - pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); + pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName); if (!pvr.isAttributionTagValid) { attributionTag = null; } @@ -4097,11 +4097,11 @@ public class AppOpsService extends IAppOpsService.Stub { */ private SyncNotedAppOp startOperationDryRun(int code, int uid, @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId, - String proxyPackageName, @OpFlags int flags, + int proxyUid, String proxyPackageName, @OpFlags int flags, boolean startIfModeDefault) { PackageVerificationResult pvr; try { - pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); + pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName); if (!pvr.isAttributionTagValid) { attributionTag = null; } @@ -4656,13 +4656,17 @@ public class AppOpsService extends IAppOpsService.Stub { private boolean isSpecialPackage(int callingUid, @Nullable String packageName) { final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName); return callingUid == Process.SYSTEM_UID - || resolveUid(resolvedPackage) != Process.INVALID_UID; + || resolveNonAppUid(resolvedPackage) != Process.INVALID_UID; } private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) { if (attributionSource.getUid() != Binder.getCallingUid() && attributionSource.isTrusted(mContext)) { - return true; + // if there is a next attribution source, it must be trusted, as well. + if (attributionSource.getNext() == null + || attributionSource.getNext().isTrusted(mContext)) { + return true; + } } return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS, Binder.getCallingPid(), Binder.getCallingUid(), null) @@ -4757,19 +4761,20 @@ public class AppOpsService extends IAppOpsService.Stub { } /** - * @see #verifyAndGetBypass(int, String, String, String, boolean) + * @see #verifyAndGetBypass(int, String, String, int, String, boolean) */ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, @Nullable String attributionTag) { - return verifyAndGetBypass(uid, packageName, attributionTag, null); + return verifyAndGetBypass(uid, packageName, attributionTag, Process.INVALID_UID, null); } /** - * @see #verifyAndGetBypass(int, String, String, String, boolean) + * @see #verifyAndGetBypass(int, String, String, int, String, boolean) */ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, - @Nullable String attributionTag, @Nullable String proxyPackageName) { - return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName, false); + @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName) { + return verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName, + false); } /** @@ -4780,14 +4785,15 @@ public class AppOpsService extends IAppOpsService.Stub { * @param uid The uid the package belongs to * @param packageName The package the might belong to the uid * @param attributionTag attribution tag or {@code null} if no need to verify - * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled + * @param proxyUid The proxy uid, from which the attribution tag is to be pulled + * @param proxyPackageName The proxy package, from which the attribution tag may be pulled * @param suppressErrorLogs Whether to print to logcat about nonmatching parameters * * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the * attribution tag is valid */ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, - @Nullable String attributionTag, @Nullable String proxyPackageName, + @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName, boolean suppressErrorLogs) { if (uid == Process.ROOT_UID) { // For backwards compatibility, don't check package name for root UID. @@ -4831,34 +4837,47 @@ public class AppOpsService extends IAppOpsService.Stub { int callingUid = Binder.getCallingUid(); - // Allow any attribution tag for resolvable uids - int pkgUid; + // Allow any attribution tag for resolvable, non-app uids + int nonAppUid; if (Objects.equals(packageName, "com.android.shell")) { // Special case for the shell which is a package but should be able // to bypass app attribution tag restrictions. - pkgUid = Process.SHELL_UID; + nonAppUid = Process.SHELL_UID; } else { - pkgUid = resolveUid(packageName); + nonAppUid = resolveNonAppUid(packageName); } - if (pkgUid != Process.INVALID_UID) { - if (pkgUid != UserHandle.getAppId(uid)) { + if (nonAppUid != Process.INVALID_UID) { + if (nonAppUid != UserHandle.getAppId(uid)) { if (!suppressErrorLogs) { Slog.e(TAG, "Bad call made by uid " + callingUid + ". " - + "Package \"" + packageName + "\" does not belong to uid " + uid - + "."); + + "Package \"" + packageName + "\" does not belong to uid " + uid + + "."); + } + String otherUidMessage = + DEBUG ? " but it is really " + nonAppUid : " but it is not"; + throw new SecurityException("Specified package \"" + packageName + + "\" under uid " + UserHandle.getAppId(uid) + otherUidMessage); + } + // We only allow bypassing the attribution tag verification if the proxy is a + // system app (or is null), in order to prevent abusive apps clogging the appops + // system with unlimited attribution tags via proxy calls. + boolean proxyIsSystemAppOrNull = true; + if (proxyPackageName != null) { + int proxyAppId = UserHandle.getAppId(proxyUid); + if (proxyAppId >= Process.FIRST_APPLICATION_UID) { + proxyIsSystemAppOrNull = + mPackageManagerInternal.isSystemPackage(proxyPackageName); } - String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not"; - throw new SecurityException("Specified package \"" + packageName + "\" under uid " - + UserHandle.getAppId(uid) + otherUidMessage); } return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED, - /* isAttributionTagValid */ true); + /* isAttributionTagValid */ proxyIsSystemAppOrNull); } int userId = UserHandle.getUserId(uid); RestrictionBypass bypass = null; boolean isAttributionTagValid = false; + int pkgUid = nonAppUid; final long ident = Binder.clearCallingIdentity(); try { PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); @@ -5649,7 +5668,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (nonpackageUid != -1) { packageName = null; } else { - packageUid = resolveUid(packageName); + packageUid = resolveNonAppUid(packageName); if (packageUid < 0) { packageUid = AppGlobals.getPackageManager().getPackageUid(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); @@ -6749,7 +6768,13 @@ public class AppOpsService extends IAppOpsService.Stub { if (restricted && attrOp.isRunning()) { attrOp.pause(); } else if (attrOp.isPaused()) { - attrOp.resume(); + RestrictionBypass bypass = verifyAndGetBypass(uid, ops.packageName, attrOp.tag) + .bypass; + if (!isOpRestrictedLocked(uid, code, ops.packageName, attrOp.tag, + Context.DEVICE_ID_DEFAULT, bypass, false)) { + // Only resume if there are no other restrictions remaining on this op + attrOp.resume(); + } } } } @@ -7198,7 +7223,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } - private static int resolveUid(String packageName) { + private static int resolveNonAppUid(String packageName) { if (packageName == null) { return Process.INVALID_UID; } diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java index 030ce12f5063..c35f4fca6edd 100644 --- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java +++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java @@ -65,6 +65,10 @@ class AudioManagerShellCommand extends ShellCommand { return setRingerMode(); case "set-volume": return setVolume(); + case "get-min-volume": + return getMinVolume(); + case "get-max-volume": + return getMaxVolume(); case "set-device-volume": return setDeviceVolume(); case "adj-mute": @@ -106,6 +110,10 @@ class AudioManagerShellCommand extends ShellCommand { pw.println(" Sets the Ringer mode to one of NORMAL|SILENT|VIBRATE"); pw.println(" set-volume STREAM_TYPE VOLUME_INDEX"); pw.println(" Sets the volume for STREAM_TYPE to VOLUME_INDEX"); + pw.println(" get-min-volume STREAM_TYPE"); + pw.println(" Gets the min volume for STREAM_TYPE"); + pw.println(" get-max-volume STREAM_TYPE"); + pw.println(" Gets the max volume for STREAM_TYPE"); pw.println(" set-device-volume STREAM_TYPE VOLUME_INDEX NATIVE_DEVICE_TYPE"); pw.println(" Sets for NATIVE_DEVICE_TYPE the STREAM_TYPE volume to VOLUME_INDEX"); pw.println(" adj-mute STREAM_TYPE"); @@ -296,6 +304,24 @@ class AudioManagerShellCommand extends ShellCommand { return 0; } + private int getMinVolume() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + final int stream = readIntArg(); + final int result = am.getStreamMinVolume(stream); + getOutPrintWriter().println("AudioManager.getStreamMinVolume(" + stream + ") -> " + result); + return 0; + } + + private int getMaxVolume() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + final int stream = readIntArg(); + final int result = am.getStreamMaxVolume(stream); + getOutPrintWriter().println("AudioManager.getStreamMaxVolume(" + stream + ") -> " + result); + return 0; + } + private int setDeviceVolume() { final Context context = mService.mContext; final AudioDeviceVolumeManager advm = (AudioDeviceVolumeManager) context.getSystemService( diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 709c13bc9704..336243f0289e 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -47,9 +47,9 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static android.media.AudioManager.STREAM_SYSTEM; -import static android.media.IAudioManagerNative.HardeningType; import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.media.audio.Flags.automaticBtDeviceType; +import static android.media.audio.Flags.cacheGetStreamMinMaxVolume; import static android.media.audio.Flags.concurrentAudioRecordBypassPermission; import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency; import static android.media.audio.Flags.focusFreezeTestApi; @@ -898,6 +898,16 @@ public class AudioService extends IAudioService.Stub public void permissionUpdateBarrier() { AudioService.this.permissionUpdateBarrier(); } + + /** + * Update mute state event for port + * @param portId Port id to update + * @param event the mute event containing info about the mute + */ + @Override + public void portMuteEvent(int portId, int event) { + mPlaybackMonitor.portMuteEvent(portId, event, Binder.getCallingUid()); + } }; // List of binder death handlers for setMode() client processes. @@ -4976,6 +4986,8 @@ public class AudioService extends IAudioService.Stub + ringMyCar()); pw.println("\tandroid.media.audio.Flags.concurrentAudioRecordBypassPermission:" + concurrentAudioRecordBypassPermission()); + pw.println("\tandroid.media.audio.Flags.cacheGetStreamMinMaxVolume:" + + cacheGetStreamMinMaxVolume()); } private void dumpAudioMode(PrintWriter pw) { @@ -9366,6 +9378,12 @@ public class AudioService extends IAudioService.Stub mIndexMinNoPerm = mIndexMin; } } + if (cacheGetStreamMinMaxVolume() && mStreamType == AudioSystem.STREAM_VOICE_CALL) { + if (DEBUG_VOL) { + Log.d(TAG, "Clear min volume cache from updateIndexFactors"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_MIN_CACHING_API); + } final int status = AudioSystem.initStreamVolume( mStreamType, indexMinVolCurve, indexMaxVolCurve); @@ -9403,11 +9421,19 @@ public class AudioService extends IAudioService.Stub * @param index minimum index expressed in "UI units", i.e. no 10x factor */ public void updateNoPermMinIndex(int index) { + boolean changedNoPermMinIndex = + cacheGetStreamMinMaxVolume() && (index * 10) != mIndexMinNoPerm; mIndexMinNoPerm = index * 10; if (mIndexMinNoPerm < mIndexMin) { Log.e(TAG, "Invalid mIndexMinNoPerm for stream " + mStreamType); mIndexMinNoPerm = mIndexMin; } + if (changedNoPermMinIndex) { + if (DEBUG_VOL) { + Log.d(TAG, "Clear min volume cache from updateNoPermMinIndex"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_MIN_CACHING_API); + } } /** diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index e2e06b63c7d6..57b5febf4df0 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -435,6 +435,49 @@ public final class PlaybackActivityMonitor /** * Update event for port * @param portId Port id to update + * @param event the mute event containing info about the mute + * @param binderUid Calling binder uid + */ + public void portMuteEvent(int portId, @PlayerMuteEvent int event, int binderUid) { + if (!UserHandle.isCore(binderUid)) { + Log.e(TAG, "Forbidden operation from uid " + binderUid); + return; + } + + synchronized (mPlayerLock) { + int piid; + if (portToPiidSimplification()) { + int idxOfPiid = mPiidToPortId.indexOfValue(portId); + if (idxOfPiid < 0) { + Log.w(TAG, "No piid assigned for invalid/internal port id " + portId); + return; + } + piid = mPiidToPortId.keyAt(idxOfPiid); + } else { + piid = mPortIdToPiid.get(portId, PLAYER_PIID_INVALID); + if (piid == PLAYER_PIID_INVALID) { + Log.w(TAG, "No piid assigned for invalid/internal port id " + portId); + return; + } + } + final AudioPlaybackConfiguration apc = mPlayers.get(piid); + if (apc == null) { + Log.w(TAG, "No AudioPlaybackConfiguration assigned for piid " + piid); + return; + } + + if (apc.getPlayerType() + == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) { + // FIXME SoundPool not ready for state reporting + return; + } + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_MUTED_EVENT, piid, event, null)); + } + } + /** + * Update event for port + * @param portId Port id to update * @param event The new port event * @param extras The values associated with this event * @param binderUid Calling binder uid @@ -479,15 +522,10 @@ public final class PlaybackActivityMonitor return; } - if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED) { - mEventHandler.sendMessage( - mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_MUTED_EVENT, piid, - portId, - extras)); - } else if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_FORMAT) { + if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_FORMAT) { mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_FORMAT, piid, - portId, + -1, extras)); } } @@ -1695,9 +1733,7 @@ public final class PlaybackActivityMonitor * event for player getting muted * args: * msg.arg1: piid - * msg.arg2: port id - * msg.obj: extras describing the mute reason - * type: PersistableBundle + * msg.arg2: mute reason */ private static final int MSG_IIL_UPDATE_PLAYER_MUTED_EVENT = 2; @@ -1705,7 +1741,6 @@ public final class PlaybackActivityMonitor * event for player reporting playback format and spatialization status * args: * msg.arg1: piid - * msg.arg2: port id * msg.obj: extras describing the sample rate, channel mask, spatialized * type: PersistableBundle */ @@ -1729,17 +1764,9 @@ public final class PlaybackActivityMonitor break; case MSG_IIL_UPDATE_PLAYER_MUTED_EVENT: - // TODO: replace PersistableBundle with own struct - PersistableBundle extras = (PersistableBundle) msg.obj; - if (extras == null) { - Log.w(TAG, "Received mute event with no extras"); - break; - } - @PlayerMuteEvent int eventValue = extras.getInt(EXTRA_PLAYER_EVENT_MUTE); - synchronized (mPlayerLock) { int piid = msg.arg1; - + @PlayerMuteEvent int eventValue = msg.arg2; int[] eventValues = new int[1]; eventValues[0] = eventValue; diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index d435144b28c6..b9ce8c93dbde 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -21,6 +21,7 @@ import android.os.Build; import android.os.SystemProperties; import android.text.TextUtils; import android.util.Slog; +import android.window.DesktopExperienceFlags; import com.android.server.display.feature.flags.Flags; import com.android.server.display.utils.DebugUtils; @@ -250,7 +251,7 @@ public class DisplayManagerFlags { ); private final FlagState mEnableDisplayContentModeManagementFlagState = new FlagState( Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT, - Flags::enableDisplayContentModeManagement + DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT::isTrue ); private final FlagState mSubscribeGranularDisplayEvents = new FlagState( diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index fd755e3cefe2..be8a94149fdc 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -186,21 +186,11 @@ final class InputGestureManager { KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT ), createKeyGesture( - KeyEvent.KEYCODE_DPAD_LEFT, - KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT - ), - createKeyGesture( KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT ), createKeyGesture( - KeyEvent.KEYCODE_DPAD_RIGHT, - KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT - ), - createKeyGesture( KeyEvent.KEYCODE_SLASH, KeyEvent.META_META_ON, KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index 2a5b779546d5..5259bcc78311 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -29,6 +29,8 @@ import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.hardware.tv.mediaquality.AmbientBacklightColorFormat; +import android.hardware.tv.mediaquality.DolbyAudioProcessing; +import android.hardware.tv.mediaquality.DtsVirtualX; import android.hardware.tv.mediaquality.IMediaQuality; import android.hardware.tv.mediaquality.PictureParameter; import android.hardware.tv.mediaquality.PictureParameters; @@ -461,7 +463,7 @@ public class MediaQualityService extends SystemService { } if (params.containsKey(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) { pictureParams.add(PictureParameter.autoSuperResolutionEnabled(params.getBoolean( - PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED))); + PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED))); } if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) { pictureParams.add(PictureParameter.colorTemperatureRedGain(params.getInt( @@ -475,63 +477,214 @@ public class MediaQualityService extends SystemService { pictureParams.add(PictureParameter.colorTemperatureBlueGain(params.getInt( PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN))); } - - /** - * TODO: add conversion for following after adding to MediaQualityContract - * - * PictureParameter.levelRange - * PictureParameter.gamutMapping - * PictureParameter.pcMode - * PictureParameter.lowLatency - * PictureParameter.vrr - * PictureParameter.cvrr - * PictureParameter.hdmiRgbRange - * PictureParameter.colorSpace - * PictureParameter.panelInitMaxLuminceNits - * PictureParameter.panelInitMaxLuminceValid - * PictureParameter.gamma - * PictureParameter.colorTemperatureRedOffset - * PictureParameter.colorTemperatureGreenOffset - * PictureParameter.colorTemperatureBlueOffset - * PictureParameter.elevenPointRed - * PictureParameter.elevenPointGreen - * PictureParameter.elevenPointBlue - * PictureParameter.lowBlueLight - * PictureParameter.LdMode - * PictureParameter.osdRedGain - * PictureParameter.osdGreenGain - * PictureParameter.osdBlueGain - * PictureParameter.osdRedOffset - * PictureParameter.osdGreenOffset - * PictureParameter.osdBlueOffset - * PictureParameter.osdHue - * PictureParameter.osdSaturation - * PictureParameter.osdContrast - * PictureParameter.colorTunerSwitch - * PictureParameter.colorTunerHueRed - * PictureParameter.colorTunerHueGreen - * PictureParameter.colorTunerHueBlue - * PictureParameter.colorTunerHueCyan - * PictureParameter.colorTunerHueMagenta - * PictureParameter.colorTunerHueYellow - * PictureParameter.colorTunerHueFlesh - * PictureParameter.colorTunerSaturationRed - * PictureParameter.colorTunerSaturationGreen - * PictureParameter.colorTunerSaturationBlue - * PictureParameter.colorTunerSaturationCyan - * PictureParameter.colorTunerSaturationMagenta - * PictureParameter.colorTunerSaturationYellow - * PictureParameter.colorTunerSaturationFlesh - * PictureParameter.colorTunerLuminanceRed - * PictureParameter.colorTunerLuminanceGreen - * PictureParameter.colorTunerLuminanceBlue - * PictureParameter.colorTunerLuminanceCyan - * PictureParameter.colorTunerLuminanceMagenta - * PictureParameter.colorTunerLuminanceYellow - * PictureParameter.colorTunerLuminanceFlesh - * PictureParameter.activeProfile - * PictureParameter.pictureQualityEventType - */ + if (params.containsKey(PictureQuality.PARAMETER_LEVEL_RANGE)) { + pictureParams.add(PictureParameter.levelRange( + (byte) params.getInt(PictureQuality.PARAMETER_LEVEL_RANGE))); + } + if (params.containsKey(PictureQuality.PARAMETER_GAMUT_MAPPING)) { + pictureParams.add(PictureParameter.gamutMapping(params.getBoolean( + PictureQuality.PARAMETER_GAMUT_MAPPING))); + } + if (params.containsKey(PictureQuality.PARAMETER_PC_MODE)) { + pictureParams.add(PictureParameter.pcMode(params.getBoolean( + PictureQuality.PARAMETER_PC_MODE))); + } + if (params.containsKey(PictureQuality.PARAMETER_LOW_LATENCY)) { + pictureParams.add(PictureParameter.lowLatency(params.getBoolean( + PictureQuality.PARAMETER_LOW_LATENCY))); + } + if (params.containsKey(PictureQuality.PARAMETER_VRR)) { + pictureParams.add(PictureParameter.vrr(params.getBoolean( + PictureQuality.PARAMETER_VRR))); + } + if (params.containsKey(PictureQuality.PARAMETER_CVRR)) { + pictureParams.add(PictureParameter.cvrr(params.getBoolean( + PictureQuality.PARAMETER_CVRR))); + } + if (params.containsKey(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) { + pictureParams.add(PictureParameter.hdmiRgbRange( + (byte) params.getInt(PictureQuality.PARAMETER_HDMI_RGB_RANGE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_SPACE)) { + pictureParams.add(PictureParameter.colorSpace( + (byte) params.getInt(PictureQuality.PARAMETER_COLOR_SPACE))); + } + if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)) { + pictureParams.add(PictureParameter.panelInitMaxLuminceNits( + params.getInt(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS))); + } + if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)) { + pictureParams.add(PictureParameter.panelInitMaxLuminceValid( + params.getBoolean(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID))); + } + if (params.containsKey(PictureQuality.PARAMETER_GAMMA)) { + pictureParams.add(PictureParameter.gamma( + (byte) params.getInt(PictureQuality.PARAMETER_GAMMA))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) { + pictureParams.add(PictureParameter.colorTemperatureRedOffset(params.getInt( + PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)) { + pictureParams.add(PictureParameter.colorTemperatureGreenOffset(params.getInt( + PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)) { + pictureParams.add(PictureParameter.colorTemperatureBlueOffset(params.getInt( + PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_RED)) { + pictureParams.add(PictureParameter.elevenPointRed(params.getIntArray( + PictureQuality.PARAMETER_ELEVEN_POINT_RED))); + } + if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)) { + pictureParams.add(PictureParameter.elevenPointGreen(params.getIntArray( + PictureQuality.PARAMETER_ELEVEN_POINT_GREEN))); + } + if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)) { + pictureParams.add(PictureParameter.elevenPointBlue(params.getIntArray( + PictureQuality.PARAMETER_ELEVEN_POINT_BLUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) { + pictureParams.add(PictureParameter.lowBlueLight( + (byte) params.getInt(PictureQuality.PARAMETER_LOW_BLUE_LIGHT))); + } + if (params.containsKey(PictureQuality.PARAMETER_LD_MODE)) { + pictureParams.add(PictureParameter.LdMode( + (byte) params.getInt(PictureQuality.PARAMETER_LD_MODE))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_GAIN)) { + pictureParams.add(PictureParameter.osdRedGain(params.getInt( + PictureQuality.PARAMETER_OSD_RED_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_GAIN)) { + pictureParams.add(PictureParameter.osdGreenGain(params.getInt( + PictureQuality.PARAMETER_OSD_GREEN_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_GAIN)) { + pictureParams.add(PictureParameter.osdBlueGain(params.getInt( + PictureQuality.PARAMETER_OSD_BLUE_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_OFFSET)) { + pictureParams.add(PictureParameter.osdRedOffset(params.getInt( + PictureQuality.PARAMETER_OSD_RED_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_OFFSET)) { + pictureParams.add(PictureParameter.osdGreenOffset(params.getInt( + PictureQuality.PARAMETER_OSD_GREEN_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_OFFSET)) { + pictureParams.add(PictureParameter.osdBlueOffset(params.getInt( + PictureQuality.PARAMETER_OSD_BLUE_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_HUE)) { + pictureParams.add(PictureParameter.osdHue(params.getInt( + PictureQuality.PARAMETER_OSD_HUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_SATURATION)) { + pictureParams.add(PictureParameter.osdSaturation(params.getInt( + PictureQuality.PARAMETER_OSD_SATURATION))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_CONTRAST)) { + pictureParams.add(PictureParameter.osdContrast(params.getInt( + PictureQuality.PARAMETER_OSD_CONTRAST))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)) { + pictureParams.add(PictureParameter.colorTunerSwitch(params.getBoolean( + PictureQuality.PARAMETER_COLOR_TUNER_SWITCH))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)) { + pictureParams.add(PictureParameter.colorTunerHueRed(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)) { + pictureParams.add(PictureParameter.colorTunerHueGreen(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)) { + pictureParams.add(PictureParameter.colorTunerHueBlue(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)) { + pictureParams.add(PictureParameter.colorTunerHueCyan(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)) { + pictureParams.add(PictureParameter.colorTunerHueMagenta(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)) { + pictureParams.add(PictureParameter.colorTunerHueYellow(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)) { + pictureParams.add(PictureParameter.colorTunerHueFlesh(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)) { + pictureParams.add(PictureParameter.colorTunerSaturationRed(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)) { + pictureParams.add(PictureParameter.colorTunerSaturationGreen(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)) { + pictureParams.add(PictureParameter.colorTunerSaturationBlue(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)) { + pictureParams.add(PictureParameter.colorTunerSaturationCyan(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)) { + pictureParams.add(PictureParameter.colorTunerSaturationMagenta(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)) { + pictureParams.add(PictureParameter.colorTunerSaturationYellow(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)) { + pictureParams.add(PictureParameter.colorTunerSaturationFlesh(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)) { + pictureParams.add(PictureParameter.colorTunerLuminanceRed(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)) { + pictureParams.add(PictureParameter.colorTunerLuminanceGreen(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)) { + pictureParams.add(PictureParameter.colorTunerLuminanceBlue(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)) { + pictureParams.add(PictureParameter.colorTunerLuminanceCyan(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)) { + pictureParams.add(PictureParameter.colorTunerLuminanceMagenta(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)) { + pictureParams.add(PictureParameter.colorTunerLuminanceYellow(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)) { + pictureParams.add(PictureParameter.colorTunerLuminanceFlesh(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH))); + } + if (params.containsKey(PictureQuality.PARAMETER_ACTIVE_PROFILE)) { + pictureParams.add(PictureParameter.activeProfile(params.getBoolean( + PictureQuality.PARAMETER_ACTIVE_PROFILE))); + } + if (params.containsKey(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)) { + pictureParams.add(PictureParameter.pictureQualityEventType( + (byte) params.getInt(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE))); + } return (PictureParameter[]) pictureParams.toArray(); } @@ -775,6 +928,7 @@ public class MediaQualityService extends SystemService { private SoundParameter[] convertPersistableBundleToSoundParameterList( PersistableBundle params) { + //TODO: set EqualizerDetail List<SoundParameter> soundParams = new ArrayList<>(); if (params.containsKey(SoundQuality.PARAMETER_BALANCE)) { soundParams.add(SoundParameter.balance(params.getInt( @@ -811,15 +965,54 @@ public class MediaQualityService extends SystemService { soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean( SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS))); } - //TODO: equalizerDetail - //TODO: downmixMode - //TODO: enhancedAudioReturnChannelEnabled - //TODO: dolbyAudioProcessing - //TODO: dolbyDialogueEnhancer - //TODO: dtsVirtualX - //TODO: digitalOutput - //TODO: activeProfile - //TODO: soundStyle + if (params.containsKey(SoundQuality.PARAMETER_EARC)) { + soundParams.add(SoundParameter.enhancedAudioReturnChannelEnabled(params.getBoolean( + SoundQuality.PARAMETER_EARC))); + } + if (params.containsKey(SoundQuality.PARAMETER_DOWN_MIX_MODE)) { + soundParams.add(SoundParameter.downmixMode((byte) params.getInt( + SoundQuality.PARAMETER_DOWN_MIX_MODE))); + } + if (params.containsKey(SoundQuality.PARAMETER_ACTIVE_PROFILE)) { + soundParams.add(SoundParameter.activeProfile(params.getBoolean( + SoundQuality.PARAMETER_ACTIVE_PROFILE))); + } + if (params.containsKey(SoundQuality.PARAMETER_SOUND_STYLE)) { + soundParams.add(SoundParameter.soundStyle((byte) params.getInt( + SoundQuality.PARAMETER_SOUND_STYLE))); + } + if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)) { + soundParams.add(SoundParameter.digitalOutput((byte) params.getInt( + SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE))); + } + if (params.containsKey(SoundQuality.PARAMETER_DIALOGUE_ENHANCER)) { + soundParams.add(SoundParameter.dolbyDialogueEnhancer((byte) params.getInt( + SoundQuality.PARAMETER_DIALOGUE_ENHANCER))); + } + + DolbyAudioProcessing dab = new DolbyAudioProcessing(); + dab.soundMode = + (byte) params.getInt(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SOUND_MODE); + dab.volumeLeveler = + params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_VOLUME_LEVELER); + dab.surroundVirtualizer = params.getBoolean( + SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SURROUND_VIRTUALIZER); + dab.dolbyAtmos = + params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_DOLBY_ATMOS); + soundParams.add(SoundParameter.dolbyAudioProcessing(dab)); + + DtsVirtualX dts = new DtsVirtualX(); + dts.tbHdx = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TBHDX); + dts.limiter = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_LIMITER); + dts.truSurroundX = params.getBoolean( + SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_SURROUND_X); + dts.truVolumeHd = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_VOLUME_HD); + dts.dialogClarity = params.getBoolean( + SoundQuality.PARAMETER_DTS_VIRTUAL_X_DIALOG_CLARITY); + dts.definition = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_DEFINITION); + dts.height = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_HEIGHT); + soundParams.add(SoundParameter.dtsVirtualX(dts)); + return (SoundParameter[]) soundParams.toArray(); } @@ -1472,7 +1665,13 @@ public class MediaQualityService extends SystemService { RemoteCallbackList<IPictureProfileCallback> { @Override public void onCallbackDied(IPictureProfileCallback callback) { - //todo + synchronized ("mPictureProfileLock") { //TODO: Change to lock + for (int i = 0; i < mUserStates.size(); i++) { + int userId = mUserStates.keyAt(i); + UserState userState = getOrCreateUserStateLocked(userId); + userState.mPictureProfileCallbackPidUidMap.remove(callback); + } + } } } @@ -1480,7 +1679,13 @@ public class MediaQualityService extends SystemService { RemoteCallbackList<ISoundProfileCallback> { @Override public void onCallbackDied(ISoundProfileCallback callback) { - //todo + synchronized ("mSoundProfileLock") { //TODO: Change to lock + for (int i = 0; i < mUserStates.size(); i++) { + int userId = mUserStates.keyAt(i); + UserState userState = getOrCreateUserStateLocked(userId); + userState.mSoundProfileCallbackPidUidMap.remove(callback); + } + } } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 7de2815eba6b..1d376b4bdfd5 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -3612,13 +3612,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final long token = Binder.clearCallingIdentity(); try { config = mCarrierConfigManager.getConfigForSubId(subId); - tm = mContext.getSystemService(TelephonyManager.class); + tm = mContext.getSystemService(TelephonyManager.class).createForSubscriptionId(subId); } finally { Binder.restoreCallingIdentity(token); } - // First check: does caller have carrier privilege? - if (tm != null && tm.hasCarrierPrivileges(subId)) { + // First check: does callingPackage have carrier privilege? + // Note that we can't call TelephonyManager.hasCarrierPrivileges() which will check if + // ourself has carrier privileges + if (tm != null && (tm.checkCarrierPrivilegesForPackage(callingPackage) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS)) { return; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index daa1042bb255..0d3c18ac339f 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -481,7 +481,8 @@ public class NotificationManagerService extends SystemService { Adjustment.KEY_SENSITIVE_CONTENT, Adjustment.KEY_RANKING_SCORE, Adjustment.KEY_NOT_CONVERSATION, - Adjustment.KEY_TYPE + Adjustment.KEY_TYPE, + Adjustment.KEY_SUMMARIZATION }; static final Integer[] DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES = new Integer[] { @@ -631,6 +632,8 @@ public class NotificationManagerService extends SystemService { // Minium number of sparse groups for a package before autogrouping them private static final int AUTOGROUP_SPARSE_GROUPS_AT_COUNT = 3; + private static final Duration ZEN_BROADCAST_DELAY = Duration.ofMillis(250); + private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; @@ -3169,6 +3172,24 @@ public class NotificationManagerService extends SystemService { sendRegisteredOnlyBroadcast(new Intent(action)); } + /** + * Schedules a broadcast to be sent to runtime receivers and DND-policy-access packages. The + * broadcast will be sent after {@link #ZEN_BROADCAST_DELAY}, unless a new broadcast is + * scheduled in the interim, in which case the previous one is dropped and the waiting period + * is <em>restarted</em>. + * + * <p>Note that this uses <em>equality of the {@link Intent#getAction}</em> as the criteria for + * deduplicating pending broadcasts, ignoring the extras and anything else. This is intentional + * so that e.g. rapidly changing some value A -> B -> C will only produce a broadcast for C + * (instead of every time because the extras are different). + */ + private void sendZenBroadcastWithDelay(Intent intent) { + String token = "zen_broadcast:" + intent.getAction(); + mHandler.removeCallbacksAndEqualMessages(token); + mHandler.postDelayed(() -> sendRegisteredOnlyBroadcast(intent), token, + ZEN_BROADCAST_DELAY.toMillis()); + } + private void sendRegisteredOnlyBroadcast(Intent baseIntent) { int[] userIds = mUmInternal.getProfileIds(mAmi.getCurrentUserId(), true); if (Flags.nmBinderPerfReduceZenBroadcasts()) { @@ -3362,14 +3383,25 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") private void updateEffectsSuppressorLocked() { + final long oldSuppressedEffects = mZenModeHelper.getSuppressedEffects(); final long updatedSuppressedEffects = calculateSuppressedEffects(); - if (updatedSuppressedEffects == mZenModeHelper.getSuppressedEffects()) return; + if (updatedSuppressedEffects == oldSuppressedEffects) return; + final List<ComponentName> suppressors = getSuppressors(); ZenLog.traceEffectsSuppressorChanged( - mEffectsSuppressors, suppressors, updatedSuppressedEffects); - mEffectsSuppressors = suppressors; + mEffectsSuppressors, suppressors, oldSuppressedEffects, updatedSuppressedEffects); mZenModeHelper.setSuppressedEffects(updatedSuppressedEffects); - sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); + + if (Flags.nmBinderPerfThrottleEffectsSuppressorBroadcast()) { + if (!suppressors.equals(mEffectsSuppressors)) { + mEffectsSuppressors = suppressors; + sendZenBroadcastWithDelay( + new Intent(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)); + } + } else { + mEffectsSuppressors = suppressors; + sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); + } } private void exitIdle() { @@ -3491,12 +3523,18 @@ public class NotificationManagerService extends SystemService { } private ArrayList<ComponentName> getSuppressors() { - ArrayList<ComponentName> names = new ArrayList<ComponentName>(); + ArrayList<ComponentName> names = new ArrayList<>(); for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) { ArraySet<ComponentName> serviceInfoList = mListenersDisablingEffects.valueAt(i); for (ComponentName info : serviceInfoList) { - names.add(info); + if (Flags.nmBinderPerfThrottleEffectsSuppressorBroadcast()) { + if (!names.contains(info)) { + names.add(info); + } + } else { + names.add(info); + } } } @@ -7212,7 +7250,7 @@ public class NotificationManagerService extends SystemService { if (!mAssistants.isAdjustmentAllowed(potentialKey)) { toRemove.add(potentialKey); } - if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) { + if (notificationClassification() && potentialKey.equals(KEY_TYPE)) { mAssistants.setNasUnsupportedDefaults(r.getSbn().getNormalizedUserId()); if (!mAssistants.isAdjustmentKeyTypeAllowed(adjustments.getInt(KEY_TYPE))) { toRemove.add(potentialKey); @@ -10395,7 +10433,8 @@ public class NotificationManagerService extends SystemService { r.getRankingScore(), r.isConversation(), r.getProposedImportance(), - r.hasSensitiveContent()); + r.hasSensitiveContent(), + r.getSummarization()); extractorDataBefore.put(r.getKey(), extractorData); mRankingHelper.extractSignals(r); } @@ -11715,7 +11754,8 @@ public class NotificationManagerService extends SystemService { : (record.getRankingScore() > 0 ? RANKING_PROMOTED : RANKING_DEMOTED), record.getNotification().isBubbleNotification(), record.getProposedImportance(), - hasSensitiveContent + hasSensitiveContent, + record.getSummarization() ); rankings.add(ranking); } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 81af0d8a6d80..52101e336920 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -25,6 +25,7 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.service.notification.Adjustment.KEY_SUMMARIZATION; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; @@ -225,6 +226,8 @@ public final class NotificationRecord { // type of the bundle if the notification was classified private @Adjustment.Types int mBundleType = Adjustment.TYPE_OTHER; + private String mSummarization = null; + public NotificationRecord(Context context, StatusBarNotification sbn, NotificationChannel channel) { this.sbn = sbn; @@ -589,6 +592,7 @@ public final class NotificationRecord { pw.println(prefix + "shortcut=" + notification.getShortcutId() + " found valid? " + (mShortcutInfo != null)); pw.println(prefix + "mUserVisOverride=" + getPackageVisibilityOverride()); + pw.println(prefix + "hasSummarization=" + (mSummarization != null)); } private void dumpNotification(PrintWriter pw, String prefix, Notification notification, @@ -811,6 +815,12 @@ public final class NotificationRecord { Adjustment.KEY_TYPE, mChannel.getId()); } + if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization()) + && signals.containsKey(KEY_SUMMARIZATION)) { + mSummarization = signals.getString(KEY_SUMMARIZATION); + EventLogTags.writeNotificationAdjusted(getKey(), + KEY_SUMMARIZATION, Boolean.toString(mSummarization != null)); + } if (!signals.isEmpty() && adjustment.getIssuer() != null) { mAdjustmentIssuer = adjustment.getIssuer(); } @@ -983,6 +993,13 @@ public final class NotificationRecord { return null; } + public String getSummarization() { + if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())) { + return mSummarization; + } + return null; + } + public boolean setIntercepted(boolean intercept) { mIntercept = intercept; mInterceptSet = true; diff --git a/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java b/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java index 3f4f7d3bbc38..9315ddc0d5b0 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java +++ b/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java @@ -47,6 +47,7 @@ public final class NotificationRecordExtractorData { private final boolean mIsConversation; private final int mProposedImportance; private final boolean mSensitiveContent; + private final String mSummarization; NotificationRecordExtractorData(int position, int visibility, boolean showBadge, boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey, @@ -54,7 +55,8 @@ public final class NotificationRecordExtractorData { Integer userSentiment, Integer suppressVisually, ArrayList<Notification.Action> systemSmartActions, ArrayList<CharSequence> smartReplies, int importance, float rankingScore, - boolean isConversation, int proposedImportance, boolean sensitiveContent) { + boolean isConversation, int proposedImportance, boolean sensitiveContent, + String summarization) { mPosition = position; mVisibility = visibility; mShowBadge = showBadge; @@ -73,6 +75,7 @@ public final class NotificationRecordExtractorData { mIsConversation = isConversation; mProposedImportance = proposedImportance; mSensitiveContent = sensitiveContent; + mSummarization = summarization; } // Returns whether the provided NotificationRecord differs from the cached data in any way. @@ -93,7 +96,8 @@ public final class NotificationRecordExtractorData { || !Objects.equals(mSmartReplies, r.getSmartReplies()) || mImportance != r.getImportance() || mProposedImportance != r.getProposedImportance() - || mSensitiveContent != r.hasSensitiveContent(); + || mSensitiveContent != r.hasSensitiveContent() + || !Objects.equals(mSummarization, r.getSummarization()); } // Returns whether the NotificationRecord has a change from this data for which we should @@ -117,6 +121,7 @@ public final class NotificationRecordExtractorData { || !r.rankingScoreMatches(mRankingScore) || mIsConversation != r.isConversation() || mProposedImportance != r.getProposedImportance() - || mSensitiveContent != r.hasSensitiveContent(); + || mSensitiveContent != r.hasSensitiveContent() + || !Objects.equals(mSummarization, r.getSummarization()); } } diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index 7e853d9d2d0b..49f93b8b7c16 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -140,8 +140,9 @@ public class ZenLog { } public static void traceEffectsSuppressorChanged(List<ComponentName> oldSuppressors, - List<ComponentName> newSuppressors, long suppressedEffects) { - append(TYPE_SUPPRESSOR_CHANGED, "suppressed effects:" + suppressedEffects + "," + List<ComponentName> newSuppressors, long oldSuppressedEffects, long suppressedEffects) { + append(TYPE_SUPPRESSOR_CHANGED, "suppressed effects:" + + oldSuppressedEffects + "->" + suppressedEffects + "," + componentListToString(oldSuppressors) + "->" + componentListToString(newSuppressors)); } diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 822ff48c831c..048f2b6b0cbc 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -182,6 +182,16 @@ flag { } flag { + name: "nm_binder_perf_throttle_effects_suppressor_broadcast" + namespace: "systemui" + description: "Delay sending the ACTION_EFFECTS_SUPPRESSOR_CHANGED broadcast if it changes too often" + bug: "371776935" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "fix_calling_uid_from_cps" namespace: "systemui" description: "Correctly checks zen rule ownership when a CPS notifies with a Condition" diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index 0b58c759b284..f011d283c8bb 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -96,6 +96,9 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; @@ -105,6 +108,11 @@ import java.util.function.Predicate; public final class DexOptHelper { private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000; + @NonNull + private static final ThreadPoolExecutor sDexoptExecutor = + new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */, + 60 /* keepAliveTime */, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); + private static boolean sArtManagerLocalIsInitialized = false; private final PackageManagerService mPm; @@ -113,6 +121,11 @@ public final class DexOptHelper { // used, to make it available to the onDexoptDone callback. private volatile long mBootDexoptStartTime; + static { + // Recycle the thread if it's not used for `keepAliveTime`. + sDexoptExecutor.allowsCoreThreadTimeOut(); + } + DexOptHelper(PackageManagerService pm) { mPm = pm; } @@ -746,43 +759,11 @@ public final class DexOptHelper { */ static void performDexoptIfNeeded(InstallRequest installRequest, DexManager dexManager, Context context, PackageManagerTracedLock.RawLock installLock) { - // Construct the DexoptOptions early to see if we should skip running dexopt. - // - // Do not run PackageDexOptimizer through the local performDexOpt - // method because `pkg` may not be in `mPackages` yet. - // - // Also, don't fail application installs if the dexopt step fails. DexoptOptions dexoptOptions = getDexoptOptionsByInstallRequest(installRequest, dexManager); - // Check whether we need to dexopt the app. - // - // NOTE: it is IMPORTANT to call dexopt: - // - after doRename which will sync the package data from AndroidPackage and - // its corresponding ApplicationInfo. - // - after installNewPackageLIF or replacePackageLIF which will update result with the - // uid of the application (pkg.applicationInfo.uid). - // This update happens in place! - // - // We only need to dexopt if the package meets ALL of the following conditions: - // 1) it is not an instant app or if it is then dexopt is enabled via gservices. - // 2) it is not debuggable. - // 3) it is not on Incremental File System. - // - // Note that we do not dexopt instant apps by default. dexopt can take some time to - // complete, so we skip this step during installation. Instead, we'll take extra time - // the first time the instant app starts. It's preferred to do it this way to provide - // continuous progress to the useur instead of mysteriously blocking somewhere in the - // middle of running an instant app. The default behaviour can be overridden - // via gservices. - // - // Furthermore, dexopt may be skipped, depending on the install scenario and current - // state of the device. - // - // TODO(b/174695087): instantApp and onIncremental should be removed and their install - // path moved to SCENARIO_FAST. + boolean performDexopt = + DexOptHelper.shouldPerformDexopt(installRequest, dexoptOptions, context); - final boolean performDexopt = DexOptHelper.shouldPerformDexopt(installRequest, - dexoptOptions, context); if (performDexopt) { // dexopt can take long, and ArtService doesn't require installd, so we release // the lock here and re-acquire the lock after dexopt is finished. @@ -791,6 +772,7 @@ public final class DexOptHelper { } try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); + // Don't fail application installs if the dexopt step fails. DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService( installRequest, dexoptOptions); installRequest.onDexoptFinished(dexOptResult); @@ -804,6 +786,41 @@ public final class DexOptHelper { } /** + * Same as above, but runs asynchronously. + */ + static CompletableFuture<Void> performDexoptIfNeededAsync(InstallRequest installRequest, + DexManager dexManager, Context context) { + // Construct the DexoptOptions early to see if we should skip running dexopt. + DexoptOptions dexoptOptions = getDexoptOptionsByInstallRequest(installRequest, dexManager); + boolean performDexopt = + DexOptHelper.shouldPerformDexopt(installRequest, dexoptOptions, context); + + if (performDexopt) { + return CompletableFuture + .runAsync(() -> { + try { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); + // Don't fail application installs if the dexopt step fails. + // TODO(jiakaiz): Make this async in ART Service. + DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService( + installRequest, dexoptOptions); + installRequest.onDexoptFinished(dexOptResult); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + }, sDexoptExecutor) + .exceptionally((t) -> { + // This should never happen. A normal dexopt failure should result in a + // DexoptResult.DEXOPT_FAILED, not an exception. + Slog.wtf(TAG, "Dexopt encountered a fatal error", t); + return null; + }); + } else { + return CompletableFuture.completedFuture(null); + } + } + + /** * Use ArtService to perform dexopt by the given InstallRequest. */ static DexoptResult dexoptPackageUsingArtService(InstallRequest installRequest, @@ -840,6 +857,20 @@ public final class DexOptHelper { */ static boolean shouldPerformDexopt(InstallRequest installRequest, DexoptOptions dexoptOptions, Context context) { + // We only need to dexopt if the package meets ALL of the following conditions: + // 1) it is not an instant app or if it is then dexopt is enabled via gservices. + // 2) it is not debuggable. + // 3) it is not on Incremental File System. + // + // Note that we do not dexopt instant apps by default. dexopt can take some time to + // complete, so we skip this step during installation. Instead, we'll take extra time + // the first time the instant app starts. It's preferred to do it this way to provide + // continuous progress to the user instead of mysteriously blocking somewhere in the + // middle of running an instant app. The default behaviour can be overridden + // via gservices. + // + // Furthermore, dexopt may be skipped, depending on the install scenario and current + // state of the device. final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0); final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0); final PackageSetting ps = installRequest.getScannedPackageSetting(); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 8eb5b6f11cb2..0c2782393879 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -1158,6 +1158,7 @@ final class InstallPackageHelper { return; } request.setKeepArtProfile(true); + // TODO(b/388159696): Use performDexoptIfNeededAsync. DexOptHelper.performDexoptIfNeeded(request, mDexManager, mContext, null); } } diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 14b0fc81fdd2..c62aaebf673b 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -584,6 +584,12 @@ public abstract class UserManagerInternal { * Returns the user id of the main user, or {@link android.os.UserHandle#USER_NULL} if there is * no main user. * + * <p>NB: Features should ideally not limit functionality to the main user. Ideally, they + * should either work for all users or for all admin users. If a feature should only work for + * select users, its determination of which user should be done intelligently or be + * customizable. Not all devices support a main user, and the idea of singling out one user as + * special is contrary to overall multiuser goals. + * * @see UserManager#isMainUser() */ public abstract @UserIdInt int getMainUserId(); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index a2c53e56b9c9..8cbccf5feead 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -3590,8 +3590,6 @@ public class UserManagerService extends IUserManager.Stub { } /** - * @hide - * * Returns who set a user restriction on a user. * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * @param restrictionKey the string key representing the restriction @@ -6275,9 +6273,6 @@ public class UserManagerService extends IUserManager.Stub { } } - /** - * @hide - */ @Override public @NonNull UserInfo createRestrictedProfileWithThrow( @Nullable String name, @UserIdInt int parentUserId) @@ -8504,7 +8499,6 @@ public class UserManagerService extends IUserManager.Stub { } /** - * @hide * Checks whether to show a notification for sounds (e.g., alarms, timers, etc.) from * background users. */ diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 7f511e1e2aa1..283979483e73 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3801,10 +3801,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { true /* leftOrTop */); notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT); - } else if (event.isAltPressed()) { - setSplitscreenFocus(true /* leftOrTop */); - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT); } else { notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_BACK); @@ -3821,11 +3817,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT); return true; - } else if (event.isAltPressed()) { - setSplitscreenFocus(false /* leftOrTop */); - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT); - return true; } } break; @@ -4241,9 +4232,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION: case KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE: case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT: - case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT: case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT: - case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT: case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER: case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP: case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN: @@ -4379,22 +4368,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { true /* leftOrTop */); } return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT: - if (complete) { - setSplitscreenFocus(true /* leftOrTop */); - } - return true; case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT: if (complete) { moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyGestureEvent(event), false /* leftOrTop */); } return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT: - if (complete) { - setSplitscreenFocus(false /* leftOrTop */); - } - return true; case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER: if (complete) { toggleKeyboardShortcutsMenu(deviceId); @@ -5084,13 +5063,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void setSplitscreenFocus(boolean leftOrTop) { - StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); - if (statusbar != null) { - statusbar.setSplitscreenFocus(leftOrTop); - } - } - void launchHomeFromHotKey(int displayId) { launchHomeFromHotKey(displayId, true /* awakenFromDreams */, true /*respectKeyguard*/); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index 4827c9f414ad..0ed522805bef 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -56,12 +56,12 @@ public interface StatusBarManagerInternal { void toggleKeyboardShortcutsMenu(int deviceId); /** - * Used by InputMethodManagerService to notify the IME status. + * Sets the new IME window status. * - * @param displayId The display to which the IME is bound to. - * @param vis The IME visibility. - * @param backDisposition The IME back disposition. - * @param showImeSwitcher {@code true} when the IME switcher button should be shown. + * @param displayId The id of the display to which the IME is bound. + * @param vis The IME window visibility. + * @param backDisposition The IME back disposition mode. + * @param showImeSwitcher Whether the IME Switcher button should be shown. */ void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, @BackDispositionMode int backDisposition, boolean showImeSwitcher); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index a8deeeac311d..83e146df3e53 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -118,6 +118,7 @@ import android.service.wallpaper.WallpaperService; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; +import android.util.ArraySet; import android.util.EventLog; import android.util.IntArray; import android.util.Slog; @@ -160,6 +161,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; @@ -772,6 +774,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private final ComponentName mImageWallpaper; /** + * Name of the component that is used when the user-selected wallpaper is incompatible with the + * display's resolution or aspect ratio. + */ + @Nullable private final ComponentName mFallbackWallpaperComponent; + + /** * Default image wallpaper shall never changed after system service started, caching it when we * first read the image file. */ @@ -799,12 +807,38 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final WallpaperDisplayHelper mWallpaperDisplayHelper; final WallpaperCropper mWallpaperCropper; - private boolean supportsMultiDisplay(WallpaperConnection connection) { - if (connection != null) { - return connection.mInfo == null // This is image wallpaper - || connection.mInfo.supportsMultipleDisplays(); + // TODO(b/384519749): Remove this set after we introduce the aspect ratio check. + private final Set<Integer> mWallpaperCompatibleDisplaysForTest = new ArraySet<>(); + + private boolean isWallpaperCompatibleForDisplay(int displayId, WallpaperConnection connection) { + if (connection == null) { + return false; } - return false; + // Non image wallpaper. + if (connection.mInfo != null) { + return connection.mInfo.supportsMultipleDisplays(); + } + + // Image wallpaper + if (enableConnectedDisplaysWallpaper()) { + // TODO(b/384519749): check display's resolution and image wallpaper cropped image + // aspect ratio. + return displayId == DEFAULT_DISPLAY + || mWallpaperCompatibleDisplaysForTest.contains(displayId); + } + // When enableConnectedDisplaysWallpaper is off, we assume the image wallpaper supports all + // usable displays. + return true; + } + + @VisibleForTesting + void addWallpaperCompatibleDisplayForTest(int displayId) { + mWallpaperCompatibleDisplaysForTest.add(displayId); + } + + @VisibleForTesting + void removeWallpaperCompatibleDisplayForTest(int displayId) { + mWallpaperCompatibleDisplaysForTest.remove(displayId); } private void updateFallbackConnection() { @@ -815,7 +849,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.w(TAG, "Fallback wallpaper connection has not been created yet!!"); return; } - if (supportsMultiDisplay(systemConnection)) { + // TODO(b/384520326) Passing DEFAULT_DISPLAY temporarily before we revamp the + // multi-display supports. + if (isWallpaperCompatibleForDisplay(DEFAULT_DISPLAY, systemConnection)) { if (fallbackConnection.mDisplayConnector.size() != 0) { fallbackConnection.forEachDisplayConnector(connector -> { if (connector.mEngine != null) { @@ -990,16 +1026,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private void initDisplayState() { // Do not initialize fallback wallpaper if (!mWallpaper.equals(mFallbackWallpaper)) { - if (supportsMultiDisplay(this)) { - // The system wallpaper is image wallpaper or it can supports multiple displays. - appendConnectorWithCondition(display -> - mWallpaperDisplayHelper.isUsableDisplay(display, mClientUid)); - } else { - // The system wallpaper does not support multiple displays, so just attach it on - // default display. - mDisplayConnector.append(DEFAULT_DISPLAY, - new DisplayConnector(DEFAULT_DISPLAY)); - } + appendConnectorWithCondition(display -> { + final int displayId = display.getDisplayId(); + if (display.getDisplayId() == DEFAULT_DISPLAY) { + return true; + } + return mWallpaperDisplayHelper.isUsableDisplay(display, mClientUid) + && isWallpaperCompatibleForDisplay(displayId, /* connection= */ this); + }); } } @@ -1601,6 +1635,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mShuttingDown = false; mImageWallpaper = ComponentName.unflattenFromString( context.getResources().getString(R.string.image_wallpaper_component)); + if (enableConnectedDisplaysWallpaper()) { + mFallbackWallpaperComponent = ComponentName.unflattenFromString( + context.getResources().getString(R.string.fallback_wallpaper_component)); + } else { + mFallbackWallpaperComponent = null; + } ComponentName defaultComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context); mDefaultWallpaperComponent = defaultComponent == null ? mImageWallpaper : defaultComponent; mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); @@ -3613,6 +3653,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (componentName != null && !componentName.equals(mImageWallpaper)) { // The requested component is not the static wallpaper service, so make sure it's // actually a wallpaper service. + if (mFallbackWallpaperComponent != null + && componentName.equals(mFallbackWallpaperComponent)) { + // The fallback wallpaper does not declare WallpaperService.SERVICE_INTERFACE + // action in its intent filter to prevent it from being listed in the wallpaper + // picker. And thus, use explicit intent to query the metadata. + intent = new Intent().setComponent(mFallbackWallpaperComponent); + } List<ResolveInfo> ris = mIPackageManager.queryIntentServices(intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), @@ -3971,7 +4018,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub for (int i = 0; i < wallpapers.size(); i++) { WallpaperData wallpaper = wallpapers.get(i); - if (supportsMultiDisplay(wallpaper.connection)) { + if (isWallpaperCompatibleForDisplay(displayId, wallpaper.connection)) { final DisplayConnector connector = wallpaper.connection.getDisplayConnectorOrCreate(displayId); if (connector != null) { @@ -3993,7 +4040,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } mFallbackWallpaper.mWhich = useFallbackWallpaperWhich; } else { - if (supportsMultiDisplay(mLastWallpaper.connection)) { + if (isWallpaperCompatibleForDisplay(displayId, mLastWallpaper.connection)) { final DisplayConnector connector = mLastWallpaper.connection.getDisplayConnectorOrCreate(displayId); if (connector == null) return; @@ -4100,8 +4147,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mFallbackWallpaper.allowBackup = false; mFallbackWallpaper.wallpaperId = makeWallpaperIdLocked(); mFallbackWallpaper.mBindSource = BindSource.INITIALIZE_FALLBACK; - bindWallpaperComponentLocked(mDefaultWallpaperComponent, true, false, - mFallbackWallpaper, null); + if (mFallbackWallpaperComponent == null) { + bindWallpaperComponentLocked(mDefaultWallpaperComponent, true, false, + mFallbackWallpaper, null); + } else { + mFallbackWallpaper.mWhich = FLAG_SYSTEM | FLAG_LOCK; + bindWallpaperComponentLocked(mFallbackWallpaperComponent, true, false, + mFallbackWallpaper, null); + } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 1d12c561f118..064ef1aa0eff 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3231,8 +3231,8 @@ final class ActivityRecord extends WindowToken { * Returns {@code true} if the fixed orientation, aspect ratio, resizability of the application * can be ignored. */ - static boolean canBeUniversalResizeable(ApplicationInfo appInfo, WindowManagerService wms, - boolean isLargeScreen, boolean forActivity) { + static boolean canBeUniversalResizeable(@NonNull ApplicationInfo appInfo, + WindowManagerService wms, boolean isLargeScreen, boolean forActivity) { if (appInfo.category == ApplicationInfo.CATEGORY_GAME) { return false; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index d4f9c0901162..cf111cdbcc6a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2154,6 +2154,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + /** + * @return ehether the application could be universal resizeable on a large screen, + * ignoring any overrides + */ + @Override + public boolean canBeUniversalResizeable(@NonNull ApplicationInfo appInfo) { + return ActivityRecord.canBeUniversalResizeable(appInfo, mWindowManager, + /* isLargeScreen */ true, /* forActivity */ false); + } + @Override public void removeAllVisibleRecentTasks() { mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeAllVisibleRecentTasks()"); diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp index 2add5b09f15b..24909ac6150b 100644 --- a/services/core/jni/com_android_server_utils_AnrTimer.cpp +++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp @@ -101,6 +101,9 @@ nsecs_t now() { return systemTime(SYSTEM_TIME_MONOTONIC); } +// The current process. This is cached here on startup. +const pid_t sThisProcess = getpid(); + // Return true if the process exists and false if we cannot know. bool processExists(pid_t pid) { char path[PATH_MAX]; @@ -726,7 +729,7 @@ class AnrTimerService::Timer { uid(uid), timeout(timeout), extend(extend), - freeze(pid != 0 && freeze), + freeze(freeze), split(trace.earlyTimeout), action(trace.action), status(Running), @@ -1188,8 +1191,11 @@ const char* AnrTimerService::statusString(Status s) { } AnrTimerService::timer_id_t AnrTimerService::start(int pid, int uid, nsecs_t timeout) { + // Use the freezer only if the pid is not 0 (a nonsense value) and the pid is not self. + // Freezing the current process is a fatal error. + bool useFreezer = freeze_ && (pid != 0) && (pid != sThisProcess); AutoMutex _l(lock_); - Timer t(pid, uid, timeout, extend_, freeze_, tracer_.getConfig(pid)); + Timer t(pid, uid, timeout, extend_, useFreezer, tracer_.getConfig(pid)); insertLocked(t); t.start(); counters_.started++; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java index 27eada013642..89b48bad2358 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java @@ -16,8 +16,6 @@ package com.android.server.am; -import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE; -import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.Process.INVALID_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -29,11 +27,9 @@ import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANC import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED; import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED; import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED; -import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.cancelReasonToString; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -43,11 +39,9 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; -import android.app.BackgroundStartPrivileges; import android.app.PendingIntent; import android.content.Intent; import android.content.pm.IPackageManager; -import android.os.Binder; import android.os.Looper; import android.os.UserHandle; @@ -185,34 +179,6 @@ public class PendingIntentControllerTest { } } - @Test - public void testClearAllowBgActivityStartsClearsToken() { - final PendingIntentRecord pir = createPendingIntentRecord(0); - Binder token = new Binder(); - pir.setAllowBgActivityStarts(token, FLAG_ACTIVITY_SENDER); - assertEquals(BackgroundStartPrivileges.allowBackgroundActivityStarts(token), - pir.getBackgroundStartPrivilegesForActivitySender(token)); - pir.clearAllowBgActivityStarts(token); - assertEquals(BackgroundStartPrivileges.NONE, - pir.getBackgroundStartPrivilegesForActivitySender(token)); - } - - @Test - public void testClearAllowBgActivityStartsClearsDuration() { - final PendingIntentRecord pir = createPendingIntentRecord(0); - Binder token = new Binder(); - pir.setAllowlistDurationLocked(token, 1000, - TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, REASON_NOTIFICATION_SERVICE, - "NotificationManagerService"); - PendingIntentRecord.TempAllowListDuration allowlistDurationLocked = - pir.getAllowlistDurationLocked(token); - assertEquals(1000, allowlistDurationLocked.duration); - pir.clearAllowBgActivityStarts(token); - PendingIntentRecord.TempAllowListDuration allowlistDurationLockedAfterClear = - pir.getAllowlistDurationLocked(token); - assertNull(allowlistDurationLockedAfterClear); - } - private void assertCancelReason(int expectedReason, int actualReason) { final String errMsg = "Expected: " + cancelReasonToString(expectedReason) + "; Actual: " + cancelReasonToString(actualReason); diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index 1cf655675a30..793ba7bc89bd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -144,6 +144,8 @@ public class WallpaperManagerServiceTests { private static ComponentName sImageWallpaperComponentName; private static ComponentName sDefaultWallpaperComponent; + private static ComponentName sFallbackWallpaperComponentName; + private IPackageManager mIpm = AppGlobals.getPackageManager(); @Mock @@ -195,6 +197,8 @@ public class WallpaperManagerServiceTests { sContext.getResources().getString(R.string.image_wallpaper_component)); // Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper. sDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(sContext); + sFallbackWallpaperComponentName = ComponentName.unflattenFromString( + sContext.getResources().getString(R.string.fallback_wallpaper_component)); if (sDefaultWallpaperComponent == null) { sDefaultWallpaperComponent = sImageWallpaperComponentName; @@ -205,6 +209,9 @@ public class WallpaperManagerServiceTests { } sContext.addMockService(sImageWallpaperComponentName, sWallpaperService); + if (sFallbackWallpaperComponentName != null) { + sContext.addMockService(sFallbackWallpaperComponentName, sWallpaperService); + } } @AfterClass @@ -216,6 +223,7 @@ public class WallpaperManagerServiceTests { LocalServices.removeServiceForTest(WindowManagerInternal.class); sImageWallpaperComponentName = null; sDefaultWallpaperComponent = null; + sFallbackWallpaperComponentName = null; reset(sContext); } @@ -306,6 +314,7 @@ public class WallpaperManagerServiceTests { * Tests that internal basic data should be correct after boot up. */ @Test + @DisableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) public void testDataCorrectAfterBoot() { mService.switchUser(USER_SYSTEM, null); @@ -719,7 +728,7 @@ public class WallpaperManagerServiceTests { final int testDisplayId = 2; setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); - // WHEN displayId, 2, is ready. + // WHEN display ID, 2, is ready. WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( WallpaperManagerInternal.class); wallpaperManagerInternal.onDisplayReady(testDisplayId); @@ -759,7 +768,7 @@ public class WallpaperManagerServiceTests { final int testDisplayId = 2; setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); - // WHEN displayId, 2, is ready. + // WHEN display ID, 2, is ready. WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( WallpaperManagerInternal.class); wallpaperManagerInternal.onDisplayReady(testDisplayId); @@ -791,6 +800,40 @@ public class WallpaperManagerServiceTests { /* info= */ any(), /* description= */ any()); } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void displayAdded_wallpaperIncompatibleForDisplay_shouldAttachFallbackWallpaperService() + throws Exception { + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + IWallpaperService mockIWallpaperService = mock(IWallpaperService.class); + mService.mFallbackWallpaper.connection.mService = mockIWallpaperService; + // GIVEN there are two displays: DEFAULT_DISPLAY, 2 + final int testDisplayId = 2; + setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); + // GIVEN the wallpaper isn't compatible with display ID, 2 + mService.removeWallpaperCompatibleDisplayForTest(testDisplayId); + + // WHEN display ID, 2, is ready. + WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( + WallpaperManagerInternal.class); + wallpaperManagerInternal.onDisplayReady(testDisplayId); + + // Then there is a connection established for the fallback wallpaper for display ID, 2. + verify(mockIWallpaperService).attach( + /* connection= */ eq(mService.mFallbackWallpaper.connection), + /* windowToken= */ any(), + /* windowType= */ anyInt(), + /* isPreview= */ anyBoolean(), + /* reqWidth= */ anyInt(), + /* reqHeight= */ anyInt(), + /* padding= */ any(), + /* displayId= */ eq(testDisplayId), + /* which= */ eq(FLAG_SYSTEM | FLAG_LOCK), + /* info= */ any(), + /* description= */ any()); + } // Verify a secondary display added end // Verify a secondary display removed started @@ -810,7 +853,7 @@ public class WallpaperManagerServiceTests { // GIVEN there are two displays: DEFAULT_DISPLAY, 2 final int testDisplayId = 2; setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); - // GIVEN wallpaper connections have been established for displayID, 2. + // GIVEN wallpaper connections have been established for display ID, 2. WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( WallpaperManagerInternal.class); wallpaperManagerInternal.onDisplayReady(testDisplayId); @@ -818,11 +861,11 @@ public class WallpaperManagerServiceTests { WallpaperManagerService.DisplayConnector displayConnector = wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); - // WHEN displayId, 2, is removed. + // WHEN display ID, 2, is removed. DisplayListener displayListener = displayListenerCaptor.getValue(); displayListener.onDisplayRemoved(testDisplayId); - // Then the wallpaper connection for displayId, 2, is detached. + // Then the wallpaper connection for display ID, 2, is detached. verify(mockIWallpaperService).detach(eq(displayConnector.mToken)); } @@ -848,27 +891,75 @@ public class WallpaperManagerServiceTests { // GIVEN there are two displays: DEFAULT_DISPLAY, 2 final int testDisplayId = 2; setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); - // GIVEN wallpaper connections have been established for displayID, 2. + // GIVEN wallpaper connections have been established for display ID, 2. WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( WallpaperManagerInternal.class); wallpaperManagerInternal.onDisplayReady(testDisplayId); - // Save displayConnectors for displayId 2 before display removal. + // Save displayConnectors for display ID, 2, before display removal. WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector = systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); WallpaperManagerService.DisplayConnector lockWallpaperDisplayConnector = lockWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); - // WHEN displayId, 2, is removed. + // WHEN display ID, 2, is removed. DisplayListener displayListener = displayListenerCaptor.getValue(); displayListener.onDisplayRemoved(testDisplayId); - // Then the system wallpaper connection for displayId, 2, is detached. + // Then the system wallpaper connection for display ID, 2, is detached. verify(mockIWallpaperService).detach(eq(systemWallpaperDisplayConnector.mToken)); - // Then the lock wallpaper connection for displayId, 2, is detached. + // Then the lock wallpaper connection for display ID, 2, is detached. verify(mockIWallpaperService).detach(eq(lockWallpaperDisplayConnector.mToken)); } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void displayRemoved_fallbackWallpaper_shouldDetachFallbackWallpaperService() + throws Exception { + ArgumentCaptor<DisplayListener> displayListenerCaptor = ArgumentCaptor.forClass( + DisplayListener.class); + verify(mDisplayManager).registerDisplayListener(displayListenerCaptor.capture(), eq(null)); + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + IWallpaperService mockIWallpaperService = mock(IWallpaperService.class); + mService.mFallbackWallpaper.connection.mService = mockIWallpaperService; + // GIVEN there are two displays: DEFAULT_DISPLAY, 2 + final int testDisplayId = 2; + setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); + // GIVEN display ID, 2, is incompatible with the wallpaper. + mService.removeWallpaperCompatibleDisplayForTest(testDisplayId); + // GIVEN wallpaper connections have been established for display ID, 2. + WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( + WallpaperManagerInternal.class); + wallpaperManagerInternal.onDisplayReady(testDisplayId); + // Save fallback wallpaper displayConnector for display ID, 2, before display removal. + WallpaperManagerService.DisplayConnector fallbackWallpaperConnector = + mService.mFallbackWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); + + // WHEN displayId, 2, is removed. + DisplayListener displayListener = displayListenerCaptor.getValue(); + displayListener.onDisplayRemoved(testDisplayId); + + // Then the fallback wallpaper connection for display ID, 2, is detached. + verify(mockIWallpaperService).detach(eq(fallbackWallpaperConnector.mToken)); + } // Verify a secondary display removed ended + // Test fallback wallpaper after enabling connected display supports. + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void testFallbackWallpaperForConnectedDisplays() { + final WallpaperData fallbackData = mService.mFallbackWallpaper; + + assertWithMessage( + "The connected wallpaper component should be fallback wallpaper component from " + + "config file.") + .that(fallbackData.connection.mWallpaper.getComponent()) + .isEqualTo(sFallbackWallpaperComponentName); + assertWithMessage("Fallback wallpaper should support both lock & system.") + .that(fallbackData.mWhich) + .isEqualTo(FLAG_LOCK | FLAG_SYSTEM); + } + // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for // non-current user must not bind to wallpaper service. private void verifyNoConnectionBeforeLastUser(int lastUserId) { @@ -893,11 +984,11 @@ public class WallpaperManagerServiceTests { final WallpaperData lastLockData = mService.mLastLockWallpaper; assertWithMessage("Last wallpaper must not be null").that(lastLockData).isNotNull(); assertWithMessage("Last wallpaper component must be equals.") - .that(expectedComponent) - .isEqualTo(lastLockData.getComponent()); + .that(lastLockData.getComponent()) + .isEqualTo(expectedComponent); assertWithMessage("The user id in last wallpaper should be the last switched user") - .that(lastUserId) - .isEqualTo(lastLockData.userId); + .that(lastLockData.userId) + .isEqualTo(lastUserId); assertWithMessage("Must exist user data connection on last wallpaper data") .that(lastLockData.connection) .isNotNull(); @@ -946,6 +1037,7 @@ public class WallpaperManagerServiceTests { doReturn(mockDisplay).when(mDisplayManager).getDisplay(eq(displayId)); doReturn(displayId).when(mockDisplay).getDisplayId(); doReturn(true).when(mockDisplay).hasAccess(anyInt()); + mService.addWallpaperCompatibleDisplayForTest(displayId); } doReturn(mockDisplays).when(mDisplayManager).getDisplays(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 5602fb76e6f5..28e5be505556 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -68,7 +68,6 @@ import android.annotation.UserIdInt; import android.app.PendingIntent; import android.app.RemoteAction; import android.app.admin.DevicePolicyManager; -import android.app.ecm.EnhancedConfirmationManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -2120,23 +2119,6 @@ public class AccessibilityManagerServiceTest { } @Test - @EnableFlags({android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED, - android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS}) - public void isAccessibilityTargetAllowed_nonSystemUserId_useEcmWithNonSystemUserId() { - String fakePackageName = "FAKE_PACKAGE_NAME"; - int uid = 0; // uid is not used in the actual implementation when flags are on - int userId = mTestableContext.getUserId() + 1234; - when(mDevicePolicyManager.getPermittedAccessibilityServices(userId)).thenReturn( - List.of(fakePackageName)); - Context mockUserContext = mock(Context.class); - mTestableContext.addMockUserContext(userId, mockUserContext); - - mA11yms.isAccessibilityTargetAllowed(fakePackageName, uid, userId); - - verify(mockUserContext).getSystemService(EnhancedConfirmationManager.class); - } - - @Test @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) public void handleKeyGestureEvent_toggleMagnifier() { mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY); diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java index ef9580c54de6..8d3eef4a3168 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java @@ -38,6 +38,7 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.media.IAudioDeviceVolumeDispatcher; import android.media.VolumeInfo; +import android.os.IpcDataCache; import android.os.PermissionEnforcer; import android.os.RemoteException; import android.os.test.TestLooper; @@ -83,6 +84,7 @@ public class AbsoluteVolumeBehaviorTest { @Before public void setUp() throws Exception { + IpcDataCache.disableForTestMode(); mTestLooper = new TestLooper(); mContext = spy(ApplicationProvider.getApplicationContext()); diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java index 746645a8c585..541dbba67957 100644 --- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java @@ -28,6 +28,7 @@ import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.IDeviceVolumeBehaviorDispatcher; +import android.os.IpcDataCache; import android.os.PermissionEnforcer; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; @@ -69,6 +70,7 @@ public class DeviceVolumeBehaviorTest { @Before public void setUp() throws Exception { + IpcDataCache.disableForTestMode(); mTestLooper = new TestLooper(); mContext = InstrumentationRegistry.getTargetContext(); mAudioSystem = new NoOpAudioSystemAdapter(); diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java index 6b41c434b80f..0bbae247d8bb 100644 --- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java @@ -80,6 +80,7 @@ import android.media.AudioSystem; import android.media.IDeviceVolumeBehaviorDispatcher; import android.media.VolumeInfo; import android.media.audiopolicy.AudioVolumeGroup; +import android.os.IpcDataCache; import android.os.Looper; import android.os.PermissionEnforcer; import android.os.test.TestLooper; @@ -210,6 +211,8 @@ public class VolumeHelperTest { @Before public void setUp() throws Exception { + IpcDataCache.disableForTestMode(); + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); mTestLooper = new TestLooper(); diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 2f3bca031f46..fbb673a194b4 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -1762,7 +1762,8 @@ public final class DataManagerTest { /* rankingAdjustment= */ 0, /* isBubble= */ false, /* proposedImportance= */ 0, - /* sensitiveContent= */ false + /* sensitiveContent= */ false, + /* summarization = */ null ); return true; }).when(mRankingMap).getRanking(eq(key), @@ -1806,7 +1807,8 @@ public final class DataManagerTest { /* rankingAdjustment= */ 0, /* isBubble= */ false, /* proposedImportance= */ 0, - /* sensitiveContent= */ false + /* sensitiveContent= */ false, + /* summarization = */ null ); return true; }).when(mRankingMap).getRanking(eq(CUSTOM_KEY), diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java index 5c2132f0e0e9..1c0ee09ccc6f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java @@ -336,7 +336,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { checkRequestPinShortcut(makeResultIntent()); } - public void testRequestPinShortcut_explicitTargetActivity() { + public void disabled_testRequestPinShortcut_explicitTargetActivity() { setDefaultLauncher(USER_10, LAUNCHER_1); setDefaultLauncher(USER_11, LAUNCHER_2); @@ -475,7 +475,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { } - public void testRequestPinShortcut_dynamicExists() { + public void disabled_testRequestPinShortcut_dynamicExists() { setDefaultLauncher(USER_10, LAUNCHER_1); final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); @@ -590,7 +590,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { }); } - public void testRequestPinShortcut_dynamicExists_alreadyPinned() { + public void disabled_testRequestPinShortcut_dynamicExists_alreadyPinned() { setDefaultLauncher(USER_10, LAUNCHER_1); final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); @@ -848,7 +848,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { }); } - public void testRequestPinShortcut_dynamicExists_alreadyPinnedByAnother() { + public void disabled_testRequestPinShortcut_dynamicExists_alreadyPinnedByAnother() { // Initially all launchers have the shortcut permission, until we call setDefaultLauncher(). runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { @@ -1041,7 +1041,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { /** * When trying to pin an existing shortcut, the new fields shouldn't override existing fields. */ - public void testRequestPinShortcut_dynamicExists_titleWontChange() { + public void disabled_testRequestPinShortcut_dynamicExists_titleWontChange() { setDefaultLauncher(USER_10, LAUNCHER_1); final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); @@ -1173,7 +1173,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { * The dynamic shortcut existed, but before accepting(), it's removed. Because the request * has a partial shortcut, accept() should fail. */ - public void testRequestPinShortcut_dynamicExists_thenRemoved_error() { + public void disabled_testRequestPinShortcut_dynamicExists_thenRemoved_error() { setDefaultLauncher(USER_10, LAUNCHER_1); runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { @@ -1231,7 +1231,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { * The dynamic shortcut existed, but before accepting(), it's removed. Because the request * has all the mandatory fields, we can go ahead and still publish it. */ - public void testRequestPinShortcut_dynamicExists_thenRemoved_okay() { + public void disabled_testRequestPinShortcut_dynamicExists_thenRemoved_okay() { setDefaultLauncher(USER_10, LAUNCHER_1); runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { @@ -1404,7 +1404,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { * The dynamic shortcut existed, but before accepting(), it's removed. Because the request * has a partial shortcut, accept() should fail. */ - public void testRequestPinShortcut_dynamicExists_thenDisabled_error() { + public void disabled_testRequestPinShortcut_dynamicExists_thenDisabled_error() { setDefaultLauncher(USER_10, LAUNCHER_1); runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index 8fad01a7541d..2616ccbe474a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -247,7 +247,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { getRankingAdjustment(i), isBubble(i), getProposedImportance(i), - hasSensitiveContent(i) + hasSensitiveContent(i), + getSummarization(i) ); rankings[i] = ranking; } @@ -383,6 +384,13 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { return index % 3 == 0; } + public static String getSummarization(int index) { + if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())) { + return "summary " + index; + } + return null; + } + private boolean isBubble(int index) { return index % 4 == 0; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index edfdb4ffec3a..a8fcadd9dd74 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -51,6 +51,7 @@ import static android.app.NotificationChannel.RECS_ID; import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE; import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED; +import static android.app.NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED; import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED; import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; @@ -123,7 +124,9 @@ import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICAT import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN; @@ -163,6 +166,7 @@ import static junit.framework.Assert.fail; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyLong; @@ -360,7 +364,6 @@ import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; -import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; @@ -368,6 +371,9 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; @@ -383,9 +389,6 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @RunWithLooper @@ -600,7 +603,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { - return FlagsParameterization.allCombinationsOf(); + return FlagsParameterization.allCombinationsOf( + FLAG_NOTIFICATION_CLASSIFICATION); } public NotificationManagerServiceTest(FlagsParameterization flags) { @@ -10889,6 +10893,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Bundle signals = new Bundle(); signals.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW); signals.putInt(KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE); + signals.putInt(KEY_TYPE, TYPE_PROMOTION); Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); @@ -11413,8 +11418,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mContext).sendBroadcastAsUser(eqIntent(expected), eq(UserHandle.of(mUserId))); } + private static Intent isIntentWithAction(String wantedAction) { + return argThat( + intent -> intent != null && wantedAction.equals(intent.getAction()) + ); + } + private static Intent eqIntent(Intent wanted) { - return ArgumentMatchers.argThat( + return argThat( new ArgumentMatcher<Intent>() { @Override public boolean matches(Intent argument) { @@ -17491,6 +17502,66 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_EFFECTS_SUPPRESSOR_BROADCAST, + Flags.FLAG_NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS}) + public void requestHintsFromListener_changingEffectsButNotSuppressor_noBroadcast() + throws Exception { + // Note that NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS is not strictly necessary; however each + // path will do slightly different calls so we force one of them to simplify the test. + when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + INotificationListener token = mock(INotificationListener.class); + mService.isSystemUid = true; + + mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_CALL_EFFECTS); + mTestableLooper.moveTimeForward(500); // more than ZEN_BROADCAST_DELAY + waitForIdle(); + + verify(mContext, times(1)).sendBroadcastMultiplePermissions( + isIntentWithAction(ACTION_EFFECTS_SUPPRESSOR_CHANGED), any(), any(), any()); + + // Same suppressor suppresses something else. + mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_NOTIFICATION_EFFECTS); + mTestableLooper.moveTimeForward(500); // more than ZEN_BROADCAST_DELAY + waitForIdle(); + + // Still 1 total calls (the previous one). + verify(mContext, times(1)).sendBroadcastMultiplePermissions( + isIntentWithAction(ACTION_EFFECTS_SUPPRESSOR_CHANGED), any(), any(), any()); + } + + @Test + @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_EFFECTS_SUPPRESSOR_BROADCAST, + Flags.FLAG_NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS}) + public void requestHintsFromListener_changingSuppressor_throttlesBroadcast() throws Exception { + // Note that NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS is not strictly necessary; however each + // path will do slightly different calls so we force one of them to simplify the test. + when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + INotificationListener token = mock(INotificationListener.class); + mService.isSystemUid = true; + + // Several updates in quick succession. + mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_CALL_EFFECTS); + mBinderService.clearRequestedListenerHints(token); + mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_NOTIFICATION_EFFECTS); + mBinderService.clearRequestedListenerHints(token); + mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_CALL_EFFECTS); + mBinderService.clearRequestedListenerHints(token); + mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_NOTIFICATION_EFFECTS); + + // No broadcasts yet! + verify(mContext, never()).sendBroadcastMultiplePermissions(any(), any(), any(), any()); + + mTestableLooper.moveTimeForward(500); // more than ZEN_BROADCAST_DELAY + waitForIdle(); + + // Only one broadcast after idle time. + verify(mContext, times(1)).sendBroadcastMultiplePermissions( + isIntentWithAction(ACTION_EFFECTS_SUPPRESSOR_CHANGED), any(), any(), any()); + } + + @Test @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) public void testApplyAdjustment_keyType_validType() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java index 9fe0e49c4ab8..09ebc23a1d7b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java @@ -29,12 +29,19 @@ import android.os.UserHandle; import android.service.notification.Adjustment; import android.service.notification.StatusBarNotification; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + import com.android.server.UiServiceTestCase; +import org.junit.Rule; import org.junit.Test; public class NotificationRecordExtractorDataTest extends UiServiceTestCase { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Test public void testHasDiffs_noDiffs() { NotificationRecord r = generateRecord(); @@ -57,7 +64,8 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase { r.getRankingScore(), r.isConversation(), r.getProposedImportance(), - r.hasSensitiveContent()); + r.hasSensitiveContent(), + r.getSummarization()); assertFalse(extractorData.hasDiffForRankingLocked(r, 1)); assertFalse(extractorData.hasDiffForLoggingLocked(r, 1)); @@ -85,7 +93,8 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase { r.getRankingScore(), r.isConversation(), r.getProposedImportance(), - r.hasSensitiveContent()); + r.hasSensitiveContent(), + r.getSummarization()); Bundle signals = new Bundle(); signals.putInt(Adjustment.KEY_IMPORTANCE_PROPOSAL, IMPORTANCE_HIGH); @@ -119,7 +128,8 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase { r.getRankingScore(), r.isConversation(), r.getProposedImportance(), - r.hasSensitiveContent()); + r.hasSensitiveContent(), + r.getSummarization()); Bundle signals = new Bundle(); signals.putString(Adjustment.KEY_GROUP_KEY, "ranker_group"); @@ -154,7 +164,8 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase { r.getRankingScore(), r.isConversation(), r.getProposedImportance(), - r.hasSensitiveContent()); + r.hasSensitiveContent(), + r.getSummarization()); Bundle signals = new Bundle(); signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, true); @@ -166,6 +177,42 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase { assertTrue(extractorData.hasDiffForLoggingLocked(r, 1)); } + @Test + @EnableFlags(android.app.Flags.FLAG_NM_SUMMARIZATION) + public void testHasDiffs_summarization() { + NotificationRecord r = generateRecord(); + + NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData( + 1, + r.getPackageVisibilityOverride(), + r.canShowBadge(), + r.canBubble(), + r.getNotification().isBubbleNotification(), + r.getChannel(), + r.getGroupKey(), + r.getPeopleOverride(), + r.getSnoozeCriteria(), + r.getUserSentiment(), + r.getSuppressedVisualEffects(), + r.getSystemGeneratedSmartActions(), + r.getSmartReplies(), + r.getImportance(), + r.getRankingScore(), + r.isConversation(), + r.getProposedImportance(), + r.hasSensitiveContent(), + r.getSummarization()); + + Bundle signals = new Bundle(); + signals.putString(Adjustment.KEY_SUMMARIZATION, "SUMMARIZED!"); + Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0); + r.addAdjustment(adjustment); + r.applyAdjustments(); + + assertTrue(extractorData.hasDiffForRankingLocked(r, 1)); + assertTrue(extractorData.hasDiffForLoggingLocked(r, 1)); + } + private NotificationRecord generateRecord() { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); final Notification.Builder builder = new Notification.Builder(getContext()) diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java index 038e1357159b..036e03c60091 100644 --- a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java @@ -23,6 +23,7 @@ import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAV import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE; import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.Presubmit; import android.view.ViewConfiguration; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -38,6 +39,7 @@ import org.junit.runner.RunWith; * Build/Install/Run: * atest WmTests:CombinationKeyTests */ +@Presubmit @MediumTest @RunWith(AndroidJUnit4.class) @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_KEY_GESTURES) diff --git a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java index ca3787ec4546..bccdd67d33ed 100644 --- a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java @@ -19,6 +19,7 @@ package com.android.server.policy; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.platform.test.annotations.Presubmit; import android.view.KeyEvent; import org.junit.Before; @@ -29,6 +30,7 @@ import org.junit.Test; * * <p>Build/Install/Run: atest WmTests:DeferredKeyActionExecutorTests */ +@Presubmit public final class DeferredKeyActionExecutorTests { private DeferredKeyActionExecutor mKeyActionExecutor; diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java index 8b5f68a1e974..a912c177c863 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java @@ -29,6 +29,7 @@ import static org.testng.Assert.assertTrue; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.platform.test.annotations.Presubmit; import android.view.KeyEvent; import androidx.test.filters.SmallTest; @@ -45,7 +46,7 @@ import java.util.concurrent.TimeUnit; * Build/Install/Run: * atest KeyCombinationManagerTests */ - +@Presubmit @SmallTest public class KeyCombinationManagerTests { private KeyCombinationManager mKeyCombinationManager; diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java index 85ef466b2477..16c786b52655 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -544,17 +544,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { } @Test - public void testKeyGestureSplitscreenFocus() { - Assert.assertTrue(sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT)); - mPhoneWindowManager.assertSetSplitscreenFocus(true); - - Assert.assertTrue(sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT)); - mPhoneWindowManager.assertSetSplitscreenFocus(false); - } - - @Test public void testKeyGestureShortcutHelper() { Assert.assertTrue(sendKeyGestureEventComplete( KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER)); diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java index 82a5add407f4..d961a6acc9c1 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java @@ -42,6 +42,7 @@ import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.view.KeyEvent; import android.view.KeyboardShortcutGroup; @@ -64,7 +65,7 @@ import java.util.Collections; * Build/Install/Run: * atest ModifierShortcutManagerTests */ - +@Presubmit @SmallTest @EnableFlags(com.android.hardware.input.Flags.FLAG_MODIFIER_SHORTCUT_MANAGER_REFACTOR) public class ModifierShortcutManagerTests { @@ -127,7 +128,7 @@ public class ModifierShortcutManagerTests { // Total valid shortcuts. KeyboardShortcutGroup group = mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(-1); - assertEquals(13, group.getItems().size()); + assertEquals(11, group.getItems().size()); // Total valid shift shortcuts. assertEquals(3, group.getItems().stream() diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java index af3dc1da4dcc..c73ce23fe6b5 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java @@ -48,6 +48,7 @@ import android.app.AppOpsManager; import android.content.Context; import android.hardware.input.InputManager; import android.os.PowerManager; +import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.filters.SmallTest; @@ -72,6 +73,7 @@ import org.junit.Test; * Build/Install/Run: * atest WmTests:PhoneWindowManagerTests */ +@Presubmit @SmallTest public class PhoneWindowManagerTests { diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java index 33ccec3f785a..53e82bad818d 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java @@ -28,6 +28,7 @@ import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_GO_ import static org.junit.Assert.assertEquals; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.view.Display; @@ -40,6 +41,7 @@ import org.junit.Test; * Build/Install/Run: * atest WmTests:PowerKeyGestureTests */ +@Presubmit public class PowerKeyGestureTests extends ShortcutKeyTestBase { @Before public void setUp() { diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java index 08eb1451bd6f..6c6594220bad 100644 --- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java @@ -39,6 +39,7 @@ import android.os.Process; import android.os.SystemClock; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.view.KeyEvent; @@ -57,6 +58,7 @@ import java.util.concurrent.TimeUnit; * Build/Install/Run: * atest WmTests:SingleKeyGestureTests */ +@Presubmit public class SingleKeyGestureTests { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java index 3ea3235df0f4..833dd5d768e4 100644 --- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java @@ -35,6 +35,7 @@ import android.app.ActivityTaskManager.RootTaskInfo; import android.content.ComponentName; import android.hardware.input.KeyGestureEvent; import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.view.Display; @@ -47,6 +48,7 @@ import org.junit.Test; * Build/Install/Run: * atest WmTests:StemKeyGestureTests */ +@Presubmit public class StemKeyGestureTests extends ShortcutKeyTestBase { private static final String TEST_TARGET_ACTIVITY = "com.android.server.policy/.TestActivity"; diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 4ff3d433632a..f88492477487 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -925,11 +925,6 @@ class TestPhoneWindowManager { verify(mStatusBarManagerInternal).moveFocusedTaskToStageSplit(anyInt(), eq(leftOrTop)); } - void assertSetSplitscreenFocus(boolean leftOrTop) { - mTestLooper.dispatchAll(); - verify(mStatusBarManagerInternal).setSplitscreenFocus(eq(leftOrTop)); - } - void assertStatusBarStartAssist() { mTestLooper.dispatchAll(); verify(mStatusBarManagerInternal).startAssist(any()); diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java index 3ca352cfa60d..9e59bced01f1 100644 --- a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java @@ -57,6 +57,7 @@ import android.content.res.Resources; import android.os.PowerManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.view.Display; @@ -83,6 +84,7 @@ import java.util.function.BooleanSupplier; * * <p>Build/Install/Run: atest WmTests:WindowWakeUpPolicyTests */ +@Presubmit public final class WindowWakeUpPolicyTests { @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); diff --git a/services/usb/OWNERS b/services/usb/OWNERS index 2dff392d4e34..259261252032 100644 --- a/services/usb/OWNERS +++ b/services/usb/OWNERS @@ -1,9 +1,9 @@ -anothermark@google.com +vmartensson@google.com +nkapron@google.com febinthattil@google.com -aprasath@google.com +shubhankarm@google.com badhri@google.com elaurent@google.com albertccwang@google.com jameswei@google.com howardyen@google.com -kumarashishg@google.com
\ No newline at end of file diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index b32379ae4b1e..4d9df4666016 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -415,4 +415,5 @@ interface ITelecomService { */ boolean hasForegroundServiceDelegation(in PhoneAccountHandle phoneAccountHandle, String callingPackage); + void setMetricsTestMode(boolean enabled); } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/RecentTasksUtils.kt index aa262f9dfd6a..1a5fda7c8324 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/RecentTasksUtils.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.utils +package com.android.server.wm.flicker.helpers import android.app.Instrumentation @@ -24,4 +24,4 @@ object RecentTasksUtils { "dumpsys activity service SystemUIService WMShell recents clearAll" ) } -}
\ No newline at end of file +} diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index e0532633d40b..eef4e6f58463 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -467,30 +467,6 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "CTRL + ALT + DPAD_LEFT -> Change Splitscreen Focus Left", - intArrayOf( - KeyEvent.KEYCODE_CTRL_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_LEFT - ), - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT, - intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT), - KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "CTRL + ALT + DPAD_RIGHT -> Change Splitscreen Focus Right", - intArrayOf( - KeyEvent.KEYCODE_CTRL_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_RIGHT - ), - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT, - intArrayOf(KeyEvent.KEYCODE_DPAD_RIGHT), - KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( "META + / -> Open Shortcut Helper", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_SLASH), KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER, |