diff options
143 files changed, 3023 insertions, 945 deletions
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/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 19fdbb7214d0..232883cbfe00 100644 --- a/core/java/android/app/supervision/flags.aconfig +++ b/core/java/android/app/supervision/flags.aconfig @@ -56,3 +56,11 @@ flag { 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/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/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 cebfb381211d..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. * @@ -110,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/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 1bae15cc5077..e35c3b80a58b 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -572,4 +572,14 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +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/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/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/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index c38d459f64d3..59e6c7786cb8 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -188,7 +188,4 @@ <string-array name="desktop_windowing_app_handle_education_allowlist_apps"></string-array> <!-- Apps that can trigger Desktop Windowing App-To-Web Education --> <string-array name="desktop_windowing_app_to_web_education_allowlist_apps"></string-array> - - <!-- Whether this device allows to drag windows across displays. --> - <bool name="config_enableConnectedDisplayWindowDrag">true</bool> </resources> 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/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index ad11252364ce..e69d60ddd6c6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -37,7 +37,6 @@ import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; import com.android.window.flags.Flags; -import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; @@ -92,10 +91,7 @@ public class DisplayController { onDisplayAdded(displayIds[i]); } - final boolean enableConnectedDisplayWindowDrag = - mContext.getResources() - .getBoolean(R.bool.config_enableConnectedDisplayWindowDrag); - if (Flags.enableConnectedDisplaysWindowDrag() && enableConnectedDisplayWindowDrag) { + if (Flags.enableConnectedDisplaysWindowDrag()) { mDisplayManager.registerTopologyListener(mMainExecutor, this::onDisplayTopologyChanged); onDisplayTopologyChanged(mDisplayManager.getDisplayTopology()); 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 fbe06762277b..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 @@ -309,7 +309,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { 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/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt index 33f0432372d0..07496eb0e526 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt @@ -29,7 +29,6 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import com.android.internal.jank.Cuj import com.android.internal.jank.InteractionJankMonitor -import com.android.wm.shell.R import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator @@ -68,8 +67,6 @@ class MultiDisplayVeiledResizeTaskPositioner( (ctrlType and DragPositioningCallback.CTRL_TYPE_LEFT) != 0 || (ctrlType and DragPositioningCallback.CTRL_TYPE_RIGHT) != 0 - private val enableMultiDisplayWindowDrag: Boolean - @DragPositioningCallback.CtrlType private var ctrlType = 0 private var isResizingOrAnimatingResize = false @Surface.Rotation private var rotation = 0 @@ -96,10 +93,6 @@ class MultiDisplayVeiledResizeTaskPositioner( init { dragEventListeners.add(dragEventListener) - enableMultiDisplayWindowDrag = - desktopWindowDecoration.mDecorWindowContext.resources.getBoolean( - R.bool.config_enableConnectedDisplayWindowDrag - ) } override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect { @@ -175,13 +168,8 @@ class MultiDisplayVeiledResizeTaskPositioner( val startDisplayLayout = displayController.getDisplayLayout(startDisplayId) val currentDisplayLayout = displayController.getDisplayLayout(displayId) - if ( - startDisplayLayout == null || - currentDisplayLayout == null || - !enableMultiDisplayWindowDrag - ) { - // Fall back to single-display drag behavior if any display layout is unavailable or - // it's explicitly disabled. + if (startDisplayLayout == null || currentDisplayLayout == null) { + // Fall back to single-display drag behavior if any display layout is unavailable. DragPositioningCallbackUtility.setPositionOnDrag( desktopWindowDecoration, repositionTaskBounds, @@ -249,13 +237,8 @@ class MultiDisplayVeiledResizeTaskPositioner( val startDisplayLayout = displayController.getDisplayLayout(startDisplayId) val currentDisplayLayout = displayController.getDisplayLayout(displayId) - if ( - startDisplayLayout == null || - currentDisplayLayout == null || - !enableMultiDisplayWindowDrag - ) { - // Fall back to single-display drag behavior if any display layout is unavailable or - // it's explicitly disabled. + if (startDisplayLayout == null || currentDisplayLayout == null) { + // Fall back to single-display drag behavior if any display layout is unavailable. DragPositioningCallbackUtility.updateTaskBounds( repositionTaskBounds, taskBoundsAtDragStart, 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/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/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/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/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index dbefcd7cf45f..7cf83135c237 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1942,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." @@ -1990,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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 4121e8d3786a..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 @@ -72,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; @@ -290,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); @@ -1255,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/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/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/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/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/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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 1b1c99cf1410..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[] { @@ -10432,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); } @@ -11752,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/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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 8a3ee61d5d80..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; @@ -805,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() { @@ -821,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) { @@ -996,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); + }); } } @@ -3990,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) { @@ -4012,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; 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/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index f583d4e67cc2..793ba7bc89bd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -728,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); @@ -768,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); @@ -800,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 @@ -819,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); @@ -827,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)); } @@ -857,25 +891,57 @@ 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. @@ -971,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/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/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/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/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, |