diff options
292 files changed, 5130 insertions, 3969 deletions
diff --git a/cmds/bootanimation/OWNERS b/cmds/bootanimation/OWNERS index b6fb007bea52..2eda44dabec3 100644 --- a/cmds/bootanimation/OWNERS +++ b/cmds/bootanimation/OWNERS @@ -1,3 +1,4 @@ dupin@google.com shanh@google.com jreck@google.com +rahulbanerjee@google.com
\ No newline at end of file diff --git a/core/api/current.txt b/core/api/current.txt index db302f43dfe0..6f60378d73e3 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32880,6 +32880,41 @@ package android.os { } @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static class Build.VERSION_CODES_FULL { + field public static final int BASE = 100000; // 0x186a0 + field public static final int BASE_1_1 = 200000; // 0x30d40 + field public static final int CUPCAKE = 300000; // 0x493e0 + field public static final int DONUT = 400000; // 0x61a80 + field public static final int ECLAIR = 500000; // 0x7a120 + field public static final int ECLAIR_0_1 = 600000; // 0x927c0 + field public static final int ECLAIR_MR1 = 700000; // 0xaae60 + field public static final int FROYO = 800000; // 0xc3500 + field public static final int GINGERBREAD = 900000; // 0xdbba0 + field public static final int GINGERBREAD_MR1 = 1000000; // 0xf4240 + field public static final int HONEYCOMB = 1100000; // 0x10c8e0 + field public static final int HONEYCOMB_MR1 = 1200000; // 0x124f80 + field public static final int HONEYCOMB_MR2 = 1300000; // 0x13d620 + field public static final int ICE_CREAM_SANDWICH = 1400000; // 0x155cc0 + field public static final int ICE_CREAM_SANDWICH_MR1 = 1500000; // 0x16e360 + field public static final int JELLY_BEAN = 1600000; // 0x186a00 + field public static final int JELLY_BEAN_MR1 = 1700000; // 0x19f0a0 + field public static final int JELLY_BEAN_MR2 = 1800000; // 0x1b7740 + field public static final int KITKAT = 1900000; // 0x1cfde0 + field public static final int KITKAT_WATCH = 2000000; // 0x1e8480 + field public static final int LOLLIPOP = 2100000; // 0x200b20 + field public static final int LOLLIPOP_MR1 = 2200000; // 0x2191c0 + field public static final int M = 2300000; // 0x231860 + field public static final int N = 2400000; // 0x249f00 + field public static final int N_MR1 = 2500000; // 0x2625a0 + field public static final int O = 2600000; // 0x27ac40 + field public static final int O_MR1 = 2700000; // 0x2932e0 + field public static final int P = 2800000; // 0x2ab980 + field public static final int Q = 2900000; // 0x2c4020 + field public static final int R = 3000000; // 0x2dc6c0 + field public static final int S = 3100000; // 0x2f4d60 + field public static final int S_V2 = 3200000; // 0x30d400 + field public static final int TIRAMISU = 3300000; // 0x325aa0 + field public static final int UPSIDE_DOWN_CAKE = 3400000; // 0x33e140 + field public static final int VANILLA_ICE_CREAM = 3500000; // 0x3567e0 } public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable { @@ -33933,12 +33968,9 @@ package android.os { public class RemoteCallbackList<E extends android.os.IInterface> { ctor public RemoteCallbackList(); method public int beginBroadcast(); - method @FlaggedApi("android.os.binder_frozen_state_change_callback") public void broadcast(@NonNull java.util.function.Consumer<E>); method public void finishBroadcast(); method public Object getBroadcastCookie(int); method public E getBroadcastItem(int); - method @FlaggedApi("android.os.binder_frozen_state_change_callback") public int getFrozenCalleePolicy(); - method @FlaggedApi("android.os.binder_frozen_state_change_callback") public int getMaxQueueSize(); method public Object getRegisteredCallbackCookie(int); method public int getRegisteredCallbackCount(); method public E getRegisteredCallbackItem(int); @@ -33948,16 +33980,6 @@ package android.os { method public boolean register(E); method public boolean register(E, Object); method public boolean unregister(E); - field @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final int FROZEN_CALLEE_POLICY_DROP = 3; // 0x3 - field @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final int FROZEN_CALLEE_POLICY_ENQUEUE_ALL = 1; // 0x1 - field @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final int FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT = 2; // 0x2 - field @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final int FROZEN_CALLEE_POLICY_UNSET = 0; // 0x0 - } - - @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final class RemoteCallbackList.Builder<E extends android.os.IInterface> { - ctor public RemoteCallbackList.Builder(int); - method @NonNull public android.os.RemoteCallbackList<E> build(); - method @NonNull public android.os.RemoteCallbackList.Builder setMaxQueueSize(int); } public class RemoteException extends android.util.AndroidException { @@ -43851,8 +43873,8 @@ package android.telephony { field public static final String KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrq_thresholds_int_array"; field public static final String KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY = "5g_nr_sssinr_thresholds_int_array"; field public static final String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; - field @FlaggedApi("com.android.internal.telephony.flags.show_call_id_and_call_waiting_in_additional_settings_menu") public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL = "additional_settings_caller_id_visibility_bool"; - field @FlaggedApi("com.android.internal.telephony.flags.show_call_id_and_call_waiting_in_additional_settings_menu") public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL = "additional_settings_call_waiting_visibility_bool"; + field public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL = "additional_settings_caller_id_visibility_bool"; + field public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL = "additional_settings_call_waiting_visibility_bool"; field public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call"; field public static final String KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool"; @@ -43935,7 +43957,7 @@ package android.telephony { field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array"; field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int"; field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array"; - field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY = "cellular_service_capabilities_int_array"; + field public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY = "cellular_service_capabilities_int_array"; field public static final String KEY_CELLULAR_USAGE_SETTING_INT = "cellular_usage_setting_int"; field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool"; field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool"; @@ -45973,7 +45995,7 @@ package android.telephony { method @Nullable public String getMncString(); method @Deprecated public String getNumber(); method public int getPortIndex(); - method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") @NonNull public java.util.Set<java.lang.Integer> getServiceCapabilities(); + method @NonNull public java.util.Set<java.lang.Integer> getServiceCapabilities(); method public int getSimSlotIndex(); method public int getSubscriptionId(); method public int getSubscriptionType(); @@ -46056,9 +46078,9 @@ package android.telephony { field public static final int PHONE_NUMBER_SOURCE_CARRIER = 2; // 0x2 field public static final int PHONE_NUMBER_SOURCE_IMS = 3; // 0x3 field public static final int PHONE_NUMBER_SOURCE_UICC = 1; // 0x1 - field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_DATA = 3; // 0x3 - field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_SMS = 2; // 0x2 - field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_VOICE = 1; // 0x1 + field public static final int SERVICE_CAPABILITY_DATA = 3; // 0x3 + field public static final int SERVICE_CAPABILITY_SMS = 2; // 0x2 + field public static final int SERVICE_CAPABILITY_VOICE = 1; // 0x1 field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0 field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1 field public static final int USAGE_SETTING_DATA_CENTRIC = 2; // 0x2 @@ -46307,8 +46329,8 @@ package android.telephony { method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabled(); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabledForReason(int); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataRoamingEnabled(); - method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public boolean isDeviceSmsCapable(); - method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public boolean isDeviceVoiceCapable(); + method public boolean isDeviceSmsCapable(); + method public boolean isDeviceVoiceCapable(); method public boolean isEmergencyNumber(@NonNull String); method public boolean isHearingAidCompatibilitySupported(); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isManualNetworkSelectionAllowed(); @@ -46367,7 +46389,7 @@ package android.telephony { field public static final String ACTION_MULTI_SIM_CONFIG_CHANGED = "android.telephony.action.MULTI_SIM_CONFIG_CHANGED"; field public static final String ACTION_NETWORK_COUNTRY_CHANGED = "android.telephony.action.NETWORK_COUNTRY_CHANGED"; field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE"; - field @FlaggedApi("com.android.internal.telephony.flags.reset_mobile_network_settings") public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS = "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS"; + field public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS = "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS"; field public static final String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE"; field public static final String ACTION_SECRET_CODE = "android.telephony.action.SECRET_CODE"; field public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION"; @@ -50947,6 +50969,7 @@ package android.view { method public android.view.Display.Mode[] getSupportedModes(); method @Deprecated public float[] getSupportedRefreshRates(); method @Deprecated public int getWidth(); + method @FlaggedApi("com.android.server.display.feature.flags.enable_has_arr_support") public boolean hasArrSupport(); method public boolean isHdr(); method public boolean isHdrSdrRatioAvailable(); method public boolean isMinimalPostProcessingSupported(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index cab836e147c4..fa4fc43c3418 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -7319,6 +7319,7 @@ package android.media { field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8 field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_CLIENT_VOLUME = 16; // 0x10 field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1 + field @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_PORT_VOLUME = 64; // 0x40 field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4 field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2 field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_VOLUME_SHAPER = 32; // 0x20 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index c79fc510c25a..1ff8c510b6bf 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1101,6 +1101,7 @@ package android.content.pm { field public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT = 265452344L; // 0xfd27b38L field public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L; // 0xf4156bcL field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2 + field public static final long UNIVERSAL_RESIZABLE_BY_DEFAULT = 357141415L; // 0x15498ba7L } public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8b33417e0a79..4ef5b5163fef 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -11296,7 +11296,11 @@ public class Notification implements Parcelable * @see Segment */ public @NonNull ProgressStyle setProgressSegments(@NonNull List<Segment> progressSegments) { - mProgressSegments = new ArrayList<>(progressSegments.size()); + if (mProgressSegments == null) { + mProgressSegments = new ArrayList<>(); + } + mProgressSegments.clear(); + mProgressSegments.addAll(progressSegments); return this; } diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java index 08ecced234a9..06d95f5270c3 100644 --- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java +++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java @@ -137,8 +137,10 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { .TOKENIZER_TYPE_VERBATIM) .build()) .addProperty( - new AppSearchSchema.BooleanPropertyConfig.Builder(PROPERTY_ENABLED) + new AppSearchSchema.LongPropertyConfig.Builder(PROPERTY_ENABLED) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setIndexingType( + AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_RANGE) .build()) .addProperty( new AppSearchSchema.StringPropertyConfig.Builder( @@ -212,19 +214,14 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { } /** - * Sets an indicator specifying if the function is enabled or not. This would override the - * default enabled state in the static metadata ({@link - * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to null - * to clear the override. - * TODO(369683073) Replace the tristate Boolean with IntDef EnabledState. + * Sets an indicator specifying the function enabled state. */ @NonNull public Builder setEnabled(@EnabledState int enabledState) { if (enabledState != APP_FUNCTION_STATE_DEFAULT && enabledState != APP_FUNCTION_STATE_ENABLED && enabledState != APP_FUNCTION_STATE_DISABLED) { - throw new IllegalArgumentException( - "Value of EnabledState is unsupported."); + throw new IllegalArgumentException("Value of EnabledState is unsupported."); } setPropertyLong(PROPERTY_ENABLED, enabledState); return this; diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java index cfbe7416bf9d..3438cc861661 100644 --- a/core/java/android/app/contextualsearch/ContextualSearchManager.java +++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java @@ -102,6 +102,7 @@ public final class ContextualSearchManager { * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. */ public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN"; + /** * Intent action for contextual search invocation. The app providing the contextual search * experience must add this intent filter action to the activity it wants to be launched. @@ -111,6 +112,14 @@ public final class ContextualSearchManager { public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH"; + /** + * System feature declaring that the device supports Contextual Search. + * + * @hide + */ + public static final String FEATURE_CONTEXTUAL_SEARCH = + "com.google.android.feature.CONTEXTUAL_SEARCH"; + /** Entrypoint to be used when a user long presses on the nav handle. */ public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1; /** Entrypoint to be used when a user long presses on the home button. */ diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 11e885055162..37fa9a26b91c 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -6,6 +6,14 @@ container: "system" # flag relates to. flag { + name: "notifications_redesign_app_icons" + namespace: "systemui" + description: "Notifications Redesign: Use app icons in notification rows (not to be confused with" + " notifications_use_app_icons, notifications_use_app_icon_in_row which are just experiments)." + bug: "371174789" +} + +flag { name: "modes_api" is_exported: true namespace: "systemui" diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 481e6b530162..ce52825ddb73 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -21,10 +21,12 @@ import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.Activity; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.EnabledSince; import android.compat.annotation.Overridable; import android.compat.annotation.UnsupportedAppUsage; @@ -1204,6 +1206,18 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public @interface SizeChangesSupportMode {} /** + * This change id makes the restriction of fixed orientation, aspect ratio, and resizability + * of the app to be ignored, which means making the app fill the given available area. + * @hide + */ + @ChangeId + @Overridable + @TestApi + @SuppressLint("UnflaggedApi") // @TestApi without associated public API. + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + public static final long UNIVERSAL_RESIZABLE_BY_DEFAULT = 357141415L; // buganizer id + + /** * This change id enables compat policy that ignores app requested orientation in * response to an app calling {@link android.app.Activity#setRequestedOrientation}. See * com.android.server.wm.LetterboxUiController#shouldIgnoreRequestedOrientation for diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig index dcb363ccf535..efddd1f9fc6f 100644 --- a/core/java/android/net/vcn/flags.aconfig +++ b/core/java/android/net/vcn/flags.aconfig @@ -21,38 +21,4 @@ flag{ namespace: "vcn" description: "Feature flag for enabling network metric monitor" bug: "282996138" -} - -flag{ - name: "validate_network_on_ipsec_loss" - namespace: "vcn" - description: "Trigger network validation when IPsec packet loss exceeds the threshold" - bug: "329139898" -} - -flag{ - name: "evaluate_ipsec_loss_on_lp_nc_change" - namespace: "vcn" - description: "Re-evaluate IPsec packet loss on LinkProperties or NetworkCapabilities change" - bug: "323238888" -} - -flag{ - name: "enforce_main_user" - namespace: "vcn" - description: "Enforce main user to make VCN HSUM compatible" - bug: "310310661" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag{ - name: "handle_seq_num_leap" - namespace: "vcn" - description: "Do not report bad network when there is a suspected sequence number leap" - bug: "332598276" - metadata { - purpose: PURPOSE_BUGFIX - } }
\ No newline at end of file diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index d3ba73ed1421..a89483394611 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -1278,6 +1278,41 @@ public class Build { /** @hide */ @IntDef(value = { + VERSION_CODES_FULL.BASE, + VERSION_CODES_FULL.BASE_1_1, + VERSION_CODES_FULL.CUPCAKE, + VERSION_CODES_FULL.DONUT, + VERSION_CODES_FULL.ECLAIR, + VERSION_CODES_FULL.ECLAIR_0_1, + VERSION_CODES_FULL.ECLAIR_MR1, + VERSION_CODES_FULL.FROYO, + VERSION_CODES_FULL.GINGERBREAD, + VERSION_CODES_FULL.GINGERBREAD_MR1, + VERSION_CODES_FULL.HONEYCOMB, + VERSION_CODES_FULL.HONEYCOMB_MR1, + VERSION_CODES_FULL.HONEYCOMB_MR2, + VERSION_CODES_FULL.ICE_CREAM_SANDWICH, + VERSION_CODES_FULL.ICE_CREAM_SANDWICH_MR1, + VERSION_CODES_FULL.JELLY_BEAN, + VERSION_CODES_FULL.JELLY_BEAN_MR1, + VERSION_CODES_FULL.JELLY_BEAN_MR2, + VERSION_CODES_FULL.KITKAT, + VERSION_CODES_FULL.KITKAT_WATCH, + VERSION_CODES_FULL.LOLLIPOP, + VERSION_CODES_FULL.LOLLIPOP_MR1, + VERSION_CODES_FULL.M, + VERSION_CODES_FULL.N, + VERSION_CODES_FULL.N_MR1, + VERSION_CODES_FULL.O, + VERSION_CODES_FULL.O_MR1, + VERSION_CODES_FULL.P, + VERSION_CODES_FULL.Q, + VERSION_CODES_FULL.R, + VERSION_CODES_FULL.S, + VERSION_CODES_FULL.S_V2, + VERSION_CODES_FULL.TIRAMISU, + VERSION_CODES_FULL.UPSIDE_DOWN_CAKE, + VERSION_CODES_FULL.VANILLA_ICE_CREAM, }) @Retention(RetentionPolicy.SOURCE) public @interface SdkIntFull {} @@ -1299,6 +1334,186 @@ public class Build { // Use the last 5 digits for the minor version. This allows the // minor version to be set to CUR_DEVELOPMENT. private static final int SDK_INT_MULTIPLIER = 100000; + + /** + * Android 1.0. + */ + public static final int BASE = VERSION_CODES.BASE * SDK_INT_MULTIPLIER; + + /** + * Android 2.0. + */ + public static final int BASE_1_1 = VERSION_CODES.BASE_1_1 * SDK_INT_MULTIPLIER; + + /** + * Android 3.0. + */ + public static final int CUPCAKE = VERSION_CODES.CUPCAKE * SDK_INT_MULTIPLIER; + + /** + * Android 4.0. + */ + public static final int DONUT = VERSION_CODES.DONUT * SDK_INT_MULTIPLIER; + + /** + * Android 5.0. + */ + public static final int ECLAIR = VERSION_CODES.ECLAIR * SDK_INT_MULTIPLIER; + + /** + * Android 6.0. + */ + public static final int ECLAIR_0_1 = VERSION_CODES.ECLAIR_0_1 * SDK_INT_MULTIPLIER; + + /** + * Android 7.0. + */ + public static final int ECLAIR_MR1 = VERSION_CODES.ECLAIR_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 8.0. + */ + public static final int FROYO = VERSION_CODES.FROYO * SDK_INT_MULTIPLIER; + + /** + * Android 9.0. + */ + public static final int GINGERBREAD = VERSION_CODES.GINGERBREAD * SDK_INT_MULTIPLIER; + + /** + * Android 10.0. + */ + public static final int GINGERBREAD_MR1 = + VERSION_CODES.GINGERBREAD_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 11.0. + */ + public static final int HONEYCOMB = VERSION_CODES.HONEYCOMB * SDK_INT_MULTIPLIER; + + /** + * Android 12.0. + */ + public static final int HONEYCOMB_MR1 = VERSION_CODES.HONEYCOMB_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 13.0. + */ + public static final int HONEYCOMB_MR2 = VERSION_CODES.HONEYCOMB_MR2 * SDK_INT_MULTIPLIER; + + /** + * Android 14.0. + */ + public static final int ICE_CREAM_SANDWICH = + VERSION_CODES.ICE_CREAM_SANDWICH * SDK_INT_MULTIPLIER; + + /** + * Android 15.0. + */ + public static final int ICE_CREAM_SANDWICH_MR1 = + VERSION_CODES.ICE_CREAM_SANDWICH_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 16.0. + */ + public static final int JELLY_BEAN = VERSION_CODES.JELLY_BEAN * SDK_INT_MULTIPLIER; + + /** + * Android 17.0. + */ + public static final int JELLY_BEAN_MR1 = VERSION_CODES.JELLY_BEAN_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 18.0. + */ + public static final int JELLY_BEAN_MR2 = VERSION_CODES.JELLY_BEAN_MR2 * SDK_INT_MULTIPLIER; + + /** + * Android 19.0. + */ + public static final int KITKAT = VERSION_CODES.KITKAT * SDK_INT_MULTIPLIER; + + /** + * Android 20.0. + */ + public static final int KITKAT_WATCH = VERSION_CODES.KITKAT_WATCH * SDK_INT_MULTIPLIER; + + /** + * Android 21.0. + */ + public static final int LOLLIPOP = VERSION_CODES.LOLLIPOP * SDK_INT_MULTIPLIER; + + /** + * Android 22.0. + */ + public static final int LOLLIPOP_MR1 = VERSION_CODES.LOLLIPOP_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 23.0. + */ + public static final int M = VERSION_CODES.M * SDK_INT_MULTIPLIER; + + /** + * Android 24.0. + */ + public static final int N = VERSION_CODES.N * SDK_INT_MULTIPLIER; + + /** + * Android 25.0. + */ + public static final int N_MR1 = VERSION_CODES.N_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 26.0. + */ + public static final int O = VERSION_CODES.O * SDK_INT_MULTIPLIER; + + /** + * Android 27.0. + */ + public static final int O_MR1 = VERSION_CODES.O_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 28.0. + */ + public static final int P = VERSION_CODES.P * SDK_INT_MULTIPLIER; + + /** + * Android 29.0. + */ + public static final int Q = VERSION_CODES.Q * SDK_INT_MULTIPLIER; + + /** + * Android 30.0. + */ + public static final int R = VERSION_CODES.R * SDK_INT_MULTIPLIER; + + /** + * Android 31.0. + */ + public static final int S = VERSION_CODES.S * SDK_INT_MULTIPLIER; + + /** + * Android 32.0. + */ + public static final int S_V2 = VERSION_CODES.S_V2 * SDK_INT_MULTIPLIER; + + /** + * Android 33.0. + */ + public static final int TIRAMISU = VERSION_CODES.TIRAMISU * SDK_INT_MULTIPLIER; + + /** + * Android 34.0. + */ + public static final int UPSIDE_DOWN_CAKE = + VERSION_CODES.UPSIDE_DOWN_CAKE * SDK_INT_MULTIPLIER; + + /** + * Android 35.0. + */ + public static final int VANILLA_ICE_CREAM = + VERSION_CODES.VANILLA_ICE_CREAM * SDK_INT_MULTIPLIER; } /** diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index f82c8221e4f9..769cbdd9886d 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -16,18 +16,11 @@ package android.os; -import android.annotation.FlaggedApi; -import android.annotation.IntDef; -import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.util.ArrayMap; import android.util.Slog; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -37,7 +30,7 @@ import java.util.function.Consumer; * {@link android.app.Service} to its clients. In particular, this: * * <ul> - * <li> Keeps track of a set of registered {@link IInterface} objects, + * <li> Keeps track of a set of registered {@link IInterface} callbacks, * taking care to identify them through their underlying unique {@link IBinder} * (by calling {@link IInterface#asBinder IInterface.asBinder()}. * <li> Attaches a {@link IBinder.DeathRecipient IBinder.DeathRecipient} to @@ -54,7 +47,7 @@ import java.util.function.Consumer; * the registered clients, use {@link #beginBroadcast}, * {@link #getBroadcastItem}, and {@link #finishBroadcast}. * - * <p>If a registered interface's process goes away, this class will take + * <p>If a registered callback's process goes away, this class will take * care of automatically removing it from the list. If you want to do * additional work in this situation, you can create a subclass that * implements the {@link #onCallbackDied} method. @@ -63,310 +56,78 @@ import java.util.function.Consumer; public class RemoteCallbackList<E extends IInterface> { private static final String TAG = "RemoteCallbackList"; - private static final int DEFAULT_MAX_QUEUE_SIZE = 1000; - - - /** - * @hide - */ - @IntDef(prefix = {"FROZEN_CALLEE_POLICY_"}, value = { - FROZEN_CALLEE_POLICY_UNSET, - FROZEN_CALLEE_POLICY_ENQUEUE_ALL, - FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT, - FROZEN_CALLEE_POLICY_DROP, - }) - @Retention(RetentionPolicy.SOURCE) - @interface FrozenCalleePolicy { - } - - /** - * Callbacks are invoked immediately regardless of the frozen state of the target process. - * - * Not recommended. Only exists for backward-compatibility. This represents the behavior up to - * SDK 35. Starting with SDK 36, clients should set a policy to govern callback invocations when - * recipients are frozen. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public static final int FROZEN_CALLEE_POLICY_UNSET = 0; - - /** - * When the callback recipient's process is frozen, callbacks are enqueued so they're invoked - * after the recipient is unfrozen. - * - * This is commonly used when the recipient wants to receive all callbacks without losing any - * history, e.g. the recipient maintains a running count of events that occurred. - * - * Queued callbacks are invoked in the order they were originally broadcasted. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public static final int FROZEN_CALLEE_POLICY_ENQUEUE_ALL = 1; - - /** - * When the callback recipient's process is frozen, only the most recent callback is enqueued, - * which is later invoked after the recipient is unfrozen. - * - * This can be used when only the most recent state matters, for instance when clients are - * listening to screen brightness changes. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public static final int FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT = 2; - - /** - * When the callback recipient's process is frozen, callbacks are suppressed as if they never - * happened. - * - * This could be useful in the case where the recipient wishes to react to callbacks only when - * they occur while the recipient is not frozen. For example, certain network events are only - * worth responding to if the response can be immediate. Another example is recipients having - * another way of getting the latest state once it's unfrozen. Therefore there is no need to - * save callbacks that happened while the recipient was frozen. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public static final int FROZEN_CALLEE_POLICY_DROP = 3; - @UnsupportedAppUsage - /*package*/ ArrayMap<IBinder, Interface> mInterfaces = new ArrayMap<IBinder, Interface>(); + /*package*/ ArrayMap<IBinder, Callback> mCallbacks + = new ArrayMap<IBinder, Callback>(); private Object[] mActiveBroadcast; private int mBroadcastCount = -1; private boolean mKilled = false; private StringBuilder mRecentCallers; - private final @FrozenCalleePolicy int mFrozenCalleePolicy; - private final int mMaxQueueSize; - - private final class Interface implements IBinder.DeathRecipient, - IBinder.FrozenStateChangeCallback { - final IBinder mBinder; - final E mInterface; + private final class Callback implements IBinder.DeathRecipient { + final E mCallback; final Object mCookie; - final Queue<Consumer<E>> mCallbackQueue; - int mCurrentState = IBinder.FrozenStateChangeCallback.STATE_UNFROZEN; - Interface(E callbackInterface, Object cookie) { - mBinder = callbackInterface.asBinder(); - mInterface = callbackInterface; + Callback(E callback, Object cookie) { + mCallback = callback; mCookie = cookie; - mCallbackQueue = mFrozenCalleePolicy == FROZEN_CALLEE_POLICY_ENQUEUE_ALL - || mFrozenCalleePolicy == FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT - ? new ConcurrentLinkedQueue<>() : null; - } - - @Override - public synchronized void onFrozenStateChanged(@NonNull IBinder who, int state) { - if (state == STATE_UNFROZEN && mCallbackQueue != null) { - while (!mCallbackQueue.isEmpty()) { - Consumer<E> callback = mCallbackQueue.poll(); - callback.accept(mInterface); - } - } - mCurrentState = state; - } - - void addCallback(@NonNull Consumer<E> callback) { - if (mFrozenCalleePolicy == FROZEN_CALLEE_POLICY_UNSET) { - callback.accept(mInterface); - return; - } - synchronized (this) { - if (mCurrentState == STATE_UNFROZEN) { - callback.accept(mInterface); - return; - } - switch (mFrozenCalleePolicy) { - case FROZEN_CALLEE_POLICY_ENQUEUE_ALL: - if (mCallbackQueue.size() >= mMaxQueueSize) { - mCallbackQueue.poll(); - } - mCallbackQueue.offer(callback); - break; - case FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: - mCallbackQueue.clear(); - mCallbackQueue.offer(callback); - break; - case FROZEN_CALLEE_POLICY_DROP: - // Do nothing. Just ignore the callback. - break; - case FROZEN_CALLEE_POLICY_UNSET: - // Do nothing. Should have returned at the start of the method. - break; - } - } - } - - public void maybeSubscribeToFrozenCallback() throws RemoteException { - if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) { - mBinder.addFrozenStateChangeCallback(this); - } - } - - public void maybeUnsubscribeFromFrozenCallback() { - if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) { - mBinder.removeFrozenStateChangeCallback(this); - } } public void binderDied() { - synchronized (mInterfaces) { - mInterfaces.remove(mBinder); - maybeUnsubscribeFromFrozenCallback(); - } - onCallbackDied(mInterface, mCookie); - } - } - - /** - * Builder for {@link RemoteCallbackList}. - * - * @param <E> The remote callback interface type. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public static final class Builder<E extends IInterface> { - private @FrozenCalleePolicy int mFrozenCalleePolicy; - private int mMaxQueueSize = DEFAULT_MAX_QUEUE_SIZE; - - /** - * Creates a Builder for {@link RemoteCallbackList}. - * - * @param frozenCalleePolicy When the callback recipient's process is frozen, this parameter - * specifies when/whether callbacks are invoked. It's important to choose a strategy that's - * right for the use case. Leaving the policy unset with {@link #FROZEN_CALLEE_POLICY_UNSET} - * is not recommended as it allows callbacks to be invoked while the recipient is frozen. - */ - public Builder(@FrozenCalleePolicy int frozenCalleePolicy) { - mFrozenCalleePolicy = frozenCalleePolicy; - } - - /** - * Sets the max queue size. - * - * @param maxQueueSize The max size limit on the queue that stores callbacks added when the - * recipient's process is frozen. Once the limit is reached, the oldest callback is dropped - * to keep the size under the limit. Should only be called for - * {@link #FROZEN_CALLEE_POLICY_ENQUEUE_ALL}. - * - * @return This builder. - * @throws IllegalArgumentException if the maxQueueSize is not positive. - * @throws UnsupportedOperationException if frozenCalleePolicy is not - * {@link #FROZEN_CALLEE_POLICY_ENQUEUE_ALL}. - */ - public @NonNull Builder setMaxQueueSize(int maxQueueSize) { - if (maxQueueSize <= 0) { - throw new IllegalArgumentException("maxQueueSize must be positive"); - } - if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_ENQUEUE_ALL) { - throw new UnsupportedOperationException( - "setMaxQueueSize can only be called for FROZEN_CALLEE_POLICY_ENQUEUE_ALL"); + synchronized (mCallbacks) { + mCallbacks.remove(mCallback.asBinder()); } - mMaxQueueSize = maxQueueSize; - return this; - } - - /** - * Builds and returns a {@link RemoteCallbackList}. - * - * @return The built {@link RemoteCallbackList} object. - */ - public @NonNull RemoteCallbackList<E> build() { - return new RemoteCallbackList<E>(mFrozenCalleePolicy, mMaxQueueSize); + onCallbackDied(mCallback, mCookie); } } /** - * Returns the frozen callee policy. - * - * @return The frozen callee policy. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public @FrozenCalleePolicy int getFrozenCalleePolicy() { - return mFrozenCalleePolicy; - } - - /** - * Returns the max queue size. - * - * @return The max queue size. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public int getMaxQueueSize() { - return mMaxQueueSize; - } - - /** - * Creates a RemoteCallbackList with {@link #FROZEN_CALLEE_POLICY_UNSET}. This is equivalent to - * <pre> - * new RemoteCallbackList.Build(RemoteCallbackList.FROZEN_CALLEE_POLICY_UNSET).build() - * </pre> - */ - public RemoteCallbackList() { - this(FROZEN_CALLEE_POLICY_UNSET, DEFAULT_MAX_QUEUE_SIZE); - } - - /** - * Creates a RemoteCallbackList with the specified frozen callee policy. - * - * @param frozenCalleePolicy When the callback recipient's process is frozen, this parameter - * specifies when/whether callbacks are invoked. It's important to choose a strategy that's - * right for the use case. Leaving the policy unset with {@link #FROZEN_CALLEE_POLICY_UNSET} - * is not recommended as it allows callbacks to be invoked while the recipient is frozen. - * - * @param maxQueueSize The max size limit on the queue that stores callbacks added when the - * recipient's process is frozen. Once the limit is reached, the oldest callbacks would be - * dropped to keep the size under limit. Ignored except for - * {@link #FROZEN_CALLEE_POLICY_ENQUEUE_ALL}. - */ - private RemoteCallbackList(@FrozenCalleePolicy int frozenCalleePolicy, int maxQueueSize) { - mFrozenCalleePolicy = frozenCalleePolicy; - mMaxQueueSize = maxQueueSize; - } - - /** * Simple version of {@link RemoteCallbackList#register(E, Object)} * that does not take a cookie object. */ - public boolean register(E callbackInterface) { - return register(callbackInterface, null); + public boolean register(E callback) { + return register(callback, null); } /** - * Add a new interface to the list. This interface will remain in the list + * Add a new callback to the list. This callback will remain in the list * until a corresponding call to {@link #unregister} or its hosting process - * goes away. If the interface was already registered (determined by - * checking to see if the {@link IInterface#asBinder callbackInterface.asBinder()} - * object is already in the list), then it will be replaced with the new interface. + * goes away. If the callback was already registered (determined by + * checking to see if the {@link IInterface#asBinder callback.asBinder()} + * object is already in the list), then it will be replaced with the new callback. * Registrations are not counted; a single call to {@link #unregister} - * will remove an interface after any number calls to register it. + * will remove a callback after any number calls to register it. * - * @param callbackInterface The callback interface to be added to the list. Must + * @param callback The callback interface to be added to the list. Must * not be null -- passing null here will cause a NullPointerException. * Most services will want to check for null before calling this with * an object given from a client, so that clients can't crash the * service with bad data. * * @param cookie Optional additional data to be associated with this - * interface. + * callback. * - * @return Returns true if the interface was successfully added to the list. + * @return Returns true if the callback was successfully added to the list. * Returns false if it was not added, either because {@link #kill} had - * previously been called or the interface's process has gone away. + * previously been called or the callback's process has gone away. * * @see #unregister * @see #kill * @see #onCallbackDied */ - public boolean register(E callbackInterface, Object cookie) { - synchronized (mInterfaces) { + public boolean register(E callback, Object cookie) { + synchronized (mCallbacks) { if (mKilled) { return false; } // Flag unusual case that could be caused by a leak. b/36778087 - logExcessiveInterfaces(); - IBinder binder = callbackInterface.asBinder(); + logExcessiveCallbacks(); + IBinder binder = callback.asBinder(); try { - Interface i = new Interface(callbackInterface, cookie); - unregister(callbackInterface); - binder.linkToDeath(i, 0); - i.maybeSubscribeToFrozenCallback(); - mInterfaces.put(binder, i); + Callback cb = new Callback(callback, cookie); + unregister(callback); + binder.linkToDeath(cb, 0); + mCallbacks.put(binder, cb); return true; } catch (RemoteException e) { return false; @@ -375,28 +136,27 @@ public class RemoteCallbackList<E extends IInterface> { } /** - * Remove from the list an interface that was previously added with + * Remove from the list a callback that was previously added with * {@link #register}. This uses the - * {@link IInterface#asBinder callbackInterface.asBinder()} object to correctly + * {@link IInterface#asBinder callback.asBinder()} object to correctly * find the previous registration. * Registrations are not counted; a single unregister call will remove - * an interface after any number calls to {@link #register} for it. + * a callback after any number calls to {@link #register} for it. * - * @param callbackInterface The interface to be removed from the list. Passing + * @param callback The callback to be removed from the list. Passing * null here will cause a NullPointerException, so you will generally want * to check for null before calling. * - * @return Returns true if the interface was found and unregistered. Returns - * false if the given interface was not found on the list. + * @return Returns true if the callback was found and unregistered. Returns + * false if the given callback was not found on the list. * * @see #register */ - public boolean unregister(E callbackInterface) { - synchronized (mInterfaces) { - Interface i = mInterfaces.remove(callbackInterface.asBinder()); - if (i != null) { - i.mInterface.asBinder().unlinkToDeath(i, 0); - i.maybeUnsubscribeFromFrozenCallback(); + public boolean unregister(E callback) { + synchronized (mCallbacks) { + Callback cb = mCallbacks.remove(callback.asBinder()); + if (cb != null) { + cb.mCallback.asBinder().unlinkToDeath(cb, 0); return true; } return false; @@ -404,21 +164,20 @@ public class RemoteCallbackList<E extends IInterface> { } /** - * Disable this interface list. All registered interfaces are unregistered, + * Disable this callback list. All registered callbacks are unregistered, * and the list is disabled so that future calls to {@link #register} will * fail. This should be used when a Service is stopping, to prevent clients - * from registering interfaces after it is stopped. + * from registering callbacks after it is stopped. * * @see #register */ public void kill() { - synchronized (mInterfaces) { - for (int cbi = mInterfaces.size() - 1; cbi >= 0; cbi--) { - Interface i = mInterfaces.valueAt(cbi); - i.mInterface.asBinder().unlinkToDeath(i, 0); - i.maybeUnsubscribeFromFrozenCallback(); + synchronized (mCallbacks) { + for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) { + Callback cb = mCallbacks.valueAt(cbi); + cb.mCallback.asBinder().unlinkToDeath(cb, 0); } - mInterfaces.clear(); + mCallbacks.clear(); mKilled = true; } } @@ -427,15 +186,15 @@ public class RemoteCallbackList<E extends IInterface> { * Old version of {@link #onCallbackDied(E, Object)} that * does not provide a cookie. */ - public void onCallbackDied(E callbackInterface) { + public void onCallbackDied(E callback) { } /** - * Called when the process hosting an interface in the list has gone away. + * Called when the process hosting a callback in the list has gone away. * The default implementation calls {@link #onCallbackDied(E)} * for backwards compatibility. * - * @param callbackInterface The interface whose process has died. Note that, since + * @param callback The callback whose process has died. Note that, since * its process has died, you can not make any calls on to this interface. * You can, however, retrieve its IBinder and compare it with another * IBinder to see if it is the same object. @@ -444,15 +203,13 @@ public class RemoteCallbackList<E extends IInterface> { * * @see #register */ - public void onCallbackDied(E callbackInterface, Object cookie) { - onCallbackDied(callbackInterface); + public void onCallbackDied(E callback, Object cookie) { + onCallbackDied(callback); } /** - * Use {@link #broadcast(Consumer)} instead to ensure proper handling of frozen processes. - * - * Prepare to start making calls to the currently registered interfaces. - * This creates a copy of the interface list, which you can retrieve items + * Prepare to start making calls to the currently registered callbacks. + * This creates a copy of the callback list, which you can retrieve items * from using {@link #getBroadcastItem}. Note that only one broadcast can * be active at a time, so you must be sure to always call this from the * same thread (usually by scheduling with {@link Handler}) or @@ -462,56 +219,44 @@ public class RemoteCallbackList<E extends IInterface> { * <p>A typical loop delivering a broadcast looks like this: * * <pre> - * int i = interfaces.beginBroadcast(); + * int i = callbacks.beginBroadcast(); * while (i > 0) { * i--; * try { - * interfaces.getBroadcastItem(i).somethingHappened(); + * callbacks.getBroadcastItem(i).somethingHappened(); * } catch (RemoteException e) { * // The RemoteCallbackList will take care of removing * // the dead object for us. * } * } - * interfaces.finishBroadcast();</pre> + * callbacks.finishBroadcast();</pre> * - * Note that this method is only supported for {@link #FROZEN_CALLEE_POLICY_UNSET}. For other - * policies use {@link #broadcast(Consumer)} instead. - * - * @return Returns the number of interfaces in the broadcast, to be used + * @return Returns the number of callbacks in the broadcast, to be used * with {@link #getBroadcastItem} to determine the range of indices you * can supply. * - * @throws UnsupportedOperationException if an frozen callee policy is set. - * * @see #getBroadcastItem * @see #finishBroadcast */ public int beginBroadcast() { - if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) { - throw new UnsupportedOperationException(); - } - return beginBroadcastInternal(); - } - - private int beginBroadcastInternal() { - synchronized (mInterfaces) { + synchronized (mCallbacks) { if (mBroadcastCount > 0) { throw new IllegalStateException( "beginBroadcast() called while already in a broadcast"); } - final int n = mBroadcastCount = mInterfaces.size(); - if (n <= 0) { + final int N = mBroadcastCount = mCallbacks.size(); + if (N <= 0) { return 0; } Object[] active = mActiveBroadcast; - if (active == null || active.length < n) { - mActiveBroadcast = active = new Object[n]; + if (active == null || active.length < N) { + mActiveBroadcast = active = new Object[N]; } - for (int i = 0; i < n; i++) { - active[i] = mInterfaces.valueAt(i); + for (int i=0; i<N; i++) { + active[i] = mCallbacks.valueAt(i); } - return n; + return N; } } @@ -522,23 +267,24 @@ public class RemoteCallbackList<E extends IInterface> { * calling {@link #finishBroadcast}. * * <p>Note that it is possible for the process of one of the returned - * interfaces to go away before you call it, so you will need to catch + * callbacks to go away before you call it, so you will need to catch * {@link RemoteException} when calling on to the returned object. - * The interface list itself, however, will take care of unregistering + * The callback list itself, however, will take care of unregistering * these objects once it detects that it is no longer valid, so you can * handle such an exception by simply ignoring it. * - * @param index Which of the registered interfaces you would like to + * @param index Which of the registered callbacks you would like to * retrieve. Ranges from 0 to {@link #beginBroadcast}-1, inclusive. * - * @return Returns the interface that you can call. This will always be non-null. + * @return Returns the callback interface that you can call. This will + * always be non-null. * * @see #beginBroadcast */ public E getBroadcastItem(int index) { - return ((Interface) mActiveBroadcast[index]).mInterface; + return ((Callback)mActiveBroadcast[index]).mCallback; } - + /** * Retrieve the cookie associated with the item * returned by {@link #getBroadcastItem(int)}. @@ -546,7 +292,7 @@ public class RemoteCallbackList<E extends IInterface> { * @see #getBroadcastItem */ public Object getBroadcastCookie(int index) { - return ((Interface) mActiveBroadcast[index]).mCookie; + return ((Callback)mActiveBroadcast[index]).mCookie; } /** @@ -557,7 +303,7 @@ public class RemoteCallbackList<E extends IInterface> { * @see #beginBroadcast */ public void finishBroadcast() { - synchronized (mInterfaces) { + synchronized (mCallbacks) { if (mBroadcastCount < 0) { throw new IllegalStateException( "finishBroadcast() called outside of a broadcast"); @@ -576,18 +322,16 @@ public class RemoteCallbackList<E extends IInterface> { } /** - * Performs {@code callback} on each registered interface. + * Performs {@code action} on each callback, calling + * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping * - * This is equivalent to #beginBroadcast, followed by iterating over the items using - * #getBroadcastItem and then @finishBroadcast, except that this method supports - * frozen callee policies. + * @hide */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public void broadcast(@NonNull Consumer<E> callback) { - int itemCount = beginBroadcastInternal(); + public void broadcast(Consumer<E> action) { + int itemCount = beginBroadcast(); try { for (int i = 0; i < itemCount; i++) { - ((Interface) mActiveBroadcast[i]).addCallback(callback); + action.accept(getBroadcastItem(i)); } } finally { finishBroadcast(); @@ -595,16 +339,16 @@ public class RemoteCallbackList<E extends IInterface> { } /** - * Performs {@code callback} for each cookie associated with an interface, calling + * Performs {@code action} for each cookie associated with a callback, calling * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping * * @hide */ - public <C> void broadcastForEachCookie(Consumer<C> callback) { + public <C> void broadcastForEachCookie(Consumer<C> action) { int itemCount = beginBroadcast(); try { for (int i = 0; i < itemCount; i++) { - callback.accept((C) getBroadcastCookie(i)); + action.accept((C) getBroadcastCookie(i)); } } finally { finishBroadcast(); @@ -612,16 +356,16 @@ public class RemoteCallbackList<E extends IInterface> { } /** - * Performs {@code callback} on each interface and associated cookie, calling {@link + * Performs {@code action} on each callback and associated cookie, calling {@link * #beginBroadcast()}/{@link #finishBroadcast()} before/after looping. * * @hide */ - public <C> void broadcast(BiConsumer<E, C> callback) { + public <C> void broadcast(BiConsumer<E, C> action) { int itemCount = beginBroadcast(); try { for (int i = 0; i < itemCount; i++) { - callback.accept(getBroadcastItem(i), (C) getBroadcastCookie(i)); + action.accept(getBroadcastItem(i), (C) getBroadcastCookie(i)); } } finally { finishBroadcast(); @@ -629,10 +373,10 @@ public class RemoteCallbackList<E extends IInterface> { } /** - * Returns the number of registered interfaces. Note that the number of registered - * interfaces may differ from the value returned by {@link #beginBroadcast()} since - * the former returns the number of interfaces registered at the time of the call - * and the second the number of interfaces to which the broadcast will be delivered. + * Returns the number of registered callbacks. Note that the number of registered + * callbacks may differ from the value returned by {@link #beginBroadcast()} since + * the former returns the number of callbacks registered at the time of the call + * and the second the number of callback to which the broadcast will be delivered. * <p> * This function is useful to decide whether to schedule a broadcast if this * requires doing some work which otherwise would not be performed. @@ -641,39 +385,39 @@ public class RemoteCallbackList<E extends IInterface> { * @return The size. */ public int getRegisteredCallbackCount() { - synchronized (mInterfaces) { + synchronized (mCallbacks) { if (mKilled) { return 0; } - return mInterfaces.size(); + return mCallbacks.size(); } } /** - * Return a currently registered interface. Note that this is + * Return a currently registered callback. Note that this is * <em>not</em> the same as {@link #getBroadcastItem} and should not be used - * interchangeably with it. This method returns the registered interface at the given + * interchangeably with it. This method returns the registered callback at the given * index, not the current broadcast state. This means that it is not itself thread-safe: * any call to {@link #register} or {@link #unregister} will change these indices, so you * must do your own thread safety between these to protect from such changes. * - * @param index Index of which interface registration to return, from 0 to + * @param index Index of which callback registration to return, from 0 to * {@link #getRegisteredCallbackCount()} - 1. * - * @return Returns whatever interface is associated with this index, or null if + * @return Returns whatever callback is associated with this index, or null if * {@link #kill()} has been called. */ public E getRegisteredCallbackItem(int index) { - synchronized (mInterfaces) { + synchronized (mCallbacks) { if (mKilled) { return null; } - return mInterfaces.valueAt(index).mInterface; + return mCallbacks.valueAt(index).mCallback; } } /** - * Return any cookie associated with a currently registered interface. Note that this is + * Return any cookie associated with a currently registered callback. Note that this is * <em>not</em> the same as {@link #getBroadcastCookie} and should not be used * interchangeably with it. This method returns the current cookie registered at the given * index, not the current broadcast state. This means that it is not itself thread-safe: @@ -687,25 +431,25 @@ public class RemoteCallbackList<E extends IInterface> { * {@link #kill()} has been called. */ public Object getRegisteredCallbackCookie(int index) { - synchronized (mInterfaces) { + synchronized (mCallbacks) { if (mKilled) { return null; } - return mInterfaces.valueAt(index).mCookie; + return mCallbacks.valueAt(index).mCookie; } } /** @hide */ public void dump(PrintWriter pw, String prefix) { - synchronized (mInterfaces) { - pw.print(prefix); pw.print("callbacks: "); pw.println(mInterfaces.size()); + synchronized (mCallbacks) { + pw.print(prefix); pw.print("callbacks: "); pw.println(mCallbacks.size()); pw.print(prefix); pw.print("killed: "); pw.println(mKilled); pw.print(prefix); pw.print("broadcasts count: "); pw.println(mBroadcastCount); } } - private void logExcessiveInterfaces() { - final long size = mInterfaces.size(); + private void logExcessiveCallbacks() { + final long size = mCallbacks.size(); final long TOO_MANY = 3000; final long MAX_CHARS = 1000; if (size >= TOO_MANY) { diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 8a8022c0206a..e940e55bd38b 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS; import static android.hardware.flags.Flags.FLAG_OVERLAYPROPERTIES_CLASS_API; import static com.android.server.display.feature.flags.Flags.FLAG_HIGHEST_HDR_SDR_RATIO_API; +import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_HAS_ARR_SUPPORT; import android.Manifest; import android.annotation.FlaggedApi; @@ -1266,6 +1267,18 @@ public final class Display { } /** + * Returns whether display supports adaptive refresh rate or not. + */ + // TODO(b/372526856) Add a link to the documentation for ARR. + @FlaggedApi(FLAG_ENABLE_HAS_ARR_SUPPORT) + public boolean hasArrSupport() { + synchronized (mLock) { + updateDisplayInfoLocked(); + return mDisplayInfo.hasArrSupport; + } + } + + /** * <p> Returns true if the connected display can be switched into a mode with minimal * post processing. </p> * diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index cac3e3c25098..26fce904eb5e 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -198,6 +198,12 @@ public final class DisplayInfo implements Parcelable { public float renderFrameRate; /** + * If {@code true} this Display supports adaptive refresh rates. + * // TODO(b/372526856) Add a link to the documentation for ARR. + */ + public boolean hasArrSupport; + + /** * The default display mode. */ public int defaultModeId; @@ -436,6 +442,7 @@ public final class DisplayInfo implements Parcelable { && rotation == other.rotation && modeId == other.modeId && renderFrameRate == other.renderFrameRate + && hasArrSupport == other.hasArrSupport && defaultModeId == other.defaultModeId && userPreferredModeId == other.userPreferredModeId && Arrays.equals(supportedModes, other.supportedModes) @@ -497,6 +504,7 @@ public final class DisplayInfo implements Parcelable { rotation = other.rotation; modeId = other.modeId; renderFrameRate = other.renderFrameRate; + hasArrSupport = other.hasArrSupport; defaultModeId = other.defaultModeId; userPreferredModeId = other.userPreferredModeId; supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length); @@ -553,6 +561,7 @@ public final class DisplayInfo implements Parcelable { rotation = source.readInt(); modeId = source.readInt(); renderFrameRate = source.readFloat(); + hasArrSupport = source.readBoolean(); defaultModeId = source.readInt(); userPreferredModeId = source.readInt(); int nModes = source.readInt(); @@ -626,6 +635,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(rotation); dest.writeInt(modeId); dest.writeFloat(renderFrameRate); + dest.writeBoolean(hasArrSupport); dest.writeInt(defaultModeId); dest.writeInt(userPreferredModeId); dest.writeInt(supportedModes.length); @@ -871,6 +881,8 @@ public final class DisplayInfo implements Parcelable { sb.append(modeId); sb.append(", renderFrameRate "); sb.append(renderFrameRate); + sb.append(", hasArrSupport "); + sb.append(hasArrSupport); sb.append(", defaultMode "); sb.append(defaultModeId); sb.append(", userPreferredModeId "); diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 19e244aa5981..e6de478e3d3d 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -1810,6 +1810,7 @@ public final class SurfaceControl implements Parcelable { public DisplayMode[] supportedDisplayModes; public int activeDisplayModeId; public float renderFrameRate; + public boolean hasArrSupport; public int[] supportedColorModes; public int activeColorMode; @@ -1827,6 +1828,7 @@ public final class SurfaceControl implements Parcelable { + "supportedDisplayModes=" + Arrays.toString(supportedDisplayModes) + ", activeDisplayModeId=" + activeDisplayModeId + ", renderFrameRate=" + renderFrameRate + + ", hasArrSupport=" + hasArrSupport + ", supportedColorModes=" + Arrays.toString(supportedColorModes) + ", activeColorMode=" + activeColorMode + ", hdrCapabilities=" + hdrCapabilities @@ -1846,13 +1848,14 @@ public final class SurfaceControl implements Parcelable { && Arrays.equals(supportedColorModes, that.supportedColorModes) && activeColorMode == that.activeColorMode && Objects.equals(hdrCapabilities, that.hdrCapabilities) - && preferredBootDisplayMode == that.preferredBootDisplayMode; + && preferredBootDisplayMode == that.preferredBootDisplayMode + && hasArrSupport == that.hasArrSupport; } @Override public int hashCode() { return Objects.hash(Arrays.hashCode(supportedDisplayModes), activeDisplayModeId, - renderFrameRate, activeColorMode, hdrCapabilities); + renderFrameRate, activeColorMode, hdrCapabilities, hasArrSupport); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index b921213cc26c..3be9a821a463 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -6971,9 +6971,7 @@ public final class ViewRootImpl implements ViewParent, handleScrollCaptureRequest((IScrollCaptureResponseListener) msg.obj); break; case MSG_PAUSED_FOR_SYNC_TIMEOUT: - Log.e(mTag, "Timedout waiting to unpause for sync"); - mNumPausedForSync = 0; - scheduleTraversals(); + resumeAfterSyncTimeout(); break; case MSG_CHECK_INVALIDATION_IDLE: { long delta; @@ -12777,6 +12775,15 @@ public final class ViewRootImpl implements ViewParent, activeSurfaceSyncGroup.addTransaction(t); } + /** + * Resume rendering after being paused for sync due to a timeout. + */ + private void resumeAfterSyncTimeout() { + Log.e(mTag, "Timedout waiting to unpause for sync mNumPausedForSync=" + mNumPausedForSync); + mNumPausedForSync = 0; + scheduleTraversals(); + } + @Override public SurfaceSyncGroup getOrCreateSurfaceSyncGroup() { boolean newSyncGroup = false; @@ -12804,6 +12811,16 @@ public final class ViewRootImpl implements ViewParent, } }); newSyncGroup = true; + + // If the sync group is marked ready by a timeout, check if rendering is paused and + // if it is, resume rendering and trigger a traversal. + mActiveSurfaceSyncGroup.addSyncCompleteCallback(mExecutor, () -> { + if (mActiveSurfaceSyncGroup != null + && mActiveSurfaceSyncGroup.isComplete() && mNumPausedForSync > 0) { + mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT); + resumeAfterSyncTimeout(); + } + }); } Trace.instant(Trace.TRACE_TAG_VIEW, @@ -12818,12 +12835,20 @@ public final class ViewRootImpl implements ViewParent, } } - mNumPausedForSync++; - mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT); - mHandler.sendEmptyMessageDelayed(MSG_PAUSED_FOR_SYNC_TIMEOUT, - 1000 * Build.HW_TIMEOUT_MULTIPLIER); + // The sync group can be marked ready by a timeout. This makes incrementing + // mNumPausedForSync racy. Here we check if the sync group is complete and + // if it is then we don't pause for syncing. + if (!mActiveSurfaceSyncGroup.isComplete()) { + mNumPausedForSync++; + mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT); + mHandler.sendEmptyMessageDelayed(MSG_PAUSED_FOR_SYNC_TIMEOUT, + 1000 * Build.HW_TIMEOUT_MULTIPLIER); + } else { + Log.d(mTag, "Active sync group is already completed " + + mActiveSurfaceSyncGroup.getName()); + } return mActiveSurfaceSyncGroup; - }; + } private final Executor mSimpleExecutor = Runnable::run; diff --git a/core/java/android/widget/RemoteCollectionItemsAdapter.java b/core/java/android/widget/RemoteCollectionItemsAdapter.java index 9b396aeea9eb..fc09f880ccd4 100644 --- a/core/java/android/widget/RemoteCollectionItemsAdapter.java +++ b/core/java/android/widget/RemoteCollectionItemsAdapter.java @@ -40,13 +40,15 @@ class RemoteCollectionItemsAdapter extends BaseAdapter { private RemoteCollectionItems mItems; private InteractionHandler mInteractionHandler; private ColorResources mColorResources; + private boolean mOnLightBackground; private SparseIntArray mLayoutIdToViewType; RemoteCollectionItemsAdapter( @NonNull RemoteCollectionItems items, @NonNull InteractionHandler interactionHandler, - @NonNull ColorResources colorResources) { + @NonNull ColorResources colorResources, + boolean onLightBackground) { // View type count can never increase after an adapter has been set on a ListView. // Additionally, decreasing it could inhibit view recycling if the count were to back and // forth between 3-2-3-2 for example. Therefore, the view type count, should be fixed for @@ -56,6 +58,7 @@ class RemoteCollectionItemsAdapter extends BaseAdapter { mItems = items; mInteractionHandler = interactionHandler; mColorResources = colorResources; + mOnLightBackground = onLightBackground; initLayoutIdToViewType(); } @@ -68,7 +71,8 @@ class RemoteCollectionItemsAdapter extends BaseAdapter { void setData( @NonNull RemoteCollectionItems items, @NonNull InteractionHandler interactionHandler, - @NonNull ColorResources colorResources) { + @NonNull ColorResources colorResources, + boolean onLightBackground) { if (mViewTypeCount < items.getViewTypeCount()) { throw new IllegalArgumentException( "RemoteCollectionItemsAdapter cannot increase view type count after creation"); @@ -77,6 +81,7 @@ class RemoteCollectionItemsAdapter extends BaseAdapter { mItems = items; mInteractionHandler = interactionHandler; mColorResources = colorResources; + mOnLightBackground = onLightBackground; initLayoutIdToViewType(); @@ -184,6 +189,7 @@ class RemoteCollectionItemsAdapter extends BaseAdapter { : new AppWidgetHostView.AdapterChildHostView(parent.getContext()); newView.setInteractionHandler(mInteractionHandler); newView.setColorResourcesNoReapply(mColorResources); + newView.setOnLightBackground(mOnLightBackground); newView.updateAppWidget(item); return newView; } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index d7b5211ad9fd..9b6311f35d17 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1248,6 +1248,7 @@ public class RemoteViews implements Parcelable, Filter { AdapterView adapterView = (AdapterView) target; Adapter adapter = adapterView.getAdapter(); + boolean onLightBackground = hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT); // We can reuse the adapter if it's a RemoteCollectionItemsAdapter and the view type // count hasn't increased. Note that AbsListView allocates a fixed size array for view // recycling in setAdapter, so we must call setAdapter again if the number of view types @@ -1255,8 +1256,12 @@ public class RemoteViews implements Parcelable, Filter { if (adapter instanceof RemoteCollectionItemsAdapter && adapter.getViewTypeCount() >= items.getViewTypeCount()) { try { - ((RemoteCollectionItemsAdapter) adapter).setData( - items, params.handler, params.colorResources); + ((RemoteCollectionItemsAdapter) adapter) + .setData( + items, + params.handler, + params.colorResources, + onLightBackground); } catch (Throwable throwable) { // setData should never failed with the validation in the items builder, but if // it does, catch and rethrow. @@ -1266,8 +1271,9 @@ public class RemoteViews implements Parcelable, Filter { } try { - adapterView.setAdapter(new RemoteCollectionItemsAdapter(items, - params.handler, params.colorResources)); + adapterView.setAdapter( + new RemoteCollectionItemsAdapter( + items, params.handler, params.colorResources, onLightBackground)); } catch (Throwable throwable) { // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to // a type error. diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java index 1c3f201c1471..23e572fcd577 100644 --- a/core/java/android/window/BackEvent.java +++ b/core/java/android/window/BackEvent.java @@ -156,6 +156,22 @@ public final class BackEvent { } @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof BackEvent)) { + return false; + } + final BackEvent that = (BackEvent) other; + return mTouchX == that.mTouchX + && mTouchY == that.mTouchY + && mProgress == that.mProgress + && mSwipeEdge == that.mSwipeEdge + && mFrameTime == that.mFrameTime; + } + + @Override public String toString() { return "BackEvent{" + "mTouchX=" + mTouchX diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java index 5d14698c82b3..a68bdc05e20e 100644 --- a/core/java/android/window/SurfaceSyncGroup.java +++ b/core/java/android/window/SurfaceSyncGroup.java @@ -839,6 +839,16 @@ public final class SurfaceSyncGroup { } /** + * Returns true if the SurfaceSyncGroup has completed its sync. + * @hide + */ + public boolean isComplete() { + synchronized (mLock) { + return mFinished; + } + } + + /** * A frame callback that is used to synchronize SurfaceViews. The owner of the SurfaceView must * implement onFrameStarted when trying to sync the SurfaceView. This is to ensure the sync * knows when the frame is ready to add to the sync. diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java index 20d1b3bd12ae..a37bef80ff04 100644 --- a/core/java/android/window/TaskSnapshot.java +++ b/core/java/android/window/TaskSnapshot.java @@ -83,13 +83,16 @@ public class TaskSnapshot implements Parcelable { public static final int REFERENCE_CACHE = 1 << 1; /** This snapshot object is being persistent. */ public static final int REFERENCE_PERSIST = 1 << 2; + /** This snapshot object is being used for content suggestion. */ + public static final int REFERENCE_CONTENT_SUGGESTION = 1 << 3; @IntDef(flag = true, prefix = { "REFERENCE_" }, value = { REFERENCE_BROADCAST, REFERENCE_CACHE, - REFERENCE_PERSIST + REFERENCE_PERSIST, + REFERENCE_CONTENT_SUGGESTION }) @Retention(RetentionPolicy.SOURCE) - @interface ReferenceFlags {} + public @interface ReferenceFlags {} public TaskSnapshot(long id, long captureTime, @NonNull ComponentName topActivityComponent, HardwareBuffer snapshot, diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 8e495ec1dc40..34abf3114925 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -525,6 +525,22 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * Disables or enables activities to be started in adjacent tasks (see + * {@link FLAG_ACTIVITY_LAUNCH_ADJACENT}) for the specified root of any child tasks. This + * differs from {@link #setLaunchAdjacentFlagRoot(WindowContainerToken)} which controls the + * preferred launch-adjacent target and allows for selectively setting which root tasks can + * support launch-adjacent. + * @hide + */ + @NonNull + public WindowContainerTransaction setDisableLaunchAdjacent( + @NonNull WindowContainerToken container, boolean disabled) { + mHierarchyOps.add(HierarchyOp.createForSetDisableLaunchAdjacent(container.asBinder(), + disabled)); + return this; + } + + /** * Starts a task by id. The task is expected to already exist (eg. as a recent task). * @param taskId Id of task to start. * @param options bundle containing ActivityOptions for the task's top activity. @@ -1488,6 +1504,7 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION = 20; public static final int HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES = 21; public static final int HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE = 22; + public static final int HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT = 23; // The following key(s) are for use with mLaunchOptions: // When launching a task (eg. from recents), this is the taskId to be launched. @@ -1556,6 +1573,8 @@ public final class WindowContainerTransaction implements Parcelable { private @InsetsType int mExcludeInsetsTypes; + private boolean mLaunchAdjacentDisabled; + public static HierarchyOp createForReparent( @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) { return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT) @@ -1644,6 +1663,15 @@ public final class WindowContainerTransaction implements Parcelable { .build(); } + /** Create a hierarchy op for disabling launch adjacent. */ + public static HierarchyOp createForSetDisableLaunchAdjacent(IBinder container, + boolean disabled) { + return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT) + .setContainer(container) + .setLaunchAdjacentDisabled(disabled) + .build(); + } + /** create a hierarchy op for deleting a task **/ public static HierarchyOp createForRemoveTask(@NonNull IBinder container) { return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REMOVE_TASK) @@ -1695,6 +1723,7 @@ public final class WindowContainerTransaction implements Parcelable { mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch; mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents; mExcludeInsetsTypes = copy.mExcludeInsetsTypes; + mLaunchAdjacentDisabled = copy.mLaunchAdjacentDisabled; } protected HierarchyOp(Parcel in) { @@ -1719,6 +1748,7 @@ public final class WindowContainerTransaction implements Parcelable { mReparentLeafTaskIfRelaunch = in.readBoolean(); mIsTrimmableFromRecents = in.readBoolean(); mExcludeInsetsTypes = in.readInt(); + mLaunchAdjacentDisabled = in.readBoolean(); } public int getType() { @@ -1814,13 +1844,11 @@ public final class WindowContainerTransaction implements Parcelable { } /** Denotes whether the parents should also be included in the op. */ - @NonNull public boolean includingParents() { return mIncludingParents; } - /** Set the task to be trimmable */ - @NonNull + /** Denotes whether the task can be trimmable from recents */ public boolean isTrimmableFromRecents() { return mIsTrimmableFromRecents; } @@ -1829,6 +1857,11 @@ public final class WindowContainerTransaction implements Parcelable { return mExcludeInsetsTypes; } + /** Denotes whether launch-adjacent flag is respected from this task or its children */ + public boolean isLaunchAdjacentDisabled() { + return mLaunchAdjacentDisabled; + } + /** Gets a string representation of a hierarchy-op type. */ public static String hopToString(int type) { switch (type) { @@ -1839,6 +1872,8 @@ public final class WindowContainerTransaction implements Parcelable { case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "SetAdjacentRoot"; case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "LaunchTask"; case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "SetAdjacentFlagRoot"; + case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT: + return "SetDisableLaunchAdjacent"; case HIERARCHY_OP_TYPE_PENDING_INTENT: return "PendingIntent"; case HIERARCHY_OP_TYPE_START_SHORTCUT: return "StartShortcut"; case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: return "addInsetsFrameProvider"; @@ -1891,6 +1926,10 @@ public final class WindowContainerTransaction implements Parcelable { case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: sb.append("container=").append(mContainer).append(" clearRoot=").append(mToTop); break; + case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT: + sb.append("container=").append(mContainer).append(" disabled=") + .append(mLaunchAdjacentDisabled); + break; case HIERARCHY_OP_TYPE_START_SHORTCUT: sb.append("options=").append(mLaunchOptions) .append(" info=").append(mShortcutInfo); @@ -1971,6 +2010,7 @@ public final class WindowContainerTransaction implements Parcelable { dest.writeBoolean(mReparentLeafTaskIfRelaunch); dest.writeBoolean(mIsTrimmableFromRecents); dest.writeInt(mExcludeInsetsTypes); + dest.writeBoolean(mLaunchAdjacentDisabled); } @Override @@ -2047,6 +2087,8 @@ public final class WindowContainerTransaction implements Parcelable { private @InsetsType int mExcludeInsetsTypes; + private boolean mLaunchAdjacentDisabled; + Builder(int type) { mType = type; } @@ -2153,6 +2195,11 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + Builder setLaunchAdjacentDisabled(boolean disabled) { + mLaunchAdjacentDisabled = disabled; + return this; + } + HierarchyOp build() { final HierarchyOp hierarchyOp = new HierarchyOp(mType); hierarchyOp.mContainer = mContainer; @@ -2179,6 +2226,7 @@ public final class WindowContainerTransaction implements Parcelable { hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch; hierarchyOp.mIsTrimmableFromRecents = mIsTrimmableFromRecents; hierarchyOp.mExcludeInsetsTypes = mExcludeInsetsTypes; + hierarchyOp.mLaunchAdjacentDisabled = mLaunchAdjacentDisabled; return hierarchyOp; } diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 44a374fb7c20..c9d458f22463 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -17,6 +17,7 @@ package android.window; import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver; +import static com.android.window.flags.Flags.predictiveBackTimestampApi; import android.annotation.NonNull; import android.annotation.Nullable; @@ -563,7 +564,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } OnBackAnimationCallback animationCallback = getBackAnimationCallback(); if (animationCallback != null - && !(callback instanceof ImeBackAnimationController)) { + && !(callback instanceof ImeBackAnimationController) + && !predictiveBackTimestampApi()) { mProgressAnimator.onBackInvoked(() -> { if (mIsSystemCallback) { mSystemNavigationObserverCallbackRunnable.run(); diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 155494fb3b25..18129530978f 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -239,6 +239,13 @@ flag { } flag { + name: "enable_desktop_windowing_enter_transitions" + namespace: "lse_desktop_experience" + description: "Enables enter desktop windowing transition & motion polish changes" + bug: "369763947" +} + +flag { name: "enable_desktop_windowing_exit_transitions" namespace: "lse_desktop_experience" description: "Enables exit desktop windowing transition & motion polish changes" diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 460469c13a3e..0d235ffad9b5 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -70,17 +70,6 @@ flag { } flag { - name: "common_surface_animator" - namespace: "windowing_frontend" - description: "A reusable surface animator for default transition" - bug: "326331384" - is_fixed_read_only: true - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "reduce_keyguard_transitions" namespace: "windowing_frontend" description: "Avoid setting keyguard transitions ready unless there are no other changes" diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java index 0a80e006d5bc..dd6c879f1135 100644 --- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java +++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java @@ -39,7 +39,7 @@ public class ProtoLogViewerConfigReader { * or the viewer config is not loaded into memory. */ @Nullable - public synchronized String getViewerString(long messageHash) { + public String getViewerString(long messageHash) { return mLogMessageMap.get(messageHash); } diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java index 98e6e8505534..adcc0f64b598 100644 --- a/core/java/com/android/internal/widget/NotificationRowIconView.java +++ b/core/java/com/android/internal/widget/NotificationRowIconView.java @@ -39,17 +39,24 @@ import com.android.internal.R; /** * An image view that holds the icon displayed at the start of a notification row. + * This can generally either display the "small icon" of a notification set via + * {@link this#setImageIcon(Icon)}, or an app icon controlled and fetched by the provider set + * through {@link this#setIconProvider(NotificationIconProvider)}. */ @RemoteViews.RemoteView public class NotificationRowIconView extends CachingIconView { + private NotificationIconProvider mIconProvider; + private boolean mApplyCircularCrop = false; private boolean mShouldShowAppIcon = false; + private Drawable mAppIcon = null; - // Padding and background set on the view prior to being changed by setShouldShowAppIcon(true), - // to be restored if shouldShowAppIcon becomes false again. + // Padding, background and colors set on the view prior to being overridden when showing the app + // icon, to be restored if we're showing the small icon again. private Rect mOriginalPadding = null; private Drawable mOriginalBackground = null; - + private int mOriginalBackgroundColor = ColoredIconHelper.COLOR_INVALID; + private int mOriginalIconColor = ColoredIconHelper.COLOR_INVALID; public NotificationRowIconView(Context context) { super(context); @@ -81,6 +88,71 @@ public class NotificationRowIconView extends CachingIconView { super.onFinishInflate(); } + /** + * Sets the icon provider for this view. This is used to determine whether we should show the + * app icon instead of the small icon, and to fetch the app icon if needed. + */ + public void setIconProvider(NotificationIconProvider iconProvider) { + mIconProvider = iconProvider; + } + + private Drawable loadAppIcon() { + if (mIconProvider != null && mIconProvider.shouldShowAppIcon()) { + return mIconProvider.getAppIcon(); + } + return null; + } + + @RemotableViewMethod(asyncImpl = "setImageIconAsync") + @Override + public void setImageIcon(Icon icon) { + if (Flags.notificationsRedesignAppIcons()) { + if (mAppIcon != null) { + // We already know that we should be using the app icon, and we already loaded it. + // We assume that cannot change throughout the lifetime of a notification, so + // there's nothing to do here. + return; + } + mAppIcon = loadAppIcon(); + if (mAppIcon != null) { + setImageDrawable(mAppIcon); + adjustViewForAppIcon(); + } else { + super.setImageIcon(icon); + restoreViewForSmallIcon(); + } + return; + } + super.setImageIcon(icon); + } + + @RemotableViewMethod + @Override + public Runnable setImageIconAsync(Icon icon) { + if (Flags.notificationsRedesignAppIcons()) { + if (mAppIcon != null) { + // We already know that we should be using the app icon, and we already loaded it. + // We assume that cannot change throughout the lifetime of a notification, so + // there's nothing to do here. + return () -> { + }; + } + mAppIcon = loadAppIcon(); + if (mAppIcon != null) { + return () -> { + setImageDrawable(mAppIcon); + adjustViewForAppIcon(); + }; + } else { + return () -> { + super.setImageIcon(icon); + restoreViewForSmallIcon(); + }; + } + } + return super.setImageIconAsync(icon); + } + /** Whether the icon represents the app icon (instead of the small icon). */ @RemotableViewMethod public void setShouldShowAppIcon(boolean shouldShowAppIcon) { @@ -91,35 +163,122 @@ public class NotificationRowIconView extends CachingIconView { mShouldShowAppIcon = shouldShowAppIcon; if (mShouldShowAppIcon) { - if (mOriginalPadding == null && mOriginalBackground == null) { - mOriginalPadding = new Rect(getPaddingLeft(), getPaddingTop(), - getPaddingRight(), getPaddingBottom()); - mOriginalBackground = getBackground(); - } - - setPadding(0, 0, 0, 0); - - // Make the background white in case the icon itself doesn't have one. - ColorFilter colorFilter = new PorterDuffColorFilter(Color.WHITE, - PorterDuff.Mode.SRC_ATOP); - - if (mOriginalBackground == null) { - setBackground(getContext().getDrawable(R.drawable.notification_icon_circle)); - } - getBackground().mutate().setColorFilter(colorFilter); + adjustViewForAppIcon(); } else { // Restore original padding and background if needed - if (mOriginalPadding != null) { - setPadding(mOriginalPadding.left, mOriginalPadding.top, mOriginalPadding.right, - mOriginalPadding.bottom); - mOriginalPadding = null; - } - setBackground(mOriginalBackground); - mOriginalBackground = null; + restoreViewForSmallIcon(); } } } + /** + * Override padding and background from the view to display the app icon. + */ + private void adjustViewForAppIcon() { + removePadding(); + + if (Flags.notificationsUseAppIconInRow()) { + addWhiteBackground(); + } else { + // No need to set the background for notification redesign, since the icon + // factory already does that for us. + removeBackground(); + } + } + + /** + * Restore padding and background overridden by {@link this#adjustViewForAppIcon}. + * Does nothing if they were not overridden. + */ + private void restoreViewForSmallIcon() { + restorePadding(); + restoreBackground(); + restoreColors(); + } + + private void removePadding() { + if (mOriginalPadding == null) { + mOriginalPadding = new Rect(getPaddingLeft(), getPaddingTop(), + getPaddingRight(), getPaddingBottom()); + } + setPadding(0, 0, 0, 0); + } + + private void restorePadding() { + if (mOriginalPadding != null) { + setPadding(mOriginalPadding.left, mOriginalPadding.top, + mOriginalPadding.right, + mOriginalPadding.bottom); + mOriginalPadding = null; + } + } + + private void removeBackground() { + if (mOriginalBackground == null) { + mOriginalBackground = getBackground(); + } + + setBackground(null); + } + + private void addWhiteBackground() { + if (mOriginalBackground == null) { + mOriginalBackground = getBackground(); + } + + // Make the background white in case the icon itself doesn't have one. + ColorFilter colorFilter = new PorterDuffColorFilter(Color.WHITE, + PorterDuff.Mode.SRC_ATOP); + + if (mOriginalBackground == null) { + setBackground(getContext().getDrawable(R.drawable.notification_icon_circle)); + } + getBackground().mutate().setColorFilter(colorFilter); + } + + private void restoreBackground() { + // NOTE: This will not work if the original background was null, but that's better than + // accidentally clearing the background. We expect that there's generally going to be one + // anyway unless we manually clear it. + if (mOriginalBackground != null) { + setBackground(mOriginalBackground); + mOriginalBackground = null; + } + } + + private void restoreColors() { + if (mOriginalBackgroundColor != ColoredIconHelper.COLOR_INVALID) { + super.setBackgroundColor(mOriginalBackgroundColor); + mOriginalBackgroundColor = ColoredIconHelper.COLOR_INVALID; + } + if (mOriginalIconColor != ColoredIconHelper.COLOR_INVALID) { + super.setOriginalIconColor(mOriginalIconColor); + mOriginalIconColor = ColoredIconHelper.COLOR_INVALID; + } + } + + @RemotableViewMethod + @Override + public void setBackgroundColor(int color) { + // Ignore color overrides if we're showing the app icon. + if (mAppIcon == null) { + super.setBackgroundColor(color); + } else { + mOriginalBackgroundColor = color; + } + } + + @RemotableViewMethod + @Override + public void setOriginalIconColor(int color) { + // Ignore color overrides if we're showing the app icon. + if (mAppIcon == null) { + super.setOriginalIconColor(color); + } else { + mOriginalIconColor = color; + } + } + @Nullable @Override Drawable loadSizeRestrictedIcon(@Nullable Icon icon) { @@ -197,4 +356,17 @@ public class NotificationRowIconView extends CachingIconView { return bitmap; } + + /** + * A provider that allows this view to verify whether it should use the app icon instead of the + * icon provided to it via setImageIcon, as well as actually fetching the app icon. It should + * primarily be called on the background thread. + */ + public interface NotificationIconProvider { + /** Whether this notification should use the app icon instead of the small icon. */ + boolean shouldShowAppIcon(); + + /** Get the app icon for this notification. */ + Drawable getAppIcon(); + } } diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 9bccf5af7096..8eaa7aa99a2d 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -747,16 +747,12 @@ android_media_AudioSystem_initStreamVolume(JNIEnv *env, jobject thiz, jint strea indexMax)); } -static jint -android_media_AudioSystem_setStreamVolumeIndex(JNIEnv *env, - jobject thiz, - jint stream, - jint index, - jint device) -{ +static jint android_media_AudioSystem_setStreamVolumeIndex(JNIEnv *env, jobject thiz, jint stream, + jint index, jboolean muted, + jint device) { return check_AudioSystem_Command( AudioSystem::setStreamVolumeIndex(static_cast<audio_stream_type_t>(stream), index, - static_cast<audio_devices_t>(device))); + muted, static_cast<audio_devices_t>(device))); } static jint @@ -773,13 +769,9 @@ android_media_AudioSystem_getStreamVolumeIndex(JNIEnv *env, return index; } -static jint -android_media_AudioSystem_setVolumeIndexForAttributes(JNIEnv *env, - jobject thiz, - jobject jaa, - jint index, - jint device) -{ +static jint android_media_AudioSystem_setVolumeIndexForAttributes(JNIEnv *env, jobject thiz, + jobject jaa, jint index, + jboolean muted, jint device) { // read the AudioAttributes values JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique(); jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get()); @@ -787,7 +779,7 @@ android_media_AudioSystem_setVolumeIndexForAttributes(JNIEnv *env, return jStatus; } return check_AudioSystem_Command( - AudioSystem::setVolumeIndexForAttributes(*(paa.get()), index, + AudioSystem::setVolumeIndexForAttributes(*(paa.get()), index, muted, static_cast<audio_devices_t>(device))); } @@ -3448,182 +3440,179 @@ static void android_media_AudioSystem_triggerSystemPropertyUpdate(JNIEnv *env, #define MAKE_AUDIO_SYSTEM_METHOD(x) \ MAKE_JNI_NATIVE_METHOD_AUTOSIG(#x, android_media_AudioSystem_##x) -static const JNINativeMethod gMethods[] = - {MAKE_AUDIO_SYSTEM_METHOD(setParameters), - MAKE_AUDIO_SYSTEM_METHOD(getParameters), - MAKE_AUDIO_SYSTEM_METHOD(muteMicrophone), - MAKE_AUDIO_SYSTEM_METHOD(isMicrophoneMuted), - MAKE_AUDIO_SYSTEM_METHOD(isStreamActive), - MAKE_AUDIO_SYSTEM_METHOD(isStreamActiveRemotely), - MAKE_AUDIO_SYSTEM_METHOD(isSourceActive), - MAKE_AUDIO_SYSTEM_METHOD(newAudioSessionId), - MAKE_AUDIO_SYSTEM_METHOD(newAudioPlayerId), - MAKE_AUDIO_SYSTEM_METHOD(newAudioRecorderId), - MAKE_JNI_NATIVE_METHOD("setDeviceConnectionState", "(ILandroid/os/Parcel;I)I", - android_media_AudioSystem_setDeviceConnectionState), - MAKE_AUDIO_SYSTEM_METHOD(getDeviceConnectionState), - MAKE_AUDIO_SYSTEM_METHOD(handleDeviceConfigChange), - MAKE_AUDIO_SYSTEM_METHOD(setPhoneState), - MAKE_AUDIO_SYSTEM_METHOD(setForceUse), - MAKE_AUDIO_SYSTEM_METHOD(getForceUse), - MAKE_AUDIO_SYSTEM_METHOD(setDeviceAbsoluteVolumeEnabled), - MAKE_AUDIO_SYSTEM_METHOD(initStreamVolume), - MAKE_AUDIO_SYSTEM_METHOD(setStreamVolumeIndex), - MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeIndex), - MAKE_JNI_NATIVE_METHOD("setVolumeIndexForAttributes", - "(Landroid/media/AudioAttributes;II)I", - android_media_AudioSystem_setVolumeIndexForAttributes), - MAKE_JNI_NATIVE_METHOD("getVolumeIndexForAttributes", - "(Landroid/media/AudioAttributes;I)I", - android_media_AudioSystem_getVolumeIndexForAttributes), - MAKE_JNI_NATIVE_METHOD("getMinVolumeIndexForAttributes", - "(Landroid/media/AudioAttributes;)I", - android_media_AudioSystem_getMinVolumeIndexForAttributes), - MAKE_JNI_NATIVE_METHOD("getMaxVolumeIndexForAttributes", - "(Landroid/media/AudioAttributes;)I", - android_media_AudioSystem_getMaxVolumeIndexForAttributes), - MAKE_AUDIO_SYSTEM_METHOD(setMasterVolume), - MAKE_AUDIO_SYSTEM_METHOD(getMasterVolume), - MAKE_AUDIO_SYSTEM_METHOD(setMasterMute), - MAKE_AUDIO_SYSTEM_METHOD(getMasterMute), - MAKE_AUDIO_SYSTEM_METHOD(setMasterMono), - MAKE_AUDIO_SYSTEM_METHOD(getMasterMono), - MAKE_AUDIO_SYSTEM_METHOD(setMasterBalance), - MAKE_AUDIO_SYSTEM_METHOD(getMasterBalance), - MAKE_AUDIO_SYSTEM_METHOD(getPrimaryOutputSamplingRate), - MAKE_AUDIO_SYSTEM_METHOD(getPrimaryOutputFrameCount), - MAKE_AUDIO_SYSTEM_METHOD(getOutputLatency), - MAKE_AUDIO_SYSTEM_METHOD(setLowRamDevice), - MAKE_AUDIO_SYSTEM_METHOD(checkAudioFlinger), - MAKE_JNI_NATIVE_METHOD("setAudioFlingerBinder", "(Landroid/os/IBinder;)V", - android_media_AudioSystem_setAudioFlingerBinder), - MAKE_JNI_NATIVE_METHOD("listAudioPorts", "(Ljava/util/ArrayList;[I)I", - android_media_AudioSystem_listAudioPorts), - MAKE_JNI_NATIVE_METHOD("getSupportedDeviceTypes", "(ILandroid/util/IntArray;)I", - android_media_AudioSystem_getSupportedDeviceTypes), - MAKE_JNI_NATIVE_METHOD("createAudioPatch", - "([Landroid/media/AudioPatch;[Landroid/media/" - "AudioPortConfig;[Landroid/media/AudioPortConfig;)I", - android_media_AudioSystem_createAudioPatch), - MAKE_JNI_NATIVE_METHOD("releaseAudioPatch", "(Landroid/media/AudioPatch;)I", - android_media_AudioSystem_releaseAudioPatch), - MAKE_JNI_NATIVE_METHOD("listAudioPatches", "(Ljava/util/ArrayList;[I)I", - android_media_AudioSystem_listAudioPatches), - MAKE_JNI_NATIVE_METHOD("setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I", - android_media_AudioSystem_setAudioPortConfig), - MAKE_JNI_NATIVE_METHOD("startAudioSource", - "(Landroid/media/AudioPortConfig;Landroid/media/AudioAttributes;)I", - android_media_AudioSystem_startAudioSource), - MAKE_AUDIO_SYSTEM_METHOD(stopAudioSource), - MAKE_AUDIO_SYSTEM_METHOD(getAudioHwSyncForSession), - MAKE_JNI_NATIVE_METHOD("registerPolicyMixes", "(Ljava/util/ArrayList;Z)I", - android_media_AudioSystem_registerPolicyMixes), - MAKE_JNI_NATIVE_METHOD("getRegisteredPolicyMixes", "(Ljava/util/List;)I", - android_media_AudioSystem_getRegisteredPolicyMixes), - MAKE_JNI_NATIVE_METHOD("updatePolicyMixes", - "([Landroid/media/audiopolicy/AudioMix;[Landroid/media/audiopolicy/" - "AudioMixingRule;)I", - android_media_AudioSystem_updatePolicyMixes), - MAKE_JNI_NATIVE_METHOD("setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I", - android_media_AudioSystem_setUidDeviceAffinities), - MAKE_AUDIO_SYSTEM_METHOD(removeUidDeviceAffinities), - MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_dynamic_policy_callback", - android_media_AudioSystem_registerDynPolicyCallback), - MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_recording_callback", - android_media_AudioSystem_registerRecordingCallback), - MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_routing_callback", - android_media_AudioSystem_registerRoutingCallback), - MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_vol_range_init_req_callback", - android_media_AudioSystem_registerVolRangeInitReqCallback), - MAKE_AUDIO_SYSTEM_METHOD(systemReady), - MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeDB), - MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_get_offload_support", - android_media_AudioSystem_getOffloadSupport), - MAKE_JNI_NATIVE_METHOD("getMicrophones", "(Ljava/util/ArrayList;)I", - android_media_AudioSystem_getMicrophones), - MAKE_JNI_NATIVE_METHOD("getSurroundFormats", "(Ljava/util/Map;)I", - android_media_AudioSystem_getSurroundFormats), - MAKE_JNI_NATIVE_METHOD("getReportedSurroundFormats", "(Ljava/util/ArrayList;)I", - android_media_AudioSystem_getReportedSurroundFormats), - MAKE_AUDIO_SYSTEM_METHOD(setSurroundFormatEnabled), - MAKE_AUDIO_SYSTEM_METHOD(setAssistantServicesUids), - MAKE_AUDIO_SYSTEM_METHOD(setActiveAssistantServicesUids), - MAKE_AUDIO_SYSTEM_METHOD(setA11yServicesUids), - MAKE_AUDIO_SYSTEM_METHOD(isHapticPlaybackSupported), - MAKE_AUDIO_SYSTEM_METHOD(isUltrasoundSupported), - MAKE_JNI_NATIVE_METHOD( - "getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I", - android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia), - MAKE_AUDIO_SYSTEM_METHOD(setSupportedSystemUsages), - MAKE_AUDIO_SYSTEM_METHOD(setAllowedCapturePolicy), - MAKE_AUDIO_SYSTEM_METHOD(setRttEnabled), - MAKE_AUDIO_SYSTEM_METHOD(setAudioHalPids), - MAKE_AUDIO_SYSTEM_METHOD(isCallScreeningModeSupported), - MAKE_JNI_NATIVE_METHOD("setDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I", - android_media_AudioSystem_setDevicesRoleForStrategy), - MAKE_JNI_NATIVE_METHOD("removeDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I", - android_media_AudioSystem_removeDevicesRoleForStrategy), - MAKE_AUDIO_SYSTEM_METHOD(clearDevicesRoleForStrategy), - MAKE_JNI_NATIVE_METHOD("getDevicesForRoleAndStrategy", "(IILjava/util/List;)I", - android_media_AudioSystem_getDevicesForRoleAndStrategy), - MAKE_JNI_NATIVE_METHOD("setDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I", - android_media_AudioSystem_setDevicesRoleForCapturePreset), - MAKE_JNI_NATIVE_METHOD("addDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I", - android_media_AudioSystem_addDevicesRoleForCapturePreset), - MAKE_JNI_NATIVE_METHOD("removeDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I", - android_media_AudioSystem_removeDevicesRoleForCapturePreset), - MAKE_AUDIO_SYSTEM_METHOD(clearDevicesRoleForCapturePreset), - MAKE_JNI_NATIVE_METHOD("getDevicesForRoleAndCapturePreset", "(IILjava/util/List;)I", - android_media_AudioSystem_getDevicesForRoleAndCapturePreset), - MAKE_JNI_NATIVE_METHOD("getDevicesForAttributes", - "(Landroid/media/AudioAttributes;[Landroid/media/" - "AudioDeviceAttributes;Z)I", - android_media_AudioSystem_getDevicesForAttributes), - MAKE_JNI_NATIVE_METHOD("setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I", - android_media_AudioSystem_setUserIdDeviceAffinities), - MAKE_AUDIO_SYSTEM_METHOD(removeUserIdDeviceAffinities), - MAKE_AUDIO_SYSTEM_METHOD(setCurrentImeUid), - MAKE_JNI_NATIVE_METHOD("setVibratorInfos", "(Ljava/util/List;)I", - android_media_AudioSystem_setVibratorInfos), - MAKE_JNI_NATIVE_METHOD("nativeGetSpatializer", - "(Landroid/media/INativeSpatializerCallback;)Landroid/os/IBinder;", - android_media_AudioSystem_getSpatializer), - MAKE_JNI_NATIVE_METHOD("canBeSpatialized", - "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;" - "[Landroid/media/AudioDeviceAttributes;)Z", - android_media_AudioSystem_canBeSpatialized), - MAKE_JNI_NATIVE_METHOD("nativeGetSoundDose", - "(Landroid/media/ISoundDoseCallback;)Landroid/os/IBinder;", - android_media_AudioSystem_nativeGetSoundDose), - MAKE_JNI_NATIVE_METHOD("getDirectPlaybackSupport", - "(Landroid/media/AudioFormat;Landroid/media/AudioAttributes;)I", - android_media_AudioSystem_getDirectPlaybackSupport), - MAKE_JNI_NATIVE_METHOD("getDirectProfilesForAttributes", - "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I", - android_media_AudioSystem_getDirectProfilesForAttributes), - MAKE_JNI_NATIVE_METHOD("getSupportedMixerAttributes", "(ILjava/util/List;)I", - android_media_AudioSystem_getSupportedMixerAttributes), - MAKE_JNI_NATIVE_METHOD("setPreferredMixerAttributes", - "(Landroid/media/AudioAttributes;IILandroid/media/" - "AudioMixerAttributes;)I", - android_media_AudioSystem_setPreferredMixerAttributes), - MAKE_JNI_NATIVE_METHOD("getPreferredMixerAttributes", - "(Landroid/media/AudioAttributes;ILjava/util/List;)I", - android_media_AudioSystem_getPreferredMixerAttributes), - MAKE_JNI_NATIVE_METHOD("clearPreferredMixerAttributes", - "(Landroid/media/AudioAttributes;II)I", - android_media_AudioSystem_clearPreferredMixerAttributes), - MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency), - MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled), - MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled), - MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange", - "(Ljava/lang/String;Ljava/lang/Runnable;)J", - android_media_AudioSystem_listenForSystemPropertyChange), - MAKE_JNI_NATIVE_METHOD("triggerSystemPropertyUpdate", - "(J)V", - android_media_AudioSystem_triggerSystemPropertyUpdate), - - }; +static const JNINativeMethod gMethods[] = { + MAKE_AUDIO_SYSTEM_METHOD(setParameters), + MAKE_AUDIO_SYSTEM_METHOD(getParameters), + MAKE_AUDIO_SYSTEM_METHOD(muteMicrophone), + MAKE_AUDIO_SYSTEM_METHOD(isMicrophoneMuted), + MAKE_AUDIO_SYSTEM_METHOD(isStreamActive), + MAKE_AUDIO_SYSTEM_METHOD(isStreamActiveRemotely), + MAKE_AUDIO_SYSTEM_METHOD(isSourceActive), + MAKE_AUDIO_SYSTEM_METHOD(newAudioSessionId), + MAKE_AUDIO_SYSTEM_METHOD(newAudioPlayerId), + MAKE_AUDIO_SYSTEM_METHOD(newAudioRecorderId), + MAKE_JNI_NATIVE_METHOD("setDeviceConnectionState", "(ILandroid/os/Parcel;I)I", + android_media_AudioSystem_setDeviceConnectionState), + MAKE_AUDIO_SYSTEM_METHOD(getDeviceConnectionState), + MAKE_AUDIO_SYSTEM_METHOD(handleDeviceConfigChange), + MAKE_AUDIO_SYSTEM_METHOD(setPhoneState), + MAKE_AUDIO_SYSTEM_METHOD(setForceUse), + MAKE_AUDIO_SYSTEM_METHOD(getForceUse), + MAKE_AUDIO_SYSTEM_METHOD(setDeviceAbsoluteVolumeEnabled), + MAKE_AUDIO_SYSTEM_METHOD(initStreamVolume), + MAKE_AUDIO_SYSTEM_METHOD(setStreamVolumeIndex), + MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeIndex), + MAKE_JNI_NATIVE_METHOD("setVolumeIndexForAttributes", + "(Landroid/media/AudioAttributes;IZI)I", + android_media_AudioSystem_setVolumeIndexForAttributes), + MAKE_JNI_NATIVE_METHOD("getVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;I)I", + android_media_AudioSystem_getVolumeIndexForAttributes), + MAKE_JNI_NATIVE_METHOD("getMinVolumeIndexForAttributes", + "(Landroid/media/AudioAttributes;)I", + android_media_AudioSystem_getMinVolumeIndexForAttributes), + MAKE_JNI_NATIVE_METHOD("getMaxVolumeIndexForAttributes", + "(Landroid/media/AudioAttributes;)I", + android_media_AudioSystem_getMaxVolumeIndexForAttributes), + MAKE_AUDIO_SYSTEM_METHOD(setMasterVolume), + MAKE_AUDIO_SYSTEM_METHOD(getMasterVolume), + MAKE_AUDIO_SYSTEM_METHOD(setMasterMute), + MAKE_AUDIO_SYSTEM_METHOD(getMasterMute), + MAKE_AUDIO_SYSTEM_METHOD(setMasterMono), + MAKE_AUDIO_SYSTEM_METHOD(getMasterMono), + MAKE_AUDIO_SYSTEM_METHOD(setMasterBalance), + MAKE_AUDIO_SYSTEM_METHOD(getMasterBalance), + MAKE_AUDIO_SYSTEM_METHOD(getPrimaryOutputSamplingRate), + MAKE_AUDIO_SYSTEM_METHOD(getPrimaryOutputFrameCount), + MAKE_AUDIO_SYSTEM_METHOD(getOutputLatency), + MAKE_AUDIO_SYSTEM_METHOD(setLowRamDevice), + MAKE_AUDIO_SYSTEM_METHOD(checkAudioFlinger), + MAKE_JNI_NATIVE_METHOD("setAudioFlingerBinder", "(Landroid/os/IBinder;)V", + android_media_AudioSystem_setAudioFlingerBinder), + MAKE_JNI_NATIVE_METHOD("listAudioPorts", "(Ljava/util/ArrayList;[I)I", + android_media_AudioSystem_listAudioPorts), + MAKE_JNI_NATIVE_METHOD("getSupportedDeviceTypes", "(ILandroid/util/IntArray;)I", + android_media_AudioSystem_getSupportedDeviceTypes), + MAKE_JNI_NATIVE_METHOD("createAudioPatch", + "([Landroid/media/AudioPatch;[Landroid/media/" + "AudioPortConfig;[Landroid/media/AudioPortConfig;)I", + android_media_AudioSystem_createAudioPatch), + MAKE_JNI_NATIVE_METHOD("releaseAudioPatch", "(Landroid/media/AudioPatch;)I", + android_media_AudioSystem_releaseAudioPatch), + MAKE_JNI_NATIVE_METHOD("listAudioPatches", "(Ljava/util/ArrayList;[I)I", + android_media_AudioSystem_listAudioPatches), + MAKE_JNI_NATIVE_METHOD("setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I", + android_media_AudioSystem_setAudioPortConfig), + MAKE_JNI_NATIVE_METHOD("startAudioSource", + "(Landroid/media/AudioPortConfig;Landroid/media/AudioAttributes;)I", + android_media_AudioSystem_startAudioSource), + MAKE_AUDIO_SYSTEM_METHOD(stopAudioSource), + MAKE_AUDIO_SYSTEM_METHOD(getAudioHwSyncForSession), + MAKE_JNI_NATIVE_METHOD("registerPolicyMixes", "(Ljava/util/ArrayList;Z)I", + android_media_AudioSystem_registerPolicyMixes), + MAKE_JNI_NATIVE_METHOD("getRegisteredPolicyMixes", "(Ljava/util/List;)I", + android_media_AudioSystem_getRegisteredPolicyMixes), + MAKE_JNI_NATIVE_METHOD("updatePolicyMixes", + "([Landroid/media/audiopolicy/AudioMix;[Landroid/media/audiopolicy/" + "AudioMixingRule;)I", + android_media_AudioSystem_updatePolicyMixes), + MAKE_JNI_NATIVE_METHOD("setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I", + android_media_AudioSystem_setUidDeviceAffinities), + MAKE_AUDIO_SYSTEM_METHOD(removeUidDeviceAffinities), + MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_dynamic_policy_callback", + android_media_AudioSystem_registerDynPolicyCallback), + MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_recording_callback", + android_media_AudioSystem_registerRecordingCallback), + MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_routing_callback", + android_media_AudioSystem_registerRoutingCallback), + MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_vol_range_init_req_callback", + android_media_AudioSystem_registerVolRangeInitReqCallback), + MAKE_AUDIO_SYSTEM_METHOD(systemReady), + MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeDB), + MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_get_offload_support", + android_media_AudioSystem_getOffloadSupport), + MAKE_JNI_NATIVE_METHOD("getMicrophones", "(Ljava/util/ArrayList;)I", + android_media_AudioSystem_getMicrophones), + MAKE_JNI_NATIVE_METHOD("getSurroundFormats", "(Ljava/util/Map;)I", + android_media_AudioSystem_getSurroundFormats), + MAKE_JNI_NATIVE_METHOD("getReportedSurroundFormats", "(Ljava/util/ArrayList;)I", + android_media_AudioSystem_getReportedSurroundFormats), + MAKE_AUDIO_SYSTEM_METHOD(setSurroundFormatEnabled), + MAKE_AUDIO_SYSTEM_METHOD(setAssistantServicesUids), + MAKE_AUDIO_SYSTEM_METHOD(setActiveAssistantServicesUids), + MAKE_AUDIO_SYSTEM_METHOD(setA11yServicesUids), + MAKE_AUDIO_SYSTEM_METHOD(isHapticPlaybackSupported), + MAKE_AUDIO_SYSTEM_METHOD(isUltrasoundSupported), + MAKE_JNI_NATIVE_METHOD( + "getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I", + android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia), + MAKE_AUDIO_SYSTEM_METHOD(setSupportedSystemUsages), + MAKE_AUDIO_SYSTEM_METHOD(setAllowedCapturePolicy), + MAKE_AUDIO_SYSTEM_METHOD(setRttEnabled), + MAKE_AUDIO_SYSTEM_METHOD(setAudioHalPids), + MAKE_AUDIO_SYSTEM_METHOD(isCallScreeningModeSupported), + MAKE_JNI_NATIVE_METHOD("setDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I", + android_media_AudioSystem_setDevicesRoleForStrategy), + MAKE_JNI_NATIVE_METHOD("removeDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I", + android_media_AudioSystem_removeDevicesRoleForStrategy), + MAKE_AUDIO_SYSTEM_METHOD(clearDevicesRoleForStrategy), + MAKE_JNI_NATIVE_METHOD("getDevicesForRoleAndStrategy", "(IILjava/util/List;)I", + android_media_AudioSystem_getDevicesForRoleAndStrategy), + MAKE_JNI_NATIVE_METHOD("setDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I", + android_media_AudioSystem_setDevicesRoleForCapturePreset), + MAKE_JNI_NATIVE_METHOD("addDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I", + android_media_AudioSystem_addDevicesRoleForCapturePreset), + MAKE_JNI_NATIVE_METHOD("removeDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I", + android_media_AudioSystem_removeDevicesRoleForCapturePreset), + MAKE_AUDIO_SYSTEM_METHOD(clearDevicesRoleForCapturePreset), + MAKE_JNI_NATIVE_METHOD("getDevicesForRoleAndCapturePreset", "(IILjava/util/List;)I", + android_media_AudioSystem_getDevicesForRoleAndCapturePreset), + MAKE_JNI_NATIVE_METHOD("getDevicesForAttributes", + "(Landroid/media/AudioAttributes;[Landroid/media/" + "AudioDeviceAttributes;Z)I", + android_media_AudioSystem_getDevicesForAttributes), + MAKE_JNI_NATIVE_METHOD("setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I", + android_media_AudioSystem_setUserIdDeviceAffinities), + MAKE_AUDIO_SYSTEM_METHOD(removeUserIdDeviceAffinities), + MAKE_AUDIO_SYSTEM_METHOD(setCurrentImeUid), + MAKE_JNI_NATIVE_METHOD("setVibratorInfos", "(Ljava/util/List;)I", + android_media_AudioSystem_setVibratorInfos), + MAKE_JNI_NATIVE_METHOD("nativeGetSpatializer", + "(Landroid/media/INativeSpatializerCallback;)Landroid/os/IBinder;", + android_media_AudioSystem_getSpatializer), + MAKE_JNI_NATIVE_METHOD("canBeSpatialized", + "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;" + "[Landroid/media/AudioDeviceAttributes;)Z", + android_media_AudioSystem_canBeSpatialized), + MAKE_JNI_NATIVE_METHOD("nativeGetSoundDose", + "(Landroid/media/ISoundDoseCallback;)Landroid/os/IBinder;", + android_media_AudioSystem_nativeGetSoundDose), + MAKE_JNI_NATIVE_METHOD("getDirectPlaybackSupport", + "(Landroid/media/AudioFormat;Landroid/media/AudioAttributes;)I", + android_media_AudioSystem_getDirectPlaybackSupport), + MAKE_JNI_NATIVE_METHOD("getDirectProfilesForAttributes", + "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I", + android_media_AudioSystem_getDirectProfilesForAttributes), + MAKE_JNI_NATIVE_METHOD("getSupportedMixerAttributes", "(ILjava/util/List;)I", + android_media_AudioSystem_getSupportedMixerAttributes), + MAKE_JNI_NATIVE_METHOD("setPreferredMixerAttributes", + "(Landroid/media/AudioAttributes;IILandroid/media/" + "AudioMixerAttributes;)I", + android_media_AudioSystem_setPreferredMixerAttributes), + MAKE_JNI_NATIVE_METHOD("getPreferredMixerAttributes", + "(Landroid/media/AudioAttributes;ILjava/util/List;)I", + android_media_AudioSystem_getPreferredMixerAttributes), + MAKE_JNI_NATIVE_METHOD("clearPreferredMixerAttributes", + "(Landroid/media/AudioAttributes;II)I", + android_media_AudioSystem_clearPreferredMixerAttributes), + MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency), + MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled), + MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled), + MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange", + "(Ljava/lang/String;Ljava/lang/Runnable;)J", + android_media_AudioSystem_listenForSystemPropertyChange), + MAKE_JNI_NATIVE_METHOD("triggerSystemPropertyUpdate", "(J)V", + android_media_AudioSystem_triggerSystemPropertyUpdate), +}; static const JNINativeMethod gEventHandlerMethods[] = {MAKE_JNI_NATIVE_METHOD("native_setup", "(Ljava/lang/Object;)V", diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index a939d9274956..755704a5ad91 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -115,6 +115,7 @@ static struct { jfieldID supportedDisplayModes; jfieldID activeDisplayModeId; jfieldID renderFrameRate; + jfieldID hasArrSupport; jfieldID supportedColorModes; jfieldID activeColorMode; jfieldID hdrCapabilities; @@ -1453,7 +1454,7 @@ static jobject nativeGetDynamicDisplayInfo(JNIEnv* env, jclass clazz, jlong disp env->SetIntField(object, gDynamicDisplayInfoClassInfo.activeDisplayModeId, info.activeDisplayModeId); env->SetFloatField(object, gDynamicDisplayInfoClassInfo.renderFrameRate, info.renderFrameRate); - + env->SetBooleanField(object, gDynamicDisplayInfoClassInfo.hasArrSupport, info.hasArrSupport); jintArray colorModesArray = env->NewIntArray(info.supportedColorModes.size()); if (colorModesArray == NULL) { jniThrowException(env, "java/lang/OutOfMemoryError", NULL); @@ -2641,6 +2642,8 @@ int register_android_view_SurfaceControl(JNIEnv* env) GetFieldIDOrDie(env, dynamicInfoClazz, "activeDisplayModeId", "I"); gDynamicDisplayInfoClassInfo.renderFrameRate = GetFieldIDOrDie(env, dynamicInfoClazz, "renderFrameRate", "F"); + gDynamicDisplayInfoClassInfo.hasArrSupport = + GetFieldIDOrDie(env, dynamicInfoClazz, "hasArrSupport", "Z"); gDynamicDisplayInfoClassInfo.supportedColorModes = GetFieldIDOrDie(env, dynamicInfoClazz, "supportedColorModes", "[I"); gDynamicDisplayInfoClassInfo.activeColorMode = diff --git a/core/res/res/color-watch-v36/btn_material_filled_background_color.xml b/core/res/res/color-watch-v36/btn_material_filled_background_color.xml deleted file mode 100644 index 8b2afa86986c..000000000000 --- a/core/res/res/color-watch-v36/btn_material_filled_background_color.xml +++ /dev/null @@ -1,23 +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. - --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" - android:alpha="?attr/disabledAlpha" - android:color="?attr/materialColorOnSurface" /> - <item android:state_enabled="true" - android:color="?attr/materialColorPrimary" /> -</selector>
\ No newline at end of file diff --git a/core/res/res/color-watch-v36/btn_material_filled_text_color.xml b/core/res/res/color-watch-v36/btn_material_filled_text_color.xml deleted file mode 100644 index cefc9121b7a4..000000000000 --- a/core/res/res/color-watch-v36/btn_material_filled_text_color.xml +++ /dev/null @@ -1,23 +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. - --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" - android:alpha="?attr/primaryContentAlpha" - android:color="?attr/materialColorOnSurface" /> - <item android:state_enabled="true" - android:color="?attr/materialColorOnPrimary" /> -</selector>
\ No newline at end of file diff --git a/core/res/res/color-watch-v36/btn_material_filled_tonal_background_color.xml b/core/res/res/color-watch-v36/btn_material_filled_tonal_background_color.xml deleted file mode 100644 index eaf9e7d50bbd..000000000000 --- a/core/res/res/color-watch-v36/btn_material_filled_tonal_background_color.xml +++ /dev/null @@ -1,23 +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. - --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" - android:alpha="?attr/disabledAlpha" - android:color="?attr/materialColorOnSurface" /> - <item android:state_enabled="true" - android:color="?attr/materialColorSurfaceContainer" /> -</selector>
\ No newline at end of file diff --git a/core/res/res/color-watch-v36/btn_material_filled_tonal_text_color.xml b/core/res/res/color-watch-v36/btn_material_filled_tonal_text_color.xml deleted file mode 100644 index 94e50fbe2533..000000000000 --- a/core/res/res/color-watch-v36/btn_material_filled_tonal_text_color.xml +++ /dev/null @@ -1,23 +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. - --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" - android:alpha="?attr/primaryContentAlpha" - android:color="?attr/materialColorOnSurface" /> - <item android:state_enabled="true" - android:color="?attr/materialColorOnSurface" /> -</selector>
\ No newline at end of file diff --git a/core/res/res/drawable-watch-v36/btn_background_material_filled.xml b/core/res/res/drawable-watch-v36/btn_background_material_filled.xml deleted file mode 100644 index 0029de14e34a..000000000000 --- a/core/res/res/drawable-watch-v36/btn_background_material_filled.xml +++ /dev/null @@ -1,28 +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. - --> - -<ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="?attr/colorControlHighlight"> - <item> - <shape android:shape="rectangle"> - <solid android:color="@color/btn_material_filled_background_color"/> - <corners android:radius="?android:attr/buttonCornerRadius"/> - <size - android:width="@dimen/btn_material_width" - android:height="@dimen/btn_material_height" /> - </shape> - </item> -</ripple>
\ No newline at end of file diff --git a/core/res/res/drawable-watch-v36/btn_background_material_filled_tonal.xml b/core/res/res/drawable-watch-v36/btn_background_material_filled_tonal.xml deleted file mode 100644 index 105f077cd841..000000000000 --- a/core/res/res/drawable-watch-v36/btn_background_material_filled_tonal.xml +++ /dev/null @@ -1,28 +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. - --> - -<ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="?attr/colorControlHighlight"> - <item> - <shape android:shape="rectangle"> - <solid android:color="@color/btn_material_filled_tonal_background_color"/> - <corners android:radius="?android:attr/buttonCornerRadius"/> - <size - android:width="@dimen/btn_material_width" - android:height="@dimen/btn_material_height" /> - </shape> - </item> -</ripple>
\ No newline at end of file diff --git a/core/res/res/values-watch-v36/colors.xml b/core/res/res/values-watch-v36/colors.xml deleted file mode 100644 index 4bc2a66fa206..000000000000 --- a/core/res/res/values-watch-v36/colors.xml +++ /dev/null @@ -1,18 +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. - --> -<!-- TODO(b/372524566): update color token's value to match material3 design. --> -<resources> -</resources>
\ No newline at end of file diff --git a/core/res/res/values-watch-v36/config.xml b/core/res/res/values-watch-v36/config.xml deleted file mode 100644 index c8f347afb318..000000000000 --- a/core/res/res/values-watch-v36/config.xml +++ /dev/null @@ -1,20 +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. - --> - -<resources> - <!-- Overrides system value --> - <dimen name="config_buttonCornerRadius">26dp</dimen> -</resources> diff --git a/core/res/res/values-watch-v36/dimens_material.xml b/core/res/res/values-watch-v36/dimens_material.xml deleted file mode 100644 index ad3c1a3ef3a1..000000000000 --- a/core/res/res/values-watch-v36/dimens_material.xml +++ /dev/null @@ -1,28 +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. - --> -<resources> - <!-- values for material3 button --> - <dimen name="btn_material_width">172dp</dimen> - <dimen name="btn_material_height">52dp</dimen> - <dimen name="btn_horizontal_edge_padding">14dp</dimen> - <dimen name="btn_drawable_padding">6dp</dimen> - <dimen name="btn_lineHeight">18sp</dimen> - <dimen name="btn_textSize">15sp</dimen> - - <!-- Opacity factor for disabled material3 widget --> - <dimen name="disabled_alpha_device_default">0.12</dimen> - <dimen name="primary_content_alpha_device_default">0.38</dimen> -</resources> diff --git a/core/res/res/values-watch-v36/styles_material.xml b/core/res/res/values-watch-v36/styles_material.xml deleted file mode 100644 index 32a22bb755cb..000000000000 --- a/core/res/res/values-watch-v36/styles_material.xml +++ /dev/null @@ -1,58 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<resources> - <!-- Button Styles --> - <!-- Material Button - Filled --> - <style name="Widget.DeviceDefault.Button.Filled" parent="Widget.DeviceDefault.Button"> - <item name="android:background">@drawable/btn_background_material_filled</item> - <item name="textAppearance">@style/TextAppearance.Widget.Button.Material.Filled</item> - </style> - - <!-- Material Button - Filled Tonal(Override system default button styles) --> - <style name="Widget.DeviceDefault.Button"> - <item name="background">@drawable/btn_background_material_filled_tonal</item> - <item name="textAppearance">@style/TextAppearance.Widget.Button.Material</item> - <item name="minHeight">@dimen/btn_material_height</item> - <item name="maxWidth">@dimen/btn_material_width</item> - <item name="android:paddingStart">@dimen/btn_horizontal_edge_padding</item> - <item name="android:paddingEnd">@dimen/btn_horizontal_edge_padding</item> - <item name="android:drawablePadding">@dimen/btn_drawable_padding</item> - <item name="android:maxLines">2</item> - <item name="android:ellipsize">end</item> - <item name="android:breakStrategy">simple</item> - <item name="stateListAnimator">@anim/button_state_list_anim_material</item> - <item name="focusable">true</item> - <item name="clickable">true</item> - <item name="gravity">center_vertical</item> - </style> - - <!-- Text Styles --> - <!-- TextAppearance for Material Button - Filled --> - <style name="TextAppearance.Widget.Button.Material.Filled" parent="TextAppearance.Widget.Button.Material"> - <item name="textColor">@color/btn_material_filled_text_color</item> - </style> - - <!-- TextAppearance for Material Button - Filled Tonal --> - <style name="TextAppearance.Widget.Button.Material" parent="TextAppearance.DeviceDefault"> - <item name="android:fontFamily">font-family-flex-device-default</item> - <item name="android:fontVariationSettings">"'wdth' 90, 'wght' 500, 'ROND' 100, 'opsz' 15, 'GRAD' 0"</item> - <item name="textSize">@dimen/btn_textSize</item> - <item name="textColor">@color/btn_material_filled_tonal_text_color</item> - <item name="lineHeight">@dimen/btn_lineHeight</item> - </style> -</resources>
\ No newline at end of file diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 48e26203fab4..23a09857032c 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -2397,10 +2397,25 @@ public class NotificationTest { public void progressStyle_getProgressMax_returnsSumOfSegmentLength() { final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); progressStyle + .setProgressSegments(List.of(new Notification.ProgressStyle.Segment(15), + new Notification.ProgressStyle.Segment(25))) .addProgressSegment(new Notification.ProgressStyle.Segment(10)) .addProgressSegment(new Notification.ProgressStyle.Segment(20)); - assertThat(progressStyle.getProgressMax()).isEqualTo(30); + assertThat(progressStyle.getProgressMax()).isEqualTo(70); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void progressStyle_getProgressMax_onSetProgressSegments_resets() { + final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); + progressStyle + .addProgressSegment(new Notification.ProgressStyle.Segment(10)) + .addProgressSegment(new Notification.ProgressStyle.Segment(20)) + .setProgressSegments(List.of(new Notification.ProgressStyle.Segment(15), + new Notification.ProgressStyle.Segment(25))); + + assertThat(progressStyle.getProgressMax()).isEqualTo(40); } @Test diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 0621d82747e5..2880ecf835c4 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -16,6 +16,8 @@ package android.widget; +import static android.appwidget.flags.Flags.remoteAdapterConversion; + import static com.android.internal.R.id.pending_intent_tag; import static org.junit.Assert.assertArrayEquals; @@ -282,7 +284,10 @@ public class RemoteViewsTest { widget.addView(view); ListView listView = (ListView) view.findViewById(R.id.list); - listView.onRemoteAdapterConnected(); + + if (!remoteAdapterConversion()) { + listView.onRemoteAdapterConnected(); + } assertNotNull(listView.getAdapter()); top.reapply(mContext, view); @@ -414,6 +419,58 @@ public class RemoteViewsTest { assertNotNull(view.findViewById(R.id.light_background_text)); } + @Test + public void remoteCollectionItemsAdapter_lightBackgroundLayoutFlagSet() { + AppWidgetHostView widget = new AppWidgetHostView(mContext); + RemoteViews container = new RemoteViews(mPackage, R.layout.remote_view_host); + RemoteViews listRemoteViews = new RemoteViews(mPackage, R.layout.remote_views_list); + RemoteViews item = new RemoteViews(mPackage, R.layout.remote_views_text); + item.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text); + listRemoteViews.setRemoteAdapter( + R.id.list, + new RemoteViews.RemoteCollectionItems.Builder().addItem(0, item).build()); + container.addView(R.id.container, listRemoteViews); + + widget.setOnLightBackground(true); + widget.updateAppWidget(container); + + // Populate the list view + ListView listView = (ListView) widget.findViewById(R.id.list); + int measureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); + listView.measure(measureSpec, measureSpec); + listView.layout(0, 0, 100, 100); + + // Picks the light background layout id for the item + assertNotNull(listView.getChildAt(0).findViewById(R.id.light_background_text)); + assertNull(listView.getChildAt(0).findViewById(R.id.text)); + } + + @Test + public void remoteCollectionItemsAdapter_lightBackgroundLayoutFlagNotSet() { + AppWidgetHostView widget = new AppWidgetHostView(mContext); + RemoteViews container = new RemoteViews(mPackage, R.layout.remote_view_host); + RemoteViews listRemoteViews = new RemoteViews(mPackage, R.layout.remote_views_list); + RemoteViews item = new RemoteViews(mPackage, R.layout.remote_views_text); + item.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text); + listRemoteViews.setRemoteAdapter( + R.id.list, + new RemoteViews.RemoteCollectionItems.Builder().addItem(0, item).build()); + container.addView(R.id.container, listRemoteViews); + + widget.setOnLightBackground(false); + widget.updateAppWidget(container); + + // Populate the list view + ListView listView = (ListView) widget.findViewById(R.id.list); + int measureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); + listView.measure(measureSpec, measureSpec); + listView.layout(0, 0, 100, 100); + + // Does not pick the light background layout id for the item + assertNotNull(listView.getChildAt(0).findViewById(R.id.text)); + assertNull(listView.getChildAt(0).findViewById(R.id.light_background_text)); + } + private RemoteViews createViewChained(int depth, String... texts) { RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host); diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java index 61cd1c323ac1..5a2a723cacee 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java @@ -61,11 +61,30 @@ public class SplitScreenConstants { @IntDef(prefix = {"SPLIT_POSITION_"}, value = { SPLIT_POSITION_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT, - SPLIT_POSITION_BOTTOM_OR_RIGHT + SPLIT_POSITION_BOTTOM_OR_RIGHT, }) public @interface SplitPosition { } + // These SPLIT_INDEX constants will be used in the same way as the above SPLIT_POSITION ints, + // but scalable to n apps. Eventually, SPLIT_POSITION can be deprecated and only the below + // will be used. + public static final int SPLIT_INDEX_UNDEFINED = -1; + public static final int SPLIT_INDEX_0 = 0; + public static final int SPLIT_INDEX_1 = 1; + public static final int SPLIT_INDEX_2 = 2; + public static final int SPLIT_INDEX_3 = 3; + + @IntDef(prefix = {"SPLIT_INDEX_"}, value = { + SPLIT_INDEX_UNDEFINED, + SPLIT_INDEX_0, + SPLIT_INDEX_1, + SPLIT_INDEX_2, + SPLIT_INDEX_3 + }) + public @interface SplitIndex { + } + /** * A snap target for two apps, where the split is 33-66. With FLAG_ENABLE_FLEXIBLE_SPLIT, * only used on tablets. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index a8a8c2a80974..eb9cdab6e856 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1246,9 +1246,12 @@ public class BubbleController implements ConfigurationChangeListener, */ public void dragBubbleToDismiss(String bubbleKey, long timestamp) { String selectedBubbleKey = mBubbleData.getSelectedBubbleKey(); - if (mBubbleData.hasAnyBubbleWithKey(bubbleKey)) { + Bubble bubbleToDismiss = mBubbleData.getAnyBubbleWithkey(bubbleKey); + if (bubbleToDismiss != null) { mBubbleData.dismissBubbleWithKey( bubbleKey, Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, timestamp); + mLogger.log(bubbleToDismiss, + BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_BUBBLE); } if (mBubbleData.hasBubbles()) { // We still have bubbles, if we dragged an individual bubble to dismiss we were expanded @@ -1979,11 +1982,15 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void addBubble(Bubble addedBubble) { + // Only log metrics event + mLogger.log(addedBubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED); // Nothing to do for adds, these are handled by launcher / in the bubble bar. } @Override public void updateBubble(Bubble updatedBubble) { + // Only log metrics event + mLogger.log(updatedBubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_UPDATED); // Nothing to do for updates, these are handled by launcher / in the bubble bar. } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java index 1abe11998500..6d757d26a9bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java @@ -16,7 +16,6 @@ package com.android.wm.shell.bubbles; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.FrameworkStatsLog; @@ -33,7 +32,6 @@ public class BubbleLogger { /** * Bubble UI event. */ - @VisibleForTesting public enum Event implements UiEventLogger.UiEventEnum { // region bubble events diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java index 9f100facc163..0b2b3e7c41c8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java @@ -19,9 +19,11 @@ package com.android.wm.shell.common.split; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_MINIMIZE; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE; @@ -33,6 +35,9 @@ import android.graphics.Rect; import androidx.annotation.Nullable; +import com.android.wm.shell.Flags; +import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition; + import java.util.ArrayList; /** @@ -79,7 +84,9 @@ public class DividerSnapAlgorithm { private final int mTaskHeightInMinimizedMode; private final float mFixedRatio; /** Allows split ratios to calculated dynamically instead of using {@link #mFixedRatio}. */ - private final boolean mAllowFlexibleSplitRatios; + private final boolean mCalculateRatiosBasedOnAvailableSpace; + /** Allows split ratios that go offscreen (a.k.a. "flexible split") */ + private final boolean mAllowOffscreenRatios; private final boolean mIsHorizontalDivision; /** The first target which is still splitting the screen */ @@ -119,8 +126,11 @@ public class DividerSnapAlgorithm { com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1); mMinimalSizeResizableTask = res.getDimensionPixelSize( com.android.internal.R.dimen.default_minimal_size_resizable_task); - mAllowFlexibleSplitRatios = res.getBoolean( + mCalculateRatiosBasedOnAvailableSpace = res.getBoolean( com.android.internal.R.bool.config_flexibleSplitRatios); + // If this is a small screen or a foldable, use offscreen ratios + mAllowOffscreenRatios = + Flags.enableFlexibleTwoAppSplit() && SplitScreenUtils.allowOffscreenRatios(res); mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize( com.android.internal.R.dimen.task_height_of_minimized_mode) : 0; calculateTargets(isHorizontalDivision, dockSide); @@ -233,6 +243,11 @@ public class DividerSnapAlgorithm { return mFirstSplitTarget.position < position && position < mLastSplitTarget.position; } + /** Returns if we are currently on a device/screen that supports split apps going offscreen. */ + public boolean areOffscreenRatiosSupported() { + return mAllowOffscreenRatios; + } + private SnapTarget snap(int position, boolean hardDismiss) { if (shouldApplyFreeSnapMode(position)) { return new SnapTarget(position, SNAP_TO_NONE); @@ -283,10 +298,14 @@ public class DividerSnapAlgorithm { private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition, int bottomPosition, int dividerMax) { - maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_2_33_66); + @PersistentSnapPosition int firstTarget = + mAllowOffscreenRatios ? SNAP_TO_2_10_90 : SNAP_TO_2_33_66; + @PersistentSnapPosition int lastTarget = + mAllowOffscreenRatios ? SNAP_TO_2_90_10 : SNAP_TO_2_66_33; + maybeAddTarget(topPosition, topPosition - getStartInset(), firstTarget); addMiddleTarget(isHorizontalDivision); maybeAddTarget(bottomPosition, - dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_2_66_33); + dividerMax - getEndInset() - (bottomPosition + mDividerSize), lastTarget); } private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) { @@ -295,7 +314,11 @@ public class DividerSnapAlgorithm { ? mDisplayHeight - mInsets.bottom : mDisplayWidth - mInsets.right; int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2; - if (mAllowFlexibleSplitRatios) { + + if (mAllowOffscreenRatios) { + // TODO (b/349828130): This is a placeholder value, real measurements to come + size = (int) (0.3f * (end - start)) - mDividerSize / 2; + } else if (mCalculateRatiosBasedOnAvailableSpace) { size = Math.max(size, mMinimalSizeResizableTask); } int topPosition = start + size; @@ -324,7 +347,7 @@ public class DividerSnapAlgorithm { * meets the minimal size requirement. */ private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapPosition) { - if (smallerSize >= mMinimalSizeResizableTask) { + if (smallerSize >= mMinimalSizeResizableTask || mAllowOffscreenRatios) { mTargets.add(new SnapTarget(position, snapPosition)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 83ffaf473999..c9c3aa0dd537 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -30,6 +30,8 @@ import static com.android.wm.shell.shared.animation.Interpolators.DIM_INTERPOLAT import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED; import static com.android.wm.shell.shared.animation.Interpolators.LINEAR; import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -438,12 +440,31 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange dividerBounds.right = dividerBounds.left + mDividerWindowWidth; bounds1.right = position; bounds2.left = bounds1.right + mDividerSize; + + // For flexible split, expand app offscreen as well + if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) { + if (position <= mDividerSnapAlgorithm.getMiddleTarget().position) { + bounds1.left = bounds1.right - bounds2.width(); + } else { + bounds2.right = bounds2.left + bounds1.width(); + } + } + } else { position += mRootBounds.top; dividerBounds.top = position - mDividerInsets; dividerBounds.bottom = dividerBounds.top + mDividerWindowWidth; bounds1.bottom = position; bounds2.top = bounds1.bottom + mDividerSize; + + // For flexible split, expand app offscreen as well + if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) { + if (position <= mDividerSnapAlgorithm.getMiddleTarget().position) { + bounds1.top = bounds1.bottom - bounds2.width(); + } else { + bounds2.bottom = bounds2.top + bounds1.width(); + } + } } DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */); DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */); @@ -669,6 +690,21 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange }); } + /** + * Moves the divider to the other side of the screen. Does nothing if the divider is in the + * center. + * TODO (b/349828130): Currently only supports the two-app case. For n-apps, + * DividerSnapAlgorithm will need to be refactored, and this function will change as well. + */ + public void flingDividerToOtherSide(@PersistentSnapPosition int currentSnapPosition) { + switch (currentSnapPosition) { + case SNAP_TO_2_10_90 -> + snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getLastSplitTarget()); + case SNAP_TO_2_90_10 -> + snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getFirstSplitTarget()); + } + } + @VisibleForTesting void flingDividerPosition(int from, int to, int duration, @Nullable Runnable flingFinishedCallback) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java index bdbcb4635ae8..65bf389f3819 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java @@ -18,6 +18,10 @@ package com.android.wm.shell.common.split; import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_10_45_45; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_45_45_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -38,6 +42,8 @@ import com.android.wm.shell.shared.split.SplitScreenConstants; /** Helper utility class for split screen components to use. */ public class SplitScreenUtils { + private static final int LARGE_SCREEN_MIN_EDGE_DP = 600; + /** Reverse the split position. */ @SplitScreenConstants.SplitPosition public static int reverseSplitPosition(@SplitScreenConstants.SplitPosition int position) { @@ -110,10 +116,9 @@ public class SplitScreenUtils { Configuration config) { // Compare the max bounds sizes as on near-square devices, the insets may result in a // configuration in the other orientation - final boolean isLargeScreen = config.smallestScreenWidthDp >= 600; final Rect maxBounds = config.windowConfiguration.getMaxBounds(); final boolean isLandscape = maxBounds.width() >= maxBounds.height(); - return isLeftRightSplit(allowLeftRightSplitInPortrait, isLargeScreen, isLandscape); + return isLeftRightSplit(allowLeftRightSplitInPortrait, isLargeScreen(config), isLandscape); } /** @@ -128,4 +133,41 @@ public class SplitScreenUtils { return isLandscape; } } + + /** + * Returns whether the current config is a large screen (tablet or unfolded foldable) + */ + public static boolean isLargeScreen(Configuration config) { + return config.smallestScreenWidthDp >= LARGE_SCREEN_MIN_EDGE_DP; + } + + /** + * Returns whether we should allow split ratios to go offscreen or not. If the device is a phone + * or a foldable (either screen), we allow it. + */ + public static boolean allowOffscreenRatios(Resources res) { + return !isLargeScreen(res.getConfiguration()) + || + res.getIntArray(com.android.internal.R.array.config_foldedDeviceStates).length != 0; + } + + /** + * Within a particular split layout, we label the stages numerically: 0, 1, 2... from left to + * right (or top to bottom). This function takes in a stage index (0th, 1st, 2nd...) and a + * PersistentSnapPosition and returns if that particular stage is offscreen in that layout. + */ + public static boolean isPartiallyOffscreen(int stageIndex, + @SplitScreenConstants.PersistentSnapPosition int snapPosition) { + switch(snapPosition) { + case SNAP_TO_2_10_90: + case SNAP_TO_3_10_45_45: + return stageIndex == 0; + case SNAP_TO_2_90_10: + return stageIndex == 1; + case SNAP_TO_3_45_45_10: + return stageIndex == 2; + default: + return false; + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 57a59c946f98..b723337cc894 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -659,6 +659,7 @@ class DesktopTasksController( } val wct = WindowContainerTransaction() + if (!task.isFreeform) addMoveToDesktopChanges(wct, task, displayId) wct.reparent(task.token, displayAreaInfo.token, true /* onTop */) transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) @@ -1245,7 +1246,7 @@ class DesktopTasksController( val bounds = when (newTaskWindowingMode) { WINDOWING_MODE_FREEFORM -> { displayController.getDisplayLayout(callingTask.displayId) - ?.let { getInitialBounds(it, callingTask) } + ?.let { getInitialBounds(it, callingTask, callingTask.displayId) } } WINDOWING_MODE_MULTI_WINDOW -> { Rect() @@ -1311,7 +1312,7 @@ class DesktopTasksController( val displayLayout = displayController.getDisplayLayout(task.displayId) if (displayLayout != null) { val initialBounds = Rect(task.configuration.windowConfiguration.bounds) - cascadeWindow(task, initialBounds, displayLayout) + cascadeWindow(initialBounds, displayLayout, task.displayId) wct.setBounds(task.token, initialBounds) } } @@ -1399,13 +1400,19 @@ class DesktopTasksController( return if (wct.isEmpty) null else wct } + /** + * Apply all changes required when task is first added to desktop. Uses the task's current + * display by default to apply initial bounds and placement relative to the display. + * Use a different [displayId] if the task should be moved to a different display. + */ @VisibleForTesting fun addMoveToDesktopChanges( wct: WindowContainerTransaction, - taskInfo: RunningTaskInfo + taskInfo: RunningTaskInfo, + displayId: Int = taskInfo.displayId, ) { - val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return - val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!! + val displayLayout = displayController.getDisplayLayout(displayId) ?: return + val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)!! val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) { @@ -1414,7 +1421,7 @@ class DesktopTasksController( } else { WINDOWING_MODE_FREEFORM } - val initialBounds = getInitialBounds(displayLayout, taskInfo) + val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId) if (canChangeTaskPosition(taskInfo)) { wct.setBounds(taskInfo.token, initialBounds) @@ -1428,7 +1435,8 @@ class DesktopTasksController( private fun getInitialBounds( displayLayout: DisplayLayout, - taskInfo: RunningTaskInfo + taskInfo: RunningTaskInfo, + displayId: Int, ): Rect { val bounds = if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue) { calculateInitialBounds(displayLayout, taskInfo) @@ -1437,7 +1445,7 @@ class DesktopTasksController( } if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) { - cascadeWindow(taskInfo, bounds, displayLayout) + cascadeWindow(bounds, displayLayout, displayId) } return bounds } @@ -1466,11 +1474,11 @@ class DesktopTasksController( } } - private fun cascadeWindow(task: TaskInfo, bounds: Rect, displayLayout: DisplayLayout) { + private fun cascadeWindow(bounds: Rect, displayLayout: DisplayLayout, displayId: Int) { val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) - val activeTasks = taskRepository.getActiveNonMinimizedOrderedTasks(task.displayId) + val activeTasks = taskRepository.getActiveNonMinimizedOrderedTasks(displayId) activeTasks.firstOrNull()?.let { activeTask -> shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let { cascadeWindow(context.resources, stableBounds, @@ -2069,6 +2077,12 @@ class DesktopTasksController( c.removeDesktop(displayId) } } + + override fun moveToExternalDisplay(taskId: Int) { + executeRemoteCallWithTaskPermission(controller, "moveTaskToExternalDisplay") { c -> + c.moveToNextDisplay(taskId) + } + } } private fun logV(msg: String, vararg arguments: Any?) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index 8e264b2410f7..34c2f1e760a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -648,7 +648,13 @@ sealed class DragToDesktopTransitionHandler( state.startAborted = true // The start-transition (DRAG_HOLD) is aborted, cancel its jank interaction. interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) - } else if (state.cancelTransitionToken != transition) { + } else if (state.cancelTransitionToken == transition) { + state.draggedTaskChange?.leash?.let { + state.startTransitionFinishTransaction?.show(it) + } + state.startTransitionFinishCb?.onTransitionFinished(null /* wct */) + clearState() + } else { // This transition being aborted is neither the start, nor the cancel transition, so // it must be the finish transition (DRAG_RELEASE); cancel its jank interaction. interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE) @@ -863,7 +869,8 @@ sealed class DragToDesktopTransitionHandler( companion object { /** The duration of the animation to commit or cancel the drag-to-desktop gesture. */ - internal const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index 86351e364cdd..c27813de5358 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -52,4 +52,7 @@ interface IDesktopMode { /** Remove desktop on the given display */ oneway void removeDesktop(int displayId); + + /** Move a task with given `taskId` to external display */ + void moveToExternalDisplay(int taskId); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 268c3a20a41a..537ef182bb04 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -350,7 +350,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } cancelPhysicsAnimation(); mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */); - // mPipTaskOrganizer.removePip(); + mPipScheduler.removePipAfterAnimation(); } /** Sets the movement bounds to use to constrain PIP position animations. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index d4f190ebd2a2..fbbf6f3596d0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -19,81 +19,40 @@ package com.android.wm.shell.pip2.phone; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.graphics.Matrix; import android.graphics.Rect; import android.view.SurfaceControl; import android.window.WindowContainerTransaction; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; -import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; +import com.android.wm.shell.pip2.animation.PipAlphaAnimator; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Scheduler for Shell initiated PiP transitions and animations. */ public class PipScheduler { private static final String TAG = PipScheduler.class.getSimpleName(); - private static final String BROADCAST_FILTER = PipScheduler.class.getCanonicalName(); private final Context mContext; private final PipBoundsState mPipBoundsState; private final ShellExecutor mMainExecutor; private final PipTransitionState mPipTransitionState; - private PipSchedulerReceiver mSchedulerReceiver; private PipTransitionController mPipTransitionController; + private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + mSurfaceControlTransactionFactory; @Nullable private Runnable mUpdateMovementBoundsRunnable; - /** - * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell. - * This is used for a broadcast receiver to resolve intents. This should be removed once - * there is an equivalent of PipTouchHandler and PipResizeGestureHandler for PiP2. - */ - private static final int PIP_EXIT_VIA_EXPAND_CODE = 0; - private static final int PIP_DOUBLE_TAP = 1; - - @IntDef(value = { - PIP_EXIT_VIA_EXPAND_CODE, - PIP_DOUBLE_TAP - }) - @Retention(RetentionPolicy.SOURCE) - @interface PipUserJourneyCode {} - - /** - * A temporary broadcast receiver to initiate PiP CUJs. - */ - private class PipSchedulerReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - int userJourneyCode = intent.getIntExtra("cuj_code_extra", 0); - switch (userJourneyCode) { - case PIP_EXIT_VIA_EXPAND_CODE: - scheduleExitPipViaExpand(); - break; - case PIP_DOUBLE_TAP: - scheduleDoubleTapToResize(); - break; - default: - throw new IllegalStateException("unexpected CUJ code=" + userJourneyCode); - } - } - } - public PipScheduler(Context context, PipBoundsState pipBoundsState, ShellExecutor mainExecutor, @@ -103,12 +62,8 @@ public class PipScheduler { mMainExecutor = mainExecutor; mPipTransitionState = pipTransitionState; - if (PipUtils.isPip2ExperimentEnabled()) { - // temporary broadcast receiver to initiate exit PiP via expand - mSchedulerReceiver = new PipSchedulerReceiver(); - ContextCompat.registerReceiver(mContext, mSchedulerReceiver, - new IntentFilter(BROADCAST_FILTER), ContextCompat.RECEIVER_EXPORTED); - } + mSurfaceControlTransactionFactory = + new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); } ShellExecutor getMainExecutor() { @@ -133,6 +88,18 @@ public class PipScheduler { return wct; } + @Nullable + private WindowContainerTransaction getRemovePipTransaction() { + if (mPipTransitionState.mPipTaskToken == null) { + return null; + } + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setBounds(mPipTransitionState.mPipTaskToken, null); + wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED); + wct.reorder(mPipTransitionState.mPipTaskToken, false); + return wct; + } + /** * Schedules exit PiP via expand transition. */ @@ -146,10 +113,26 @@ public class PipScheduler { } } - /** - * Schedules resize PiP via double tap. - */ - public void scheduleDoubleTapToResize() {} + // TODO: Optimize this by running the animation as part of the transition + /** Runs remove PiP animation and schedules remove PiP transition after the animation ends. */ + public void removePipAfterAnimation() { + SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); + PipAlphaAnimator animator = new PipAlphaAnimator(mContext, + mPipTransitionState.mPinnedTaskLeash, tx, PipAlphaAnimator.FADE_OUT); + animator.setAnimationEndCallback(this::scheduleRemovePipImmediately); + animator.start(); + } + + /** Schedules remove PiP transition. */ + private void scheduleRemovePipImmediately() { + WindowContainerTransaction wct = getRemovePipTransaction(); + if (wct != null) { + mMainExecutor.execute(() -> { + mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct, + null /* destinationBounds */); + }); + } + } /** * Animates resizing of the pinned stack given the duration. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index c58de2c3512a..373ec806c40c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java @@ -90,9 +90,10 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, if (mPictureInPictureParams.equals(params)) { return; } - if (PipUtils.remoteActionsChanged(params.getActions(), mPictureInPictureParams.getActions()) + if (params != null && (PipUtils.remoteActionsChanged(params.getActions(), + mPictureInPictureParams.getActions()) || !PipUtils.remoteActionsMatch(params.getCloseAction(), - mPictureInPictureParams.getCloseAction())) { + mPictureInPictureParams.getCloseAction()))) { for (PipParamsChangedCallback listener : mPipParamsChangedListeners) { listener.onActionsChanged(params.getActions(), params.getCloseAction()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 029f001401c5..a4a7973ef4bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -582,7 +582,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha return true; } - /* if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting()) && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) { // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event @@ -599,6 +598,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha return true; } + /* // Ignore the motion event When the entry animation is waiting to be started if (!mTouchState.isUserInteracting() && mPipTaskOrganizer.isEntryScheduled()) { ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index b57f51aff176..ac1567aba6e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -25,6 +25,7 @@ import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP; import android.animation.Animator; @@ -605,8 +606,11 @@ public class PipTransition extends PipTransitionController implements && pipChange.getMode() == TRANSIT_TO_BACK; boolean isPipClosed = info.getType() == TRANSIT_CLOSE && pipChange.getMode() == TRANSIT_CLOSE; - // PiP is being removed if the pinned task is either moved to back or closed. - return isPipMovedToBack || isPipClosed; + // If PiP is dismissed by user (i.e. via dismiss button in PiP menu) + boolean isPipDismissed = info.getType() == TRANSIT_REMOVE_PIP + && pipChange.getMode() == TRANSIT_TO_BACK; + // PiP is being removed if the pinned task is either moved to back, closed, or dismissed. + return isPipMovedToBack || isPipClosed || isPipDismissed; } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 47c5eec8cbd1..3e76403de51b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -35,12 +35,19 @@ import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; +import static com.android.wm.shell.common.split.SplitScreenUtils.isPartiallyOffscreen; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; +import static com.android.wm.shell.shared.TransitionUtil.isOrderOnly; import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -117,6 +124,7 @@ import com.android.internal.logging.InstanceId; import com.android.internal.policy.FoldLockSettingsObserver; import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -160,19 +168,19 @@ import java.util.concurrent.Executor; * - The {@link SplitLayout} divider is only visible if multiple {@link StageTaskListener}s are * visible * - Both stages are put under a single-top root task. - * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and - * {@link #onStageHasChildrenChanged(StageListenerImpl).} */ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler, - ShellTaskOrganizer.TaskListener { + ShellTaskOrganizer.TaskListener, StageTaskListener.StageListenerCallbacks { private static final String TAG = StageCoordinator.class.getSimpleName(); + // The duration in ms to prevent launch-adjacent from working after split screen is first + // entered + private static final int DISABLE_LAUNCH_ADJACENT_AFTER_ENTER_TIMEOUT_MS = 1000; + private final StageTaskListener mMainStage; - private final StageListenerImpl mMainStageListener = new StageListenerImpl(); private final StageTaskListener mSideStage; - private final StageListenerImpl mSideStageListener = new StageListenerImpl(); @SplitPosition private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -235,6 +243,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private SplitScreen.SplitInvocationListener mSplitInvocationListener; private Executor mSplitInvocationListenerExecutor; + // Re-enables launch-adjacent handling on the split root task. This needs to be a member + // because we will be posting and removing it from the handler. + private final Runnable mReEnableLaunchAdjacentOnRoot = () -> setLaunchAdjacentDisabled(false); + /** * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support * CompatUI layouts. CompatUI is handled separately by MainStage and SideStage. @@ -328,7 +340,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mContext, mTaskOrganizer, mDisplayId, - mMainStageListener, + this /*stageListenerCallbacks*/, mSyncQueue, iconProvider, mWindowDecorViewModel); @@ -336,7 +348,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mContext, mTaskOrganizer, mDisplayId, - mSideStageListener, + this /*stageListenerCallbacks*/, mSyncQueue, iconProvider, mWindowDecorViewModel); @@ -411,7 +423,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } public boolean isSplitScreenVisible() { - return mSideStageListener.mVisible && mMainStageListener.mVisible; + return mSideStage.mVisible && mMainStage.mVisible; } private void activateSplit(WindowContainerTransaction wct, boolean includingTopTask) { @@ -1096,7 +1108,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStagePosition = sideStagePosition; sendOnStagePositionChanged(); - if (mSideStageListener.mVisible && updateBounds) { + if (mSideStage.mVisible && updateBounds) { if (wct == null) { // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds. onLayoutSizeChanged(mSplitLayout); @@ -1317,8 +1329,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void clearRequestIfPresented() { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented"); - if (mSideStageListener.mVisible && mSideStageListener.mHasChildren - && mMainStageListener.mVisible && mSideStageListener.mHasChildren) { + if (mSideStage.mVisible && mSideStage.mHasChildren + && mMainStage.mVisible && mSideStage.mHasChildren) { mSplitRequest = null; } } @@ -1571,11 +1583,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId, + @Override + public void onChildTaskStatusChanged(StageTaskListener stageListener, int taskId, boolean present, boolean visible) { int stage; if (present) { - stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; + stage = stageListener == mSideStage ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; } else { // No longer on any stage stage = STAGE_TYPE_UNDEFINED; @@ -1706,13 +1719,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @VisibleForTesting - void onRootTaskAppeared() { + @Override + public void onRootTaskAppeared() { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskAppeared: rootTask=%s mainRoot=%b sideRoot=%b", - mRootTaskInfo, mMainStageListener.mHasRootTask, mSideStageListener.mHasRootTask); + mRootTaskInfo, mMainStage.mHasRootTask, mSideStage.mHasRootTask); // Wait unit all root tasks appeared. if (mRootTaskInfo == null - || !mMainStageListener.mHasRootTask - || !mSideStageListener.mHasRootTask) { + || !mMainStage.mHasRootTask + || !mSideStage.mHasRootTask) { return; } @@ -1732,35 +1746,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token); } - /** Callback when split roots have child task appeared under it, this is a little different from - * #onStageHasChildrenChanged because this would be called every time child task appeared. - * NOTICE: This only be called on legacy transition. */ - private void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onChildTaskAppeared: isMainStage=%b task=%d", - stageListener == mMainStageListener, taskId); - // Handle entering split screen while there is a split pair running in the background. - if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive() - && mSplitRequest == null) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareEnterSplitScreen(wct); - mMainStage.evictAllChildren(wct); - mSideStage.evictOtherChildren(wct, taskId); - - mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> { - if (mIsDropEntering) { - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); - mIsDropEntering = false; - mSkipEvictingMainStageChildren = false; - } else { - mShowDecorImmediately = true; - mSplitLayout.flingDividerToCenter(/*finishCallback*/ null); - } - }); - } - } - - private void onRootTaskVanished() { + @Override + public void onRootTaskVanished() { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskVanished"); final WindowContainerTransaction wct = new WindowContainerTransaction(); mLaunchAdjacentController.clearLaunchAdjacentRoot(); @@ -1777,15 +1764,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Callback when split roots visiblility changed. * NOTICE: This only be called on legacy transition. */ - private void onStageVisibilityChanged(StageListenerImpl stageListener) { + @Override + public void onStageVisibilityChanged(StageTaskListener stageListener) { // If split didn't active, just ignore this callback because we should already did these // on #applyExitSplitScreen. if (!isSplitActive()) { return; } - final boolean sideStageVisible = mSideStageListener.mVisible; - final boolean mainStageVisible = mMainStageListener.mVisible; + final boolean sideStageVisible = mSideStage.mVisible; + final boolean mainStageVisible = mMainStage.mVisible; // Wait for both stages having the same visibility to prevent causing flicker. if (mainStageVisible != sideStageVisible) { @@ -1922,18 +1910,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Callback when split roots have child or haven't under it. * NOTICE: This only be called on legacy transition. */ - private void onStageHasChildrenChanged(StageListenerImpl stageListener) { + @Override + public void onStageHasChildrenChanged(StageTaskListener stageListener) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onStageHasChildrenChanged: isMainStage=%b", - stageListener == mMainStageListener); + stageListener == mMainStage); final boolean hasChildren = stageListener.mHasChildren; - final boolean isSideStage = stageListener == mSideStageListener; + final boolean isSideStage = stageListener == mSideStage; if (!hasChildren && !mIsExiting && isSplitActive()) { - if (isSideStage && mMainStageListener.mVisible) { + if (isSideStage && mMainStage.mVisible) { // Exit to main stage if side stage no longer has children. mSplitLayout.flingDividerToDismiss( mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT, EXIT_REASON_APP_FINISHED); - } else if (!isSideStage && mSideStageListener.mVisible) { + } else if (!isSideStage && mSideStage.mVisible) { // Exit to side stage if main stage no longer has children. mSplitLayout.flingDividerToDismiss( mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT, @@ -1958,7 +1947,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }); } - if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) { + if (mMainStage.mHasChildren && mSideStage.mHasChildren) { mShouldUpdateRecents = true; clearRequestIfPresented(); updateRecentTasksSplitPair(); @@ -1974,6 +1963,35 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override + public void onNoLongerSupportMultiWindow(StageTaskListener stageTaskListener, + ActivityManager.RunningTaskInfo taskInfo) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo); + if (isSplitActive()) { + final boolean isMainStage = mMainStage == stageTaskListener; + + // If visible, we preserve the app and keep it running. If an app becomes + // unsupported in the bg, break split without putting anything on top + boolean splitScreenVisible = isSplitScreenVisible(); + int stageType = STAGE_TYPE_UNDEFINED; + if (splitScreenVisible) { + stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + } + final WindowContainerTransaction wct = new WindowContainerTransaction(); + prepareExitSplitScreen(stageType, wct); + clearSplitPairedInRecents(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); + mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType, + EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); + Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow", + "app package " + taskInfo.baseIntent.getComponent() + + " does not support splitscreen, or is a controlled activity" + + " type")); + if (splitScreenVisible) { + handleUnsupportedSplitStart(); + } + } + } + + @Override public void onSnappedToDismiss(boolean bottomOrRight, @ExitReason int exitReason) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onSnappedToDismiss: bottomOrRight=%b reason=%s", bottomOrRight, exitReasonToString(exitReason)); @@ -2045,6 +2063,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish"); }, mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager()); + if (Flags.enableFlexibleTwoAppSplit()) { + switch (layout.calculateCurrentSnapPosition()) { + case SNAP_TO_2_10_90 -> grantFocusToPosition(false /* leftOrTop */); + case SNAP_TO_2_90_10 -> grantFocusToPosition(true /* leftOrTop */); + } + } + mLogger.logResize(mSplitLayout.getDividerPositionAsFraction()); } @@ -2498,6 +2523,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mTaskOrganizer.applyTransaction(wct); } continue; + } else if (Flags.enableFlexibleTwoAppSplit() && isOrderOnly(change)) { + int focusedStageIndex = SPLIT_INDEX_UNDEFINED; + if (taskInfo.token.equals(mMainStage.mRootTaskInfo.token)) { + focusedStageIndex = mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT + ? SPLIT_INDEX_0 : SPLIT_INDEX_1; + } else if (taskInfo.token.equals(mSideStage.mRootTaskInfo.token)) { + focusedStageIndex = mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT + ? SPLIT_INDEX_1 : SPLIT_INDEX_0; + } + + if (focusedStageIndex != SPLIT_INDEX_UNDEFINED) { + @PersistentSnapPosition int currentSnapPosition = + mSplitLayout.calculateCurrentSnapPosition(); + boolean offscreenTaskFocused = + isPartiallyOffscreen(focusedStageIndex, currentSnapPosition); + + if (offscreenTaskFocused) { + mSplitLayout.flingDividerToOtherSide(currentSnapPosition); + } + } } final StageTaskListener stage = getStageOfTask(taskInfo); if (stage == null) { @@ -2662,6 +2707,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + /** + * Sets whether launch-adjacent is disabled or enabled. + */ + private void setLaunchAdjacentDisabled(boolean disabled) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setLaunchAdjacentDisabled: disabled=%b", disabled); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setDisableLaunchAdjacent(mRootTaskInfo.token, disabled); + mTaskOrganizer.applyTransaction(wct); + } + /** Starts the pending transition animation. */ public boolean startPendingAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @@ -2674,6 +2729,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mSplitTransitions.isPendingEnter(transition)) { shouldAnimate = startPendingEnterAnimation(transition, mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction); + + // Disable launch adjacent after an enter animation to prevent cases where apps are + // incorrectly trampolining and incorrectly triggering a double launch-adjacent task + // launch (ie. main -> split -> main). See b/344216031 + setLaunchAdjacentDisabled(true); + mMainHandler.removeCallbacks(mReEnableLaunchAdjacentOnRoot); + mMainHandler.postDelayed(mReEnableLaunchAdjacentOnRoot, + DISABLE_LAUNCH_ADJACENT_AFTER_ENTER_TIMEOUT_MS); } else if (mSplitTransitions.isPendingDismiss(transition)) { final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss; shouldAnimate = startPendingDismissAnimation( @@ -3182,13 +3245,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition())); pw.println(childPrefix + "isActive=" + isSplitActive()); mMainStage.dump(pw, childPrefix); - pw.println(innerPrefix + "MainStageListener"); - mMainStageListener.dump(pw, childPrefix); pw.println(innerPrefix + "SideStage"); pw.println(childPrefix + "stagePosition=" + splitPositionToString(getSideStagePosition())); mSideStage.dump(pw, childPrefix); - pw.println(innerPrefix + "SideStageListener"); - mSideStageListener.dump(pw, childPrefix); if (mSplitLayout != null) { mSplitLayout.dump(pw, childPrefix); } @@ -3204,8 +3263,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, */ private void setSplitsVisible(boolean visible) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setSplitsVisible: visible=%b", visible); - mMainStageListener.mVisible = mSideStageListener.mVisible = visible; - mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible; + mMainStage.mVisible = mSideStage.mVisible = visible; + mMainStage.mHasChildren = mSideStage.mHasChildren = visible; } /** @@ -3255,86 +3314,4 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */, mSplitLayout.isLeftRightSplit()); } - - class StageListenerImpl implements StageTaskListener.StageListenerCallbacks { - boolean mHasRootTask = false; - boolean mVisible = false; - boolean mHasChildren = false; - - @Override - public void onRootTaskAppeared() { - mHasRootTask = true; - StageCoordinator.this.onRootTaskAppeared(); - } - - @Override - public void onChildTaskAppeared(int taskId) { - StageCoordinator.this.onChildTaskAppeared(this, taskId); - } - - @Override - public void onStatusChanged(boolean visible, boolean hasChildren) { - if (!mHasRootTask) return; - - if (mHasChildren != hasChildren) { - mHasChildren = hasChildren; - StageCoordinator.this.onStageHasChildrenChanged(this); - } - if (mVisible != visible) { - mVisible = visible; - StageCoordinator.this.onStageVisibilityChanged(this); - } - } - - @Override - public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) { - StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible); - } - - @Override - public void onRootTaskVanished() { - reset(); - StageCoordinator.this.onRootTaskVanished(); - } - - @Override - public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo); - if (isSplitActive()) { - final boolean isMainStage = mMainStageListener == this; - - // If visible, we preserve the app and keep it running. If an app becomes - // unsupported in the bg, break split without putting anything on top - boolean splitScreenVisible = isSplitScreenVisible(); - int stageType = STAGE_TYPE_UNDEFINED; - if (splitScreenVisible) { - stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; - } - final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(stageType, wct); - clearSplitPairedInRecents(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); - mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType, - EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); - Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow", - "app package " + taskInfo.baseIntent.getComponent() - + " does not support splitscreen, or is a controlled activity" - + " type")); - if (splitScreenVisible) { - handleUnsupportedSplitStart(); - } - } - } - - private void reset() { - mHasRootTask = false; - mVisible = false; - mHasChildren = false; - } - - public void dump(@NonNull PrintWriter pw, String prefix) { - pw.println(prefix + "mHasRootTask=" + mHasRootTask); - pw.println(prefix + "mVisible=" + mVisible); - pw.println(prefix + "mHasChildren=" + mHasChildren); - } - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index d64c0a24be68..ae4bd1615ae1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -22,20 +22,17 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.RemoteAnimationTarget.MODE_OPENING; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE; -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; -import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import android.annotation.CallSuper; import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; -import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; -import android.util.Slog; import android.util.SparseArray; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; @@ -74,20 +71,21 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { // No current way to enforce this but if enableFlexibleSplit() is enabled, then only 1 of the // stages should have this be set/being used private boolean mIsActive; - /** Callback interface for listening to changes in a split-screen stage. */ public interface StageListenerCallbacks { void onRootTaskAppeared(); - void onChildTaskAppeared(int taskId); + void onStageHasChildrenChanged(StageTaskListener stageTaskListener); - void onStatusChanged(boolean visible, boolean hasChildren); + void onStageVisibilityChanged(StageTaskListener stageTaskListener); - void onChildTaskStatusChanged(int taskId, boolean present, boolean visible); + void onChildTaskStatusChanged(StageTaskListener stage, int taskId, boolean present, + boolean visible); void onRootTaskVanished(); - void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo); + void onNoLongerSupportMultiWindow(StageTaskListener stageTaskListener, + ActivityManager.RunningTaskInfo taskInfo); } private final Context mContext; @@ -96,6 +94,12 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { private final IconProvider mIconProvider; private final Optional<WindowDecorViewModel> mWindowDecorViewModel; + /** Whether or not the root task has been created. */ + boolean mHasRootTask = false; + /** Whether or not the root task is visible. */ + boolean mVisible = false; + /** Whether or not the root task has any children or not. */ + boolean mHasChildren = false; protected ActivityManager.RunningTaskInfo mRootTaskInfo; protected SurfaceControl mRootLeash; protected SurfaceControl mDimLayer; @@ -201,6 +205,7 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { mSplitDecorManager = new SplitDecorManager( mRootTaskInfo.configuration, mIconProvider); + mHasRootTask = true; mCallbacks.onRootTaskAppeared(); sendStatusChanged(); mSyncQueue.runInSync(t -> mDimLayer = @@ -209,15 +214,8 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { final int taskId = taskInfo.taskId; mChildrenLeashes.put(taskId, leash); mChildrenTaskInfo.put(taskId, taskInfo); - mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, + mCallbacks.onChildTaskStatusChanged(this, taskId, true /* present */, taskInfo.isVisible && taskInfo.isVisibleRequested); - if (ENABLE_SHELL_TRANSITIONS) { - // Status is managed/synchronized by the transition lifecycle. - return; - } - updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); - mCallbacks.onChildTaskAppeared(taskId); - sendStatusChanged(); } else { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); @@ -231,14 +229,6 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { taskInfo.taskId, taskInfo.baseActivity); mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo)); if (mRootTaskInfo.taskId == taskInfo.taskId) { - // Inflates split decor view only when the root task is visible. - if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) { - if (taskInfo.isVisible) { - mSplitDecorManager.inflate(mContext, mRootLeash); - } else { - mSyncQueue.runInSync(t -> mSplitDecorManager.release(t)); - } - } mRootTaskInfo = taskInfo; } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { if (!taskInfo.supportsMultiWindow @@ -250,25 +240,16 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { taskInfo.taskId); // Leave split screen if the task no longer supports multi window or have // uncontrolled task. - mCallbacks.onNoLongerSupportMultiWindow(taskInfo); + mCallbacks.onNoLongerSupportMultiWindow(this, taskInfo); return; } mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); - mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */, + mCallbacks.onChildTaskStatusChanged(this, taskInfo.taskId, true /* present */, taskInfo.isVisible && taskInfo.isVisibleRequested); - if (!ENABLE_SHELL_TRANSITIONS) { - updateChildTaskSurface( - taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */); - } } else { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); } - if (ENABLE_SHELL_TRANSITIONS) { - // Status is managed/synchronized by the transition lifecycle. - return; - } - sendStatusChanged(); } @Override @@ -278,6 +259,9 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { final int taskId = taskInfo.taskId; mWindowDecorViewModel.ifPresent(vm -> vm.onTaskVanished(taskInfo)); if (mRootTaskInfo.taskId == taskId) { + mHasRootTask = false; + mVisible = false; + mHasChildren = false; mCallbacks.onRootTaskVanished(); mRootTaskInfo = null; mRootLeash = null; @@ -288,12 +272,8 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { } else if (mChildrenTaskInfo.contains(taskId)) { mChildrenTaskInfo.remove(taskId); mChildrenLeashes.remove(taskId); - mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible); - if (ENABLE_SHELL_TRANSITIONS) { - // Status is managed/synchronized by the transition lifecycle. - return; - } - sendStatusChanged(); + mCallbacks.onChildTaskStatusChanged(this, taskId, false /* present */, + taskInfo.isVisible); } else { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); @@ -455,26 +435,6 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { } } - private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo, - SurfaceControl leash, boolean firstAppeared) { - final Point taskPositionInParent = taskInfo.positionInParent; - mSyncQueue.runInSync(t -> { - // The task surface might be released before running in the sync queue for the case like - // trampoline launch, so check if the surface is valid before processing it. - if (!leash.isValid()) { - Slog.w(TAG, "Skip updating invalid child task surface of task#" + taskInfo.taskId); - return; - } - t.setCrop(leash, null); - t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y); - if (firstAppeared) { - t.setAlpha(leash, 1f); - t.setMatrix(leash, 1, 0, 0, 1); - t.show(leash); - } - }); - } - // --------- // Previously only used in MainStage boolean isActive() { @@ -538,7 +498,19 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { } private void sendStatusChanged() { - mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0); + boolean hasChildren = mChildrenTaskInfo.size() > 0; + boolean visible = mRootTaskInfo.isVisible; + if (!mHasRootTask) return; + + if (mHasChildren != hasChildren) { + mHasChildren = hasChildren; + mCallbacks.onStageHasChildrenChanged(this); + } + + if (mVisible != visible) { + mVisible = visible; + mCallbacks.onStageVisibilityChanged(this); + } } @Override @@ -554,5 +526,8 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { + " baseActivity=" + taskInfo.baseActivity); } } + pw.println(prefix + "mHasRootTask=" + mHasRootTask); + pw.println(prefix + "mVisible=" + mVisible); + pw.println(prefix + "mHasChildren=" + mHasChildren); } } 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 a2439a937512..5437167f58d5 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 @@ -61,6 +61,7 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN; +import static com.android.wm.shell.transition.DefaultSurfaceAnimator.buildSurfaceAnimation; import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow; import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet; import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo; @@ -68,8 +69,6 @@ import static com.android.wm.shell.transition.TransitionAnimationHelper.isCovere import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; @@ -81,7 +80,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Color; -import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -90,12 +88,10 @@ import android.os.Handler; import android.os.IBinder; import android.os.UserHandle; import android.util.ArrayMap; -import android.view.Choreographer; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; -import android.view.animation.Transformation; import android.window.TransitionInfo; import android.window.TransitionMetrics; import android.window.TransitionRequestInfo; @@ -362,7 +358,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS; if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) { final int flags = wallpaperTransit != WALLPAPER_TRANSITION_NONE - && Flags.commonSurfaceAnimator() ? ScreenRotationAnimation.FLAG_HAS_WALLPAPER : 0; startRotationAnimation(startTransaction, change, info, anim, flags, animations, onAnimFinish); @@ -823,72 +818,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return a; } - /** Builds an animator for the surface and adds it to the `animations` list. */ - static void buildSurfaceAnimation(@NonNull ArrayList<Animator> animations, - @NonNull Animation anim, @NonNull SurfaceControl leash, - @NonNull Runnable finishCallback, @NonNull TransactionPool pool, - @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius, - @Nullable Rect clipRect, boolean isActivity) { - if (Flags.commonSurfaceAnimator()) { - DefaultSurfaceAnimator.buildSurfaceAnimation(animations, anim, leash, finishCallback, - pool, mainExecutor, position, cornerRadius, clipRect, isActivity); - return; - } - final SurfaceControl.Transaction transaction = pool.acquire(); - final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); - final Transformation transformation = new Transformation(); - final float[] matrix = new float[9]; - // Animation length is already expected to be scaled. - va.overrideDurationScale(1.0f); - va.setDuration(anim.computeDurationHint()); - final ValueAnimator.AnimatorUpdateListener updateListener = animation -> { - final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime()); - - applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix, - position, cornerRadius, clipRect, isActivity); - }; - va.addUpdateListener(updateListener); - - final Runnable finisher = () -> { - applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix, - position, cornerRadius, clipRect, isActivity); - - pool.release(transaction); - mainExecutor.execute(() -> { - animations.remove(va); - finishCallback.run(); - }); - }; - va.addListener(new AnimatorListenerAdapter() { - // It is possible for the end/cancel to be called more than once, which may cause - // issues if the animating surface has already been released. Track the finished - // state here to skip duplicate callbacks. See b/252872225. - private boolean mFinished = false; - - @Override - public void onAnimationEnd(Animator animation) { - onFinish(); - } - - @Override - public void onAnimationCancel(Animator animation) { - onFinish(); - } - - private void onFinish() { - if (mFinished) return; - mFinished = true; - finisher.run(); - // The update listener can continue to be called after the animation has ended if - // end() is called manually again before the finisher removes the animation. - // Remove it manually here to prevent animating a released surface. - // See b/252872225. - va.removeUpdateListener(updateListener); - } - }); - animations.add(va); - } - private void attachThumbnail(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, TransitionInfo.AnimationOptions options, float cornerRadius) { @@ -1014,38 +943,4 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS || animType == ANIM_FROM_STYLE; } - - private static void applyTransformation(long time, SurfaceControl.Transaction t, - SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix, - Point position, float cornerRadius, @Nullable Rect immutableClipRect, - boolean isActivity) { - tmpTransformation.clear(); - anim.getTransformation(time, tmpTransformation); - if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader() - && anim.getExtensionEdges() != 0x0 && isActivity) { - t.setEdgeExtensionEffect(leash, anim.getExtensionEdges()); - } - if (position != null) { - tmpTransformation.getMatrix().postTranslate(position.x, position.y); - } - t.setMatrix(leash, tmpTransformation.getMatrix(), matrix); - t.setAlpha(leash, tmpTransformation.getAlpha()); - - final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect); - Insets extensionInsets = Insets.min(tmpTransformation.getInsets(), Insets.NONE); - if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) { - // Clip out any overflowing edge extension - clipRect.inset(extensionInsets); - t.setCrop(leash, clipRect); - } - - if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) { - // We can only apply rounded corner if a crop is set - t.setCrop(leash, clipRect); - t.setCornerRadius(leash, cornerRadius); - } - - t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); - t.apply(); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java index 1a04997fa384..6f3aa11a8f52 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java @@ -21,7 +21,7 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFA import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT; import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE; -import static com.android.wm.shell.transition.DefaultTransitionHandler.buildSurfaceAnimation; +import static com.android.wm.shell.transition.DefaultSurfaceAnimator.buildSurfaceAnimation; import static com.android.wm.shell.transition.Transitions.TAG; import android.animation.Animator; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index 230f7e6912ee..0bd3e083671e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -1,5 +1,6 @@ package com.android.wm.shell.desktopmode +import android.animation.AnimatorTestRule import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD @@ -24,6 +25,7 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.splitscreen.SplitScreenController @@ -38,6 +40,7 @@ import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any @@ -58,6 +61,9 @@ import org.mockito.quality.Strictness @RunWithLooper @RunWith(AndroidTestingRunner::class) class DragToDesktopTransitionHandlerTest : ShellTestCase() { + @JvmField + @Rule + val mAnimatorTestRule = AnimatorTestRule(this) @Mock private lateinit var transitions: Transitions @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @@ -267,16 +273,36 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { } @Test - fun cancelDragToDesktop_startWasReady_cancel() { - startDrag(defaultHandler) + fun cancelDragToDesktop_startWasReady_cancel_merged() { + val startToken = startDrag(defaultHandler) // Then user cancelled after it had already started. - defaultHandler.cancelDragToDesktopTransition( - DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL - ) + val cancelToken = cancelDragToDesktopTransition( + defaultHandler, DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL) + defaultHandler.mergeAnimation( + cancelToken, + TransitionInfo(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, 0), + mock<SurfaceControl.Transaction>(), + startToken, + mock<Transitions.TransitionFinishCallback>()) + + // Cancel animation should run since it had already started. + verify(dragAnimator).cancelAnimator() + assertFalse("Drag should not be in progress after cancelling", defaultHandler.inProgress) + } + + @Test + fun cancelDragToDesktop_startWasReady_cancel_aborted() { + val startToken = startDrag(defaultHandler) + + // Then user cancelled after it had already started. + val cancelToken = cancelDragToDesktopTransition( + defaultHandler, DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL) + defaultHandler.onTransitionConsumed(cancelToken, aborted = true, null) // Cancel animation should run since it had already started. verify(dragAnimator).cancelAnimator() + assertFalse("Drag should not be in progress after cancelling", defaultHandler.inProgress) } @Test @@ -585,6 +611,23 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { return token } + private fun cancelDragToDesktopTransition( + handler: DragToDesktopTransitionHandler, + cancelState: DragToDesktopTransitionHandler.CancelState): IBinder { + val token = mock<IBinder>() + whenever( + transitions.startTransition( + eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), + any(), + eq(handler) + ) + ) + .thenReturn(token) + handler.cancelDragToDesktopTransition(cancelState) + mAnimatorTestRule.advanceTimeBy(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS) + return token + } + private fun performEarlyCancel( handler: DragToDesktopTransitionHandler, cancelState: DragToDesktopTransitionHandler.CancelState diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index b7b7d0d35bcf..189684dc391a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -23,10 +23,9 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.times; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.app.ActivityManager; @@ -64,8 +63,6 @@ import java.util.Optional; @SmallTest @RunWith(AndroidJUnit4.class) public final class StageTaskListenerTests extends ShellTestCase { - private static final boolean ENABLE_SHELL_TRANSITIONS = - SystemProperties.getBoolean("persist.wm.debug.shell_transit", true); @Mock private ShellTaskOrganizer mTaskOrganizer; @@ -117,20 +114,20 @@ public final class StageTaskListenerTests extends ShellTestCase { public void testRootTaskAppeared() { assertThat(mStageTaskListener.mRootTaskInfo.taskId).isEqualTo(mRootTask.taskId); verify(mCallbacks).onRootTaskAppeared(); - verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(false)); + verify(mCallbacks, never()).onStageHasChildrenChanged(mStageTaskListener); + verify(mCallbacks, never()).onStageVisibilityChanged(mStageTaskListener); } @Test - public void testChildTaskAppeared() { - // With shell transitions, the transition manages status changes, so skip this test. - assumeFalse(ENABLE_SHELL_TRANSITIONS); - final ActivityManager.RunningTaskInfo childTask = - new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build(); + public void testRootTaskVisible() { + mStageTaskListener.onTaskVanished(mRootTask); + mRootTask = new TestRunningTaskInfoBuilder().setVisible(true).build(); + mRootTask.parentTaskId = INVALID_TASK_ID; + mSurfaceControl = new SurfaceControl.Builder().setName("test").build(); + mStageTaskListener.onTaskAppeared(mRootTask, mSurfaceControl); - mStageTaskListener.onTaskAppeared(childTask, mSurfaceControl); + verify(mCallbacks).onStageVisibilityChanged(mStageTaskListener); - assertThat(mStageTaskListener.mChildrenTaskInfo.contains(childTask.taskId)).isTrue(); - verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true)); } @Test(expected = IllegalArgumentException.class) @@ -140,29 +137,13 @@ public final class StageTaskListenerTests extends ShellTestCase { } @Test - public void testTaskVanished() { - // With shell transitions, the transition manages status changes, so skip this test. - assumeFalse(ENABLE_SHELL_TRANSITIONS); - final ActivityManager.RunningTaskInfo childTask = - new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build(); - mStageTaskListener.mRootTaskInfo = mRootTask; - mStageTaskListener.mChildrenTaskInfo.put(childTask.taskId, childTask); - - mStageTaskListener.onTaskVanished(childTask); - verify(mCallbacks, times(2)).onStatusChanged(eq(mRootTask.isVisible), eq(false)); - - mStageTaskListener.onTaskVanished(mRootTask); - verify(mCallbacks).onRootTaskVanished(); - } - - @Test public void testTaskInfoChanged_notSupportsMultiWindow() { final ActivityManager.RunningTaskInfo childTask = new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build(); childTask.supportsMultiWindow = false; mStageTaskListener.onTaskInfoChanged(childTask); - verify(mCallbacks).onNoLongerSupportMultiWindow(childTask); + verify(mCallbacks).onNoLongerSupportMultiWindow(mStageTaskListener, childTask); } @Test diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java index 4d6ddfdaef2e..3cd5f5266ef2 100644 --- a/media/java/android/media/AudioPlaybackConfiguration.java +++ b/media/java/android/media/AudioPlaybackConfiguration.java @@ -18,7 +18,9 @@ package android.media; import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_ALL; import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE; +import static android.media.audio.Flags.FLAG_MUTED_BY_PORT_VOLUME_API; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -291,12 +293,24 @@ public final class AudioPlaybackConfiguration implements Parcelable { @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_VOLUME_SHAPER = (1 << 5); + /** + * @hide + * Flag used when muted by the track's port volume. + * + * <p>Note: this will replace the stream volume mute when using the AudioFlinger port volume + * APIs + */ + @SystemApi + @FlaggedApi(FLAG_MUTED_BY_PORT_VOLUME_API) + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public static final int MUTED_BY_PORT_VOLUME = (1 << 6); /** @hide */ @IntDef( flag = true, value = {MUTED_BY_MASTER, MUTED_BY_STREAM_VOLUME, MUTED_BY_STREAM_MUTED, - MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER}) + MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER, + MUTED_BY_PORT_VOLUME}) @Retention(RetentionPolicy.SOURCE) public @interface PlayerMuteEvent { } @@ -858,6 +872,9 @@ public final class AudioPlaybackConfiguration implements Parcelable { if ((mMutedState & MUTED_BY_VOLUME_SHAPER) != 0) { apcToString.append("volumeShaper "); } + if ((mMutedState & MUTED_BY_PORT_VOLUME) != 0) { + apcToString.append("portVolume "); + } } apcToString.append(" ").append(mFormatInfo); } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index a8b863bc67f9..bf09cb07a8ed 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1698,12 +1698,12 @@ public class AudioSystem } /** @hide Wrapper for native methods called from AudioService */ - public static int setStreamVolumeIndexAS(int stream, int index, int device) { + public static int setStreamVolumeIndexAS(int stream, int index, boolean muted, int device) { if (DEBUG_VOLUME) { Log.i(TAG, "setStreamVolumeIndex: " + STREAM_NAMES[stream] - + " dev=" + Integer.toHexString(device) + " idx=" + index); + + " dev=" + Integer.toHexString(device) + " idx=" + index + " muted=" + muted); } - return setStreamVolumeIndex(stream, index, device); + return setStreamVolumeIndex(stream, index, muted, device); } // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t @@ -1774,7 +1774,8 @@ public class AudioSystem @UnsupportedAppUsage public static native int initStreamVolume(int stream, int indexMin, int indexMax); @UnsupportedAppUsage - private static native int setStreamVolumeIndex(int stream, int index, int device); + private static native int setStreamVolumeIndex(int stream, int index, boolean muted, + int device); /** @hide */ public static native int getStreamVolumeIndex(int stream, int device); /** @@ -1787,7 +1788,7 @@ public class AudioSystem * @return command completion status. */ public static native int setVolumeIndexForAttributes(@NonNull AudioAttributes attributes, - int index, int device); + int index, boolean muted, int device); /** * @hide * get the volume index for the given {@link AudioAttributes}. diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt index 8c5d8778c96f..8fb16d8ac840 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt @@ -78,6 +78,10 @@ private constructor(private val context: Context, private val request: GetPrefer for (activityClass in request.activityClasses) { add(activityClass) } + // Temporarily add all screens + for (key in PreferenceScreenRegistry.preferenceScreens.keys) { + addPreferenceScreenFromRegistry(key, Activity::class.java) + } } fun build() = builder.build() diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt index 48798da57dae..6646d6c32d15 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt @@ -34,7 +34,7 @@ object PreferenceScreenRegistry : ReadWritePermitProvider { ImmutableMap.of() } - private val preferenceScreens: PreferenceScreenMap + val preferenceScreens: PreferenceScreenMap get() = preferenceScreensSupplier.get() private var readWritePermitProvider: ReadWritePermitProvider? = null diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index b69912a3fd36..73d0beccd0ba 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath allprojects { extra["androidTop"] = androidTop - extra["jetpackComposeVersion"] = "1.7.0" + extra["jetpackComposeVersion"] = "1.7.3" } subprojects { diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index 8f8275bed702..914f06c1fef7 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -54,7 +54,7 @@ android { dependencies { api(project(":SettingsLibColor")) api("androidx.appcompat:appcompat:1.7.0") - api("androidx.compose.material3:material3:1.3.0") + api("androidx.compose.material3:material3:1.4.0-alpha01") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt index 2c55779c9a01..693fb3541bb8 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt @@ -46,6 +46,7 @@ import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.NonRestartableComposable import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf @@ -326,6 +327,9 @@ private fun TwoRowsTopAppBar( // Sets the app bar's height offset limit to hide just the bottom title area and keep top title // visible when collapsed. scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue + if (isSpaExpressiveEnabled) { + LaunchedEffect(scrollBehavior?.state?.heightOffsetLimit) { scrollBehavior?.collapse() } + } // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt index 7d8ee79b3344..60b1e639a2b7 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt @@ -39,7 +39,6 @@ import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.compose.horizontalValues import com.android.settingslib.spa.framework.compose.verticalValues import com.android.settingslib.spa.framework.theme.SettingsTheme -import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled import com.android.settingslib.spa.framework.theme.settingsBackground import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel @@ -56,9 +55,6 @@ fun SettingsScaffold( ) { ActivityTitle(title) val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - if (isSpaExpressiveEnabled) { - LaunchedEffect(scrollBehavior.state.heightOffsetLimit) { scrollBehavior.collapse() } - } Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp index 1a99d25786ff..65b22758946d 100644 --- a/packages/SettingsProvider/Android.bp +++ b/packages/SettingsProvider/Android.bp @@ -39,6 +39,7 @@ android_library { "configinfra_framework_flags_java_lib", "device_config_service_flags_java", "libaconfig_java_proto_lite", + "notification_flags_lib", "SettingsLibDeviceStateRotationLock", "SettingsLibDisplayUtils", ], diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 6c3183191163..ebeee8564d2f 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -29,6 +29,7 @@ import android.hardware.display.ColorDisplayManager; import android.icu.util.ULocale; import android.media.AudioManager; import android.media.RingtoneManager; +import android.media.Utils; import android.net.Uri; import android.os.LocaleList; import android.os.RemoteException; @@ -309,6 +310,13 @@ public class SettingsHelper { return SILENT_RINGTONE; } } else { + // If the ringtone/notification support the vibration, use the original value. + final int ringtoneType = getRingtoneType(name); + if ((ringtoneType == RingtoneManager.TYPE_RINGTONE + || ringtoneType == RingtoneManager.TYPE_NOTIFICATION) + && hasVibrationSettings(value, ringtoneType)) { + return value; + } return getCanonicalRingtoneValue(value); } } @@ -362,6 +370,15 @@ public class SettingsHelper { return; } + // If the ringtone/notification has vibration, we backup original value in onBackupValue. + // So use the value directly for restoring. + if ((ringtoneType == RingtoneManager.TYPE_RINGTONE + || ringtoneType == RingtoneManager.TYPE_NOTIFICATION) + && hasVibrationSettings(value, ringtoneType)) { + RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, Uri.parse(value)); + return; + } + Uri ringtoneUri = null; try { ringtoneUri = @@ -617,6 +634,19 @@ public class SettingsHelper { return allLocales.remove(toFullLocale(filteredLocale)); } + private boolean hasVibrationSettings(String value, int type) { + if (Utils.hasVibration(Uri.parse(value)) && mContext.getResources().getBoolean( + com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported)) { + if (type == RingtoneManager.TYPE_RINGTONE) { + return android.media.audio.Flags.enableRingtoneHapticsCustomization(); + } + if (type == RingtoneManager.TYPE_NOTIFICATION) { + return com.android.server.notification.Flags.notificationVibrationInSoundUri(); + } + } + return false; + } + /** * Sets the locale specified. Input data is the byte representation of comma separated * multiple BCP-47 language tags. For backwards compatibility, strings of the form diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java index 4b10b56f49fb..cea2bbc5c535 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java @@ -37,9 +37,12 @@ import android.content.res.Resources; import android.database.Cursor; import android.database.MatrixCursor; import android.media.AudioManager; +import android.media.Utils; import android.net.Uri; import android.os.Bundle; import android.os.LocaleList; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.BaseColumns; import android.provider.MediaStore; import android.provider.Settings; @@ -54,8 +57,11 @@ import com.android.internal.R; import org.junit.After; import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -63,6 +69,7 @@ import org.mockito.MockitoAnnotations; * Tests for the SettingsHelperTest */ @RunWith(AndroidJUnit4.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class SettingsHelperTest { private static final String SETTING_KEY = "setting_key"; private static final String SETTING_VALUE = "setting_value"; @@ -74,9 +81,13 @@ public class SettingsHelperTest { "content://media/internal/audio/media/20?title=DefaultNotification&canonical=1"; private static final String DEFAULT_ALARM_VALUE = "content://media/internal/audio/media/30?title=DefaultAlarm&canonical=1"; + private static final String VIBRATION_FILE_NAME = "haptics.xml"; private SettingsHelper mSettingsHelper; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private Context mContext; @Mock private Resources mResources; @Mock private AudioManager mAudioManager; @@ -120,6 +131,22 @@ public class SettingsHelperTest { } @Test + @EnableFlags({android.media.audio.Flags.FLAG_ENABLE_RINGTONE_HAPTICS_CUSTOMIZATION, + com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI}) + public void testOnBackupValue_ringtoneVibrationSupport_returnsSameValue() { + when(mResources.getBoolean( + com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported)).thenReturn( + true); + String testRingtoneVibrationValue = createUriWithVibration(DEFAULT_RINGTONE_VALUE); + String testNotificationVibrationValue = createUriWithVibration(DEFAULT_NOTIFICATION_VALUE); + + assertEquals(testRingtoneVibrationValue, mSettingsHelper.onBackupValue( + Settings.System.RINGTONE, testRingtoneVibrationValue)); + assertEquals(testNotificationVibrationValue, mSettingsHelper.onBackupValue( + Settings.System.NOTIFICATION_SOUND, testNotificationVibrationValue)); + } + + @Test public void testGetRealValue_settingNotReplaced_returnsSameValue() { when(mSettingsHelper.isReplacedSystemSetting(eq(SETTING_KEY))).thenReturn(false); @@ -675,6 +702,30 @@ public class SettingsHelperTest { .isEqualTo(null); } + @Test + @EnableFlags({android.media.audio.Flags.FLAG_ENABLE_RINGTONE_HAPTICS_CUSTOMIZATION, + com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI}) + public void testRestoreValue_ringtoneVibrationSupport_restoreValue() { + when(mResources.getBoolean( + com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported)).thenReturn( + true); + String testRingtoneVibrationValue = createUriWithVibration(DEFAULT_RINGTONE_VALUE); + String testNotificationVibrationValue = createUriWithVibration(DEFAULT_NOTIFICATION_VALUE); + ContentProvider mockMediaContentProvider = + new MockContentProvider(mContext) { + @Override + public String getType(Uri url) { + return "audio/ogg"; + } + }; + mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider); + resetRingtoneSettingsToDefault(); + + assertRingtoneSettingsRestoring(Settings.System.RINGTONE, testRingtoneVibrationValue); + assertRingtoneSettingsRestoring( + Settings.System.NOTIFICATION_SOUND, testNotificationVibrationValue); + } + private static class MockSettingsProvider extends MockContentProvider { private final ArrayMap<String, String> mKeyValueStore = new ArrayMap<>(); MockSettingsProvider(Context context) { @@ -766,4 +817,25 @@ public class SettingsHelperTest { assertThat(Settings.System.getString(mContentResolver, Settings.System.ALARM_ALERT)) .isEqualTo(DEFAULT_ALARM_VALUE); } + + private String createUriWithVibration(String defaultUriString) { + return Uri.parse(defaultUriString).buildUpon() + .appendQueryParameter( + Utils.VIBRATION_URI_PARAM, VIBRATION_FILE_NAME).build().toString(); + } + + private void assertRingtoneSettingsRestoring( + String settings, String testRingtoneSettingsValue) { + mSettingsHelper.restoreValue( + mContext, + mContentResolver, + new ContentValues(), + Uri.EMPTY, + settings, + testRingtoneSettingsValue, + 0); + + assertThat(Settings.System.getString(mContentResolver, settings)) + .isEqualTo(testRingtoneSettingsValue); + } } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index a21a80506279..189294484085 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1481,3 +1481,9 @@ flag { bug: "370863642" } +flag { + name: "notes_role_qs_tile" + namespace: "systemui" + description: "Enables notes role qs tile which opens default notes role app in app bubbles" + bug: "357863750" +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt index 93a99bd948f1..18f40c98fe04 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -136,20 +136,16 @@ constructor( ) /** - * The timings when animating a View into an app using a spring animator. - * - * Important: since springs don't have fixed durations, these timings represent fractions of - * the progress between the spring's initial value and its final value. - * - * TODO(b/372858592): make this a separate class explicitly using percentages. + * The timings when animating a View into an app using a spring animator. These timings + * represent fractions of the progress between the spring's initial value and its final + * value. */ val SPRING_TIMINGS = - TransitionAnimator.Timings( - totalDuration = 1000L, - contentBeforeFadeOutDelay = 0L, - contentBeforeFadeOutDuration = 800L, - contentAfterFadeInDelay = 850L, - contentAfterFadeInDuration = 135L, + TransitionAnimator.SpringTimings( + contentBeforeFadeOutDelay = 0f, + contentBeforeFadeOutDuration = 0.8f, + contentAfterFadeInDelay = 0.85f, + contentAfterFadeInDuration = 0.135f, ) /** diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt index 1d8ff77ac719..9dc93484a638 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt @@ -52,7 +52,7 @@ class TransitionAnimator( private val interpolators: Interpolators, /** [springTimings] and [springInterpolators] must either both be null or both not null. */ - private val springTimings: Timings? = null, + private val springTimings: SpringTimings? = null, private val springInterpolators: Interpolators? = null, private val springParams: SpringParams = DEFAULT_SPRING_PARAMS, ) { @@ -83,8 +83,22 @@ class TransitionAnimator( delay: Long, duration: Long, ): Float { + return getProgressInternal( + timings.totalDuration.toFloat(), + linearProgress, + delay.toFloat(), + duration.toFloat(), + ) + } + + private fun getProgressInternal( + totalDuration: Float, + linearProgress: Float, + delay: Float, + duration: Float, + ): Float { return MathUtils.constrain( - (linearProgress * timings.totalDuration - delay) / duration, + (linearProgress * totalDuration - delay) / duration, 0.0f, 1.0f, ) @@ -367,6 +381,25 @@ class TransitionAnimator( val contentAfterFadeInDuration: Long, ) + /** + * The timings (durations and delays) used by the multi-spring animator. These are expressed as + * fractions of 1, similar to how the progress of an animator can be expressed as a float value + * between 0 and 1. + */ + class SpringTimings( + /** The portion of animation to wait before fading out the expanding content. */ + val contentBeforeFadeOutDelay: Float, + + /** The portion of animation during which the expanding content fades out. */ + val contentBeforeFadeOutDuration: Float, + + /** The portion of animation to wait before fading in the expanded content. */ + val contentAfterFadeInDelay: Float, + + /** The portion of animation during which the expanded content fades in. */ + val contentAfterFadeInDuration: Float, + ) + /** The interpolators used by this animator. */ data class Interpolators( /** The interpolator used for the Y position, width, height and corner radius. */ @@ -576,18 +609,14 @@ class TransitionAnimator( } override fun onAnimationEnd(animation: Animator) { - if (DEBUG) { - Log.d(TAG, "Animation ended") - } - - // TODO(b/330672236): Post this to the main thread instead so that it does not - // flicker with Flexiglass enabled. - controller.onTransitionAnimationEnd(isExpandingFullyAbove) - transitionContainerOverlay.remove(windowBackgroundLayer) - - if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) { - openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) - } + onAnimationEnd( + controller, + isExpandingFullyAbove, + windowBackgroundLayer, + transitionContainerOverlay, + openingWindowSyncViewOverlay, + moveBackgroundLayerWhenAppVisibilityChanges, + ) } } ) @@ -1021,34 +1050,47 @@ class TransitionAnimator( cornerRadii[7] = state.bottomCornerRadius drawable.cornerRadii = cornerRadii - val timings: Timings val interpolators: Interpolators + val fadeInProgress: Float + val fadeOutProgress: Float if (useSpring) { - timings = springTimings!! interpolators = springInterpolators!! + val timings = springTimings!! + fadeInProgress = + getProgressInternal( + totalDuration = 1f, + linearProgress, + timings.contentBeforeFadeOutDelay, + timings.contentBeforeFadeOutDuration, + ) + fadeOutProgress = + getProgressInternal( + totalDuration = 1f, + linearProgress, + timings.contentAfterFadeInDelay, + timings.contentAfterFadeInDuration, + ) } else { - timings = this.timings interpolators = this.interpolators + fadeInProgress = + getProgress( + timings, + linearProgress, + timings.contentBeforeFadeOutDelay, + timings.contentBeforeFadeOutDuration, + ) + fadeOutProgress = + getProgress( + timings, + linearProgress, + timings.contentAfterFadeInDelay, + timings.contentAfterFadeInDuration, + ) } // We first fade in the background layer to hide the expanding view, then fade it out with // SRC mode to draw a hole punch in the status bar and reveal the opening window (if // needed). If !isLaunching, the reverse happens. - val fadeInProgress = - getProgress( - timings, - linearProgress, - timings.contentBeforeFadeOutDelay, - timings.contentBeforeFadeOutDuration, - ) - val fadeOutProgress = - getProgress( - timings, - linearProgress, - timings.contentAfterFadeInDelay, - timings.contentAfterFadeInDuration, - ) - if (isLaunching) { if (fadeInProgress < 1) { val alpha = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt index a525f36c71ce..9390664d1283 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt @@ -30,14 +30,10 @@ import androidx.compose.ui.unit.IntRect import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope -import com.android.keyguard.LockIconView -import com.android.keyguard.LockIconViewController import com.android.systemui.biometrics.AuthController import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines import com.android.systemui.keyguard.ui.view.DeviceEntryIconView @@ -61,7 +57,6 @@ constructor( private val windowManager: WindowManager, private val authController: AuthController, private val featureFlags: FeatureFlagsClassic, - private val lockIconViewController: Lazy<LockIconViewController>, private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>, private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>, private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>, @@ -71,42 +66,28 @@ constructor( ) { @Composable fun SceneScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) { - if (!KeyguardBottomAreaRefactor.isEnabled && !DeviceEntryUdfpsRefactor.isEnabled) { - return - } - val context = LocalContext.current AndroidView( factory = { context -> - val view = - if (DeviceEntryUdfpsRefactor.isEnabled) { - DeviceEntryIconView( - context, - null, - logger = LongPressHandlingViewLogger(logBuffer, tag = TAG) - ) - .apply { - id = R.id.device_entry_icon_view - DeviceEntryIconViewBinder.bind( - applicationScope, - this, - deviceEntryIconViewModel.get(), - deviceEntryForegroundViewModel.get(), - deviceEntryBackgroundViewModel.get(), - falsingManager.get(), - vibratorHelper.get(), - overrideColor, - ) - } - } else { - // KeyguardBottomAreaRefactor.isEnabled - LockIconView(context, null).apply { - id = R.id.lock_icon_view - lockIconViewController.get().setLockIconView(this) - } + DeviceEntryIconView( + context, + null, + logger = LongPressHandlingViewLogger(logBuffer, tag = TAG), + ) + .apply { + id = R.id.device_entry_icon_view + DeviceEntryIconViewBinder.bind( + applicationScope, + this, + deviceEntryIconViewModel.get(), + deviceEntryForegroundViewModel.get(), + deviceEntryBackgroundViewModel.get(), + falsingManager.get(), + vibratorHelper.get(), + overrideColor, + ) } - view }, modifier = modifier.element(LockIconElementKey).layout { measurable, _ -> @@ -141,9 +122,7 @@ constructor( * On devices that support UDFPS (under-display fingerprint sensor), the bounds of the icon are * the same as the bounds of the sensor. */ - private fun lockIconBounds( - context: Context, - ): IntRect { + private fun lockIconBounds(context: Context): IntRect { val windowViewBounds = windowManager.currentWindowMetrics.bounds var widthPx = windowViewBounds.right.toFloat() if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) { @@ -162,10 +141,7 @@ constructor( val (center, radius) = if (authController.isUdfpsSupported && udfpsLocation != null) { Pair( - IntOffset( - x = udfpsLocation.x, - y = udfpsLocation.y, - ), + IntOffset(x = udfpsLocation.x, y = udfpsLocation.y), authController.udfpsRadius.toInt(), ) } else { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt index 2e39524baaad..73c4fab7b646 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt @@ -33,7 +33,7 @@ import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding -import com.android.systemui.customization.R as customizationR +import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockElementKeys import com.android.systemui.keyguard.ui.composable.modifier.burnInAware import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel @@ -57,12 +57,12 @@ constructor( Row( modifier = Modifier.padding( - horizontal = dimensionResource(customizationR.dimen.clock_padding_start) + horizontal = dimensionResource(customR.dimen.clock_padding_start) ) .burnInAware(aodBurnInViewModel, burnInParams, isClock = true) ) { WeatherElement( - weatherClockElementViewId = customizationR.id.weather_clock_time, + weatherClockElementViewId = customR.id.weather_clock_time, clock = clock, elementKey = WeatherClockElementKeys.timeElementKey, ) @@ -75,7 +75,7 @@ constructor( modifier: Modifier = Modifier, ) { WeatherElement( - weatherClockElementViewId = customizationR.id.weather_clock_date, + weatherClockElementViewId = customR.id.weather_clock_date, clock = clock, elementKey = WeatherClockElementKeys.dateElementKey, modifier = modifier, @@ -88,7 +88,7 @@ constructor( modifier: Modifier = Modifier, ) { WeatherElement( - weatherClockElementViewId = customizationR.id.weather_clock_weather_icon, + weatherClockElementViewId = customR.id.weather_clock_weather_icon, clock = clock, elementKey = WeatherClockElementKeys.weatherIconElementKey, modifier = modifier.wrapContentSize(), @@ -101,7 +101,7 @@ constructor( modifier: Modifier = Modifier, ) { WeatherElement( - weatherClockElementViewId = customizationR.id.weather_clock_alarm_dnd, + weatherClockElementViewId = customR.id.weather_clock_alarm_dnd, clock = clock, elementKey = WeatherClockElementKeys.dndAlarmElementKey, modifier = modifier.wrapContentSize(), @@ -114,7 +114,7 @@ constructor( modifier: Modifier = Modifier, ) { WeatherElement( - weatherClockElementViewId = customizationR.id.weather_clock_temperature, + weatherClockElementViewId = customR.id.weather_clock_temperature, clock = clock, elementKey = WeatherClockElementKeys.temperatureElementKey, modifier = modifier.wrapContentSize(), @@ -159,7 +159,7 @@ constructor( modifier = Modifier.height(IntrinsicSize.Max) .padding( - horizontal = dimensionResource(customizationR.dimen.clock_padding_start) + horizontal = dimensionResource(customR.dimen.clock_padding_start) ) .burnInAware(aodBurnInViewModel, burnInParams, isClock = true) ) { @@ -168,7 +168,7 @@ constructor( modifier = Modifier.fillMaxSize() .padding( - start = dimensionResource(customizationR.dimen.clock_padding_start) + start = dimensionResource(customR.dimen.clock_padding_start) ) ) { Weather(clock = clock, modifier = Modifier.align(Alignment.TopStart)) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt index 8b9e9274b448..e4c60e166fd5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt @@ -18,6 +18,8 @@ package com.android.systemui.notifications.ui.composable import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.util.fastCoerceAtLeast +import androidx.compose.ui.util.fastCoerceAtMost import com.android.compose.nestedscroll.PriorityNestedScrollConnection /** @@ -44,7 +46,7 @@ fun NotificationScrimNestedScrollConnection( orientation = Orientation.Vertical, // scrolling up and inner content is taller than the scrim, so scrim needs to // expand; content can scroll once scrim is at the minScrimOffset. - canStartPreScroll = { offsetAvailable, offsetBeforeStart -> + canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ -> offsetAvailable < 0 && offsetBeforeStart == 0f && contentHeight() > minVisibleScrimHeight() && @@ -52,36 +54,38 @@ fun NotificationScrimNestedScrollConnection( }, // scrolling down and content is done scrolling to top. After that, the scrim // needs to collapse; collapse the scrim until it is at the maxScrimOffset. - canStartPostScroll = { offsetAvailable, _ -> + canStartPostScroll = { offsetAvailable, _, _ -> offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll()) }, canStartPostFling = { false }, - canContinueScroll = { - val currentHeight = scrimOffset() - minScrimOffset() < currentHeight && currentHeight < maxScrimOffset - }, - canScrollOnFling = true, + canStopOnPreFling = { false }, onStart = { offsetAvailable -> onStart(offsetAvailable) }, - onScroll = { offsetAvailable -> + onScroll = { offsetAvailable, _ -> val currentHeight = scrimOffset() val amountConsumed = if (offsetAvailable > 0) { val amountLeft = maxScrimOffset - currentHeight - offsetAvailable.coerceAtMost(amountLeft) + offsetAvailable.fastCoerceAtMost(amountLeft) } else { val amountLeft = minScrimOffset() - currentHeight - offsetAvailable.coerceAtLeast(amountLeft) + offsetAvailable.fastCoerceAtLeast(amountLeft) } snapScrimOffset(currentHeight + amountConsumed) amountConsumed }, - // Don't consume the velocity on pre/post fling onStop = { velocityAvailable -> onStop(velocityAvailable) if (scrimOffset() < minScrimOffset()) { animateScrimOffset(minScrimOffset()) } - { 0f } + // Don't consume the velocity on pre/post fling + 0f + }, + onCancel = { + onStop(0f) + if (scrimOffset() < minScrimOffset()) { + animateScrimOffset(minScrimOffset()) + } }, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt index a706585deebc..edb05ebd77d1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastCoerceAtLeast import com.android.compose.nestedscroll.PriorityNestedScrollConnection import kotlin.math.max import kotlin.math.roundToInt @@ -86,21 +87,25 @@ fun NotificationStackNestedScrollConnection( ): PriorityNestedScrollConnection { return PriorityNestedScrollConnection( orientation = Orientation.Vertical, - canStartPreScroll = { _, _ -> false }, - canStartPostScroll = { offsetAvailable, offsetBeforeStart -> + canStartPreScroll = { _, _, _ -> false }, + canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ -> offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward() }, canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() }, - canContinueScroll = { stackOffset() > 0f }, - canScrollOnFling = true, + canStopOnPreFling = { false }, onStart = { offsetAvailable -> onStart(offsetAvailable) }, - onScroll = { offsetAvailable -> - onScroll(offsetAvailable) - offsetAvailable + onScroll = { offsetAvailable, _ -> + val minOffset = 0f + val consumed = offsetAvailable.fastCoerceAtLeast(minOffset - stackOffset()) + if (consumed != 0f) { + onScroll(consumed) + } + consumed }, onStop = { velocityAvailable -> onStop(velocityAvailable) - suspend { velocityAvailable } + velocityAvailable }, + onCancel = { onStop(0f) }, ) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 085157ac72b9..7e288ddd3a4c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -27,9 +27,10 @@ import com.android.compose.animation.scene.content.Content import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified import com.android.compose.nestedscroll.PriorityNestedScrollConnection -import com.android.compose.nestedscroll.SuspendedValue import kotlin.math.absoluteValue +internal typealias SuspendedValue<T> = suspend () -> T + internal interface DraggableHandler { /** * Start a drag in the given [startedPosition], with the given [overSlop] and number of @@ -612,7 +613,7 @@ internal class NestedScrollHandlerImpl( return PriorityNestedScrollConnection( orientation = orientation, - canStartPreScroll = { offsetAvailable, offsetBeforeStart -> + canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ -> canChangeScene = if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f @@ -638,7 +639,7 @@ internal class NestedScrollHandlerImpl( isIntercepting = true true }, - canStartPostScroll = { offsetAvailable, offsetBeforeStart -> + canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ -> val behavior: NestedScrollBehavior = when { offsetAvailable > 0f -> topOrLeftBehavior @@ -693,8 +694,7 @@ internal class NestedScrollHandlerImpl( canStart }, - canContinueScroll = { true }, - canScrollOnFling = false, + canStopOnPreFling = { true }, onStart = { offsetAvailable -> val pointersInfo = pointersInfo() dragController = @@ -704,7 +704,7 @@ internal class NestedScrollHandlerImpl( overSlop = if (isIntercepting) 0f else offsetAvailable, ) }, - onScroll = { offsetAvailable -> + onScroll = { offsetAvailable, _ -> val controller = dragController ?: error("Should be called after onStart") // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is @@ -713,10 +713,18 @@ internal class NestedScrollHandlerImpl( }, onStop = { velocityAvailable -> val controller = dragController ?: error("Should be called after onStart") - - controller - .onStop(velocity = velocityAvailable, canChangeContent = canChangeScene) - .also { dragController = null } + try { + controller + .onStop(velocity = velocityAvailable, canChangeContent = canChangeScene) + .invoke() + } finally { + dragController = null + } + }, + onCancel = { + val controller = dragController ?: error("Should be called after onStart") + controller.onStop(velocity = 0f, canChangeContent = canChangeScene) + dragController = null }, ) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 2657d7cf8156..3c3c612c028b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -430,6 +430,10 @@ internal class MutableSceneTransitionLayoutStateImpl( // Replace the transition. transitionStates = transitionStates.subList(0, transitionStates.lastIndex) + transition + + // Make sure it is removed from the finishedTransitions set if it was already + // finished. + finishedTransitions.remove(currentState) } else { // Append the new transition. transitionStates = transitionStates + transition diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt index 205267da151a..f0043e1e89b0 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt @@ -27,7 +27,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified -import com.android.compose.nestedscroll.SuspendedValue import kotlin.math.absoluteValue import kotlinx.coroutines.CompletableDeferred diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt index 4ae323517b26..ecf64b771d1f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt @@ -18,6 +18,8 @@ package com.android.compose.nestedscroll import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.util.fastCoerceAtLeast +import androidx.compose.ui.util.fastCoerceAtMost /** * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the @@ -43,35 +45,32 @@ fun LargeTopAppBarNestedScrollConnection( orientation = Orientation.Vertical, // When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will // expand. Then, you can then scroll down the content. - canStartPreScroll = { offsetAvailable, offsetBeforeStart -> + canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ -> offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight() }, // When swiping down, the content will scroll up until it reaches the top. Then, the // LargeTopAppBar will expand until it reaches its [maxHeight]. - canStartPostScroll = { offsetAvailable, _ -> + canStartPostScroll = { offsetAvailable, _, _ -> offsetAvailable > 0 && height() < maxHeight() }, canStartPostFling = { false }, - canContinueScroll = { - val currentHeight = height() - minHeight() < currentHeight && currentHeight < maxHeight() - }, - canScrollOnFling = true, + canStopOnPreFling = { false }, onStart = { /* do nothing */ }, - onScroll = { offsetAvailable -> + onScroll = { offsetAvailable, _ -> val currentHeight = height() val amountConsumed = if (offsetAvailable > 0) { val amountLeft = maxHeight() - currentHeight - offsetAvailable.coerceAtMost(amountLeft) + offsetAvailable.fastCoerceAtMost(amountLeft) } else { val amountLeft = minHeight() - currentHeight - offsetAvailable.coerceAtLeast(amountLeft) + offsetAvailable.fastCoerceAtLeast(amountLeft) } onHeightChanged(currentHeight + amountConsumed) amountConsumed }, // Don't consume the velocity on pre/post fling - onStop = { { 0f } }, + onStop = { 0f }, + onCancel = { /* do nothing */ }, ) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt index a3641e6635e7..636c55799ff2 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt @@ -16,37 +16,59 @@ package com.android.compose.nestedscroll +import androidx.compose.animation.core.AnimationState +import androidx.compose.animation.core.DecayAnimationSpec +import androidx.compose.animation.core.animateDecay import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.unit.Velocity import com.android.compose.ui.util.SpaceVectorConverter +import kotlin.math.abs import kotlin.math.sign - -internal typealias SuspendedValue<T> = suspend () -> T +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope /** - * This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and - * then decides (via [canStartPreScroll] or [canStartPostScroll]) if it should take over scrolling. - * If it does, it will scroll before its children, until [canContinueScroll] allows it. + * A [NestedScrollConnection] that intercepts scroll events in priority mode. * - * Note: Call [reset] before destroying this object to make sure you always get a call to [onStop] - * after [onStart]. + * Priority mode allows this connection to take control over scroll events within a nested scroll + * hierarchy. When in priority mode, this connection consumes scroll events before its children, + * enabling custom scrolling behaviors like sticky headers. * + * @param orientation The orientation of the scroll. + * @param canStartPreScroll lambda that returns true if the connection can start consuming scroll + * events in pre-scroll mode. + * @param canStartPostScroll lambda that returns true if the connection can start consuming scroll + * events in post-scroll mode. + * @param canStartPostFling lambda that returns true if the connection can start consuming scroll + * events in post-fling mode. + * @param canStopOnPreFling lambda that returns true if the connection can stop consuming scroll + * events in pre-fling (i.e. as soon as the user lifts their fingers). + * @param onStart lambda that is called when the connection starts consuming scroll events. + * @param onScroll lambda that is called when the connection consumes a scroll event and returns the + * consumed amount. + * @param onStop lambda that is called when the connection stops consuming scroll events and returns + * the consumed velocity. + * @param onCancel lambda that is called when the connection is cancelled. * @sample LargeTopAppBarNestedScrollConnection * @sample com.android.compose.animation.scene.NestedScrollHandlerImpl.nestedScrollConnection */ class PriorityNestedScrollConnection( orientation: Orientation, - private val canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean, - private val canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean, + private val canStartPreScroll: + (offsetAvailable: Float, offsetBeforeStart: Float, source: NestedScrollSource) -> Boolean, + private val canStartPostScroll: + (offsetAvailable: Float, offsetBeforeStart: Float, source: NestedScrollSource) -> Boolean, private val canStartPostFling: (velocityAvailable: Float) -> Boolean, - private val canContinueScroll: (source: NestedScrollSource) -> Boolean, - private val canScrollOnFling: Boolean, + private val canStopOnPreFling: () -> Boolean, private val onStart: (offsetAvailable: Float) -> Unit, - private val onScroll: (offsetAvailable: Float) -> Float, - private val onStop: (velocityAvailable: Float) -> SuspendedValue<Float>, + private val onScroll: (offsetAvailable: Float, source: NestedScrollSource) -> Float, + private val onStop: suspend (velocityAvailable: Float) -> Float, + private val onCancel: () -> Unit, ) : NestedScrollConnection, SpaceVectorConverter by SpaceVectorConverter(orientation) { /** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */ @@ -54,6 +76,9 @@ class PriorityNestedScrollConnection( private var offsetScrolledBeforePriorityMode = 0f + /** This job allows us to interrupt the onStop animation */ + private var onStopJob: Deferred<Float> = CompletableDeferred(0f) + override fun onPostScroll( consumed: Offset, available: Offset, @@ -64,62 +89,48 @@ class PriorityNestedScrollConnection( // the beginning or from the last fling gesture. val offsetBeforeStart = offsetScrolledBeforePriorityMode - availableFloat - if ( - isPriorityMode || - (source == NestedScrollSource.SideEffect && !canScrollOnFling) || - !canStartPostScroll(availableFloat, offsetBeforeStart) - ) { + if (isPriorityMode || !canStartPostScroll(availableFloat, offsetBeforeStart, source)) { // The priority mode cannot start so we won't consume the available offset. return Offset.Zero } - return onPriorityStart(availableFloat).toOffset() + return start(availableFloat, source).toOffset() } override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { if (!isPriorityMode) { - if (source == NestedScrollSource.UserInput || canScrollOnFling) { - val availableFloat = available.toFloat() - if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode)) { - return onPriorityStart(availableFloat).toOffset() - } - // We want to track the amount of offset consumed before entering priority mode - offsetScrolledBeforePriorityMode += availableFloat + val availableFloat = available.toFloat() + if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode, source)) { + return start(availableFloat, source).toOffset() } - - return Offset.Zero - } - - val availableFloat = available.toFloat() - if (!canContinueScroll(source)) { - // Step 3a: We have lost priority and we no longer need to intercept scroll events. - onPriorityStop(velocity = 0f) - - // We've just reset offsetScrolledBeforePriorityMode to 0f // We want to track the amount of offset consumed before entering priority mode offsetScrolledBeforePriorityMode += availableFloat - return Offset.Zero } - // Step 2: We have the priority and can consume the scroll events. - return onScroll(availableFloat).toOffset() + return scroll(available.toFloat(), source).toOffset() } override suspend fun onPreFling(available: Velocity): Velocity { - if (isPriorityMode && canScrollOnFling) { - // We don't want to consume the velocity, we prefer to continue receiving scroll events. + if (!isPriorityMode) { + resetOffsetTracker() return Velocity.Zero } - // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the speed - // of the fling gesture. - return onPriorityStop(velocity = available.toFloat()).invoke().toVelocity() + + if (canStopOnPreFling()) { + // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the + // velocity of the fling gesture. + return stop(velocityAvailable = available.toFloat()).toVelocity() + } + + // We don't want to consume the velocity, we prefer to continue receiving scroll events. + return Velocity.Zero } override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { val availableFloat = available.toFloat() if (isPriorityMode) { - return onPriorityStop(velocity = availableFloat).invoke().toVelocity() + return stop(velocityAvailable = availableFloat).toVelocity() } if (!canStartPostFling(availableFloat)) { @@ -131,10 +142,14 @@ class PriorityNestedScrollConnection( // TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the // overscroll behavior on the Scene level. val smallOffset = availableFloat.sign - onPriorityStart(availableOffset = smallOffset) + start( + availableOffset = smallOffset, + source = NestedScrollSource.SideEffect, + skipScroll = true, + ) // This is the last event of a scroll gesture. - return onPriorityStop(availableFloat).invoke().toVelocity() + return stop(availableFloat).toVelocity() } /** @@ -143,36 +158,76 @@ class PriorityNestedScrollConnection( * TODO(b/303224944) This method should be removed. */ fun reset() { - // Step 3c: To ensure that an onStop is always called for every onStart. - onPriorityStop(velocity = 0f) + if (isPriorityMode) { + // Step 3c: To ensure that an onStop (or onCancel) is always called for every onStart. + cancel() + } else { + resetOffsetTracker() + } } - private fun onPriorityStart(availableOffset: Float): Float { - if (isPriorityMode) { - error("This should never happen, onPriorityStart() was called when isPriorityMode") + private fun shouldStop(consumed: Float): Boolean { + return consumed == 0f + } + + private fun start( + availableOffset: Float, + source: NestedScrollSource, + skipScroll: Boolean = false, + ): Float { + check(!isPriorityMode) { + "This should never happen, start() was called when isPriorityMode" } // Step 1: It's our turn! We start capturing scroll events when one of our children has an // available offset following a scroll event. isPriorityMode = true + onStopJob.cancel() + // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is // lifted (step 3b), or this object has been destroyed (step 3c). onStart(availableOffset) - return onScroll(availableOffset) + return if (skipScroll) 0f else scroll(availableOffset, source) } - private fun onPriorityStop(velocity: Float): SuspendedValue<Float> { - // We can restart tracking the consumed offsets from scratch. - offsetScrolledBeforePriorityMode = 0f + private fun scroll(offsetAvailable: Float, source: NestedScrollSource): Float { + // Step 2: We have the priority and can consume the scroll events. + val consumedByScroll = onScroll(offsetAvailable, source) - if (!isPriorityMode) { - return { 0f } + if (shouldStop(consumedByScroll)) { + // Step 3a: We have lost priority and we no longer need to intercept scroll events. + cancel() + + // We've just reset offsetScrolledBeforePriorityMode to 0f + // We want to track the amount of offset consumed before entering priority mode + offsetScrolledBeforePriorityMode += offsetAvailable - consumedByScroll } + return consumedByScroll + } + + /** Reset the tracking of consumed offsets before entering in priority mode. */ + private fun resetOffsetTracker() { + offsetScrolledBeforePriorityMode = 0f + } + + private suspend fun stop(velocityAvailable: Float): Float { + check(isPriorityMode) { "This should never happen, stop() was called before start()" } isPriorityMode = false + resetOffsetTracker() - return onStop(velocity) + return coroutineScope { + onStopJob = async { onStop(velocityAvailable) } + onStopJob.await() + } + } + + private fun cancel() { + check(isPriorityMode) { "This should never happen, cancel() was called before start()" } + isPriorityMode = false + resetOffsetTracker() + onCancel() } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index ecef6be49df8..57b9423e85d1 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -39,7 +39,6 @@ import com.android.compose.animation.scene.TestScenes.SceneC import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.content.state.TransitionState.Transition import com.android.compose.animation.scene.subjects.assertThat -import com.android.compose.nestedscroll.SuspendedValue import com.android.compose.test.MonotonicClockTestScope import com.android.compose.test.runMonotonicClockTest import com.android.compose.test.transition diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt index c8f6e6d99933..3df608717414 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt @@ -46,7 +46,6 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Velocity import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.modifiers.thenIf -import com.android.compose.nestedscroll.SuspendedValue import com.google.common.truth.Truth.assertThat import kotlin.properties.Delegates import kotlinx.coroutines.coroutineScope diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index f3a34884c756..f5bb5ba032c2 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -727,4 +727,45 @@ class SceneTransitionLayoutStateTest { // The previous job is cancelled and does not infinitely collect the progress. job.join() } + + @Test + fun replacedTransitionIsRemovedFromFinishedTransitions() = runTest { + val state = MutableSceneTransitionLayoutState(SceneA) + + val aToB = + transition( + SceneA, + SceneB, + onFreezeAndAnimate = { + // Do nothing, so that this transition stays in the transitionStates list and we + // can finish() it manually later. + }, + ) + val replacingAToB = transition(SceneB, SceneC) + val replacingBToC = transition(SceneB, SceneC, replacedTransition = replacingAToB) + + // Start A => B. + val aToBJob = state.startTransitionImmediately(animationScope = this, aToB) + + // Start B => C and immediately finish it. It will be flagged as finished in + // STLState.finishedTransitions given that A => B is not finished yet. + val bToCJob = state.startTransitionImmediately(animationScope = this, replacingAToB) + replacingAToB.finish() + bToCJob.join() + + // Start a new B => C that replaces the previously finished B => C. + val replacingBToCJob = + state.startTransitionImmediately(animationScope = this, replacingBToC) + + // Finish A => B. + aToB.finish() + aToBJob.join() + + // Finish the new B => C. + replacingBToC.finish() + replacingBToCJob.join() + + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneC) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 28d0a473935d..1711f3145cae 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -102,26 +102,22 @@ class SwipeToSceneTest { modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.debugName), ) { scene( - SceneA, + key = SceneA, userActions = if (swipesEnabled()) - mapOf( - Swipe.Left to SceneB, - Swipe.Down to TestScenes.SceneC, - Swipe.Up to SceneB, - ) + mapOf(Swipe.Left to SceneB, Swipe.Down to SceneC, Swipe.Up to SceneB) else emptyMap(), ) { Box(Modifier.fillMaxSize()) } scene( - SceneB, + key = SceneB, userActions = if (swipesEnabled()) mapOf(Swipe.Right to SceneA) else emptyMap(), ) { Box(Modifier.fillMaxSize()) } scene( - TestScenes.SceneC, + key = SceneC, userActions = if (swipesEnabled()) mapOf( @@ -196,7 +192,7 @@ class SwipeToSceneTest { // Drag is in progress, so currentScene = SceneA and progress = 56dp / LayoutHeight transition = assertThat(layoutState.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneC) assertThat(transition).hasCurrentScene(SceneA) assertThat(transition).hasProgress(56.dp / LayoutHeight) assertThat(transition).isInitiatedByUserInput() @@ -206,15 +202,15 @@ class SwipeToSceneTest { rule.onRoot().performTouchInput { up() } transition = assertThat(layoutState.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(TestScenes.SceneC) - assertThat(transition).hasCurrentScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneC) + assertThat(transition).hasCurrentScene(SceneC) assertThat(transition).hasProgress(56.dp / LayoutHeight) assertThat(transition).isInitiatedByUserInput() // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) } @Test @@ -271,20 +267,20 @@ class SwipeToSceneTest { // We should be animating to C (currentScene = SceneC). transition = assertThat(layoutState.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(TestScenes.SceneC) - assertThat(transition).hasCurrentScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneC) + assertThat(transition).hasCurrentScene(SceneC) assertThat(transition).hasProgress(55.dp / LayoutHeight) // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) } @Test fun multiPointerSwipe() { // Start at scene C. - val layoutState = layoutState(TestScenes.SceneC) + val layoutState = layoutState(SceneC) // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is // detected as a drag event. @@ -295,7 +291,7 @@ class SwipeToSceneTest { } assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) // Swipe down with two fingers. rule.onRoot().performTouchInput { @@ -307,7 +303,7 @@ class SwipeToSceneTest { // We are transitioning to B because we used 2 fingers. val transition = assertThat(layoutState.transitionState).isSceneTransition() - assertThat(transition).hasFromScene(TestScenes.SceneC) + assertThat(transition).hasFromScene(SceneC) assertThat(transition).hasToScene(SceneB) // Release the fingers and wait for the animation to end. We are back to C because we only @@ -315,13 +311,13 @@ class SwipeToSceneTest { rule.onRoot().performTouchInput { repeat(2) { i -> up(pointerId = i) } } rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) } @Test fun defaultEdgeSwipe() { // Start at scene C. - val layoutState = layoutState(TestScenes.SceneC) + val layoutState = layoutState(SceneC) // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is // detected as a drag event. @@ -332,7 +328,7 @@ class SwipeToSceneTest { } assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) // Swipe down from the top edge. rule.onRoot().performTouchInput { @@ -342,7 +338,7 @@ class SwipeToSceneTest { // We are transitioning to B (and not A) because we started from the top edge. var transition = assertThat(layoutState.transitionState).isSceneTransition() - assertThat(transition).hasFromScene(TestScenes.SceneC) + assertThat(transition).hasFromScene(SceneC) assertThat(transition).hasToScene(SceneB) // Release the fingers and wait for the animation to end. We are back to C because we only @@ -350,7 +346,7 @@ class SwipeToSceneTest { rule.onRoot().performTouchInput { up() } rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) // Swipe right from the left edge. rule.onRoot().performTouchInput { @@ -360,7 +356,7 @@ class SwipeToSceneTest { // We are transitioning to B (and not A) because we started from the left edge. transition = assertThat(layoutState.transitionState).isSceneTransition() - assertThat(transition).hasFromScene(TestScenes.SceneC) + assertThat(transition).hasFromScene(SceneC) assertThat(transition).hasToScene(SceneB) // Release the fingers and wait for the animation to end. We are back to C because we only @@ -368,7 +364,7 @@ class SwipeToSceneTest { rule.onRoot().performTouchInput { up() } rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) } @Test @@ -434,7 +430,7 @@ class SwipeToSceneTest { // We should still correctly compute that we are swiping down to scene C. var transition = assertThat(layoutState.transitionState).isSceneTransition() - assertThat(transition).hasToScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneC) // Release the finger, animating back to scene A. rule.onRoot().performTouchInput { up() } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt index badc43bd3e0f..1a3b86b936df 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt @@ -34,30 +34,31 @@ class PriorityNestedScrollConnectionTest { private var canStartPreScroll = false private var canStartPostScroll = false private var canStartPostFling = false - private var canContinueScroll = false + private var canStopOnPreFling = true private var isStarted = false private var lastScroll: Float? = null - private var returnOnScroll = 0f + private var consumeScroll = true private var lastStop: Float? = null - private var returnOnStop = 0f + private var isCancelled: Boolean = false + private var consumeStop = true private val scrollConnection = PriorityNestedScrollConnection( orientation = Orientation.Vertical, - canStartPreScroll = { _, _ -> canStartPreScroll }, - canStartPostScroll = { _, _ -> canStartPostScroll }, + canStartPreScroll = { _, _, _ -> canStartPreScroll }, + canStartPostScroll = { _, _, _ -> canStartPostScroll }, canStartPostFling = { canStartPostFling }, - canContinueScroll = { canContinueScroll }, - canScrollOnFling = false, + canStopOnPreFling = { canStopOnPreFling }, onStart = { isStarted = true }, - onScroll = { - lastScroll = it - returnOnScroll + onScroll = { offsetAvailable, _ -> + lastScroll = offsetAvailable + if (consumeScroll) offsetAvailable else 0f }, onStop = { lastStop = it - { returnOnStop } + if (consumeStop) it else 0f }, + onCancel = { isCancelled = true }, ) @Test @@ -85,7 +86,7 @@ class PriorityNestedScrollConnectionTest { canStartPostScroll = true scrollConnection.onPostScroll( consumed = Offset.Zero, - available = Offset.Zero, + available = Offset(1f, 1f), source = UserInput, ) } @@ -136,45 +137,55 @@ class PriorityNestedScrollConnectionTest { @Test fun step2_onPriorityMode_shouldContinueIfAllowed() { startPriorityModePostScroll() - canContinueScroll = true - scrollConnection.onPreScroll(available = Offset(1f, 1f), source = UserInput) + val scroll1 = scrollConnection.onPreScroll(available = Offset(0f, 1f), source = UserInput) assertThat(lastScroll).isEqualTo(1f) + assertThat(scroll1.y).isEqualTo(1f) - canContinueScroll = false - scrollConnection.onPreScroll(available = Offset(2f, 2f), source = UserInput) - assertThat(lastScroll).isNotEqualTo(2f) - assertThat(lastScroll).isEqualTo(1f) + consumeScroll = false + val scroll2 = scrollConnection.onPreScroll(available = Offset(0f, 2f), source = UserInput) + assertThat(lastScroll).isEqualTo(2f) + assertThat(scroll2.y).isEqualTo(0f) } @Test - fun step3a_onPriorityMode_shouldStopIfCannotContinue() { + fun step3a_onPriorityMode_shouldCancelIfCannotContinue() { startPriorityModePostScroll() - canContinueScroll = false + consumeScroll = false - scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput) + scrollConnection.onPreScroll(available = Offset(0f, 1f), source = UserInput) - assertThat(lastStop).isNotNull() + assertThat(isCancelled).isTrue() } @Test fun step3b_onPriorityMode_shouldStopOnFling() = runTest { startPriorityModePostScroll() - canContinueScroll = true scrollConnection.onPreFling(available = Velocity.Zero) - assertThat(lastStop).isNotNull() + assertThat(lastStop).isEqualTo(0f) + } + + @Test + fun ifCannotStopOnPreFling_shouldStopOnPostFling() = runTest { + startPriorityModePostScroll() + canStopOnPreFling = false + + scrollConnection.onPreFling(available = Velocity.Zero) + assertThat(lastStop).isNull() + + scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero) + assertThat(lastStop).isEqualTo(0f) } @Test - fun step3c_onPriorityMode_shouldStopOnReset() { + fun step3c_onPriorityMode_shouldCancelOnReset() { startPriorityModePostScroll() - canContinueScroll = true scrollConnection.reset() - assertThat(lastStop).isNotNull() + assertThat(isCancelled).isTrue() } @Test diff --git a/packages/SystemUI/customization/res/values/ids.xml b/packages/SystemUI/customization/res/values/ids.xml index ec466f041179..3a3e06bdd377 100644 --- a/packages/SystemUI/customization/res/values/ids.xml +++ b/packages/SystemUI/customization/res/values/ids.xml @@ -1,5 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <resources> + <item type="id" name="lockscreen_clock_view_large" /> + <item type="id" name="lockscreen_clock_view" /> + <!-- View ids for elements in large weather clock --> <item type="id" name="weather_clock_time" /> <item type="id" name="weather_clock_date" /> diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt index 9b94c91a348c..eedddb28ff89 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt @@ -30,16 +30,16 @@ import com.android.systemui.plugins.clocks.ClockFaceEvents import com.android.systemui.plugins.clocks.ClockReactiveSetting import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData -import com.android.systemui.shared.clocks.view.DigitalClockFaceView import com.android.systemui.shared.clocks.view.FlexClockView +import com.android.systemui.shared.clocks.view.SimpleDigitalClockTextView import java.util.Locale import java.util.TimeZone class ComposedDigitalLayerController( private val ctx: Context, - private val assets: AssetLoader, + private val resources: Resources, + private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources private val layer: ComposedDigitalHandLayer, - private val isLargeClock: Boolean, messageBuffer: MessageBuffer, ) : SimpleClockLayerController { private val logger = Logger(messageBuffer, ComposedDigitalLayerController::class.simpleName!!) @@ -48,34 +48,22 @@ class ComposedDigitalLayerController( val dozeState = DefaultClockController.AnimationState(1F) var isRegionDark = true - override var view: DigitalClockFaceView = - when (layer.customizedView) { - "FlexClockView" -> FlexClockView(ctx, assets, messageBuffer) - else -> { - throw IllegalStateException("CustomizedView string is not valid") - } - } - - // Matches LayerControllerConstructor - internal constructor( - ctx: Context, - assets: AssetLoader, - layer: ClockLayer, - isLargeClock: Boolean, - messageBuffer: MessageBuffer, - ) : this(ctx, assets, layer as ComposedDigitalHandLayer, isLargeClock, messageBuffer) + override val view = FlexClockView(ctx, assets, messageBuffer) init { layer.digitalLayers.forEach { + val childView = SimpleDigitalClockTextView(ctx, messageBuffer) val controller = - SimpleClockLayerController.Factory.create( + SimpleDigitalHandLayerController( ctx, + resources, assets, - it, - isLargeClock, + it as DigitalHandLayer, + childView, messageBuffer, ) - view.addView(controller.view) + + view.addView(childView) layerControllers.add(controller) } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index ac268420fb75..3903dbaf64c6 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -53,11 +53,8 @@ class DefaultClockProvider( } return if (clockReactiveVariants) { - // TODO handle the case here where only the smallClock message buffer is added - val assetLoader = - AssetLoader(ctx, ctx, "clocks/", messageBuffers?.smallClockMessageBuffer!!) - - SimpleClockController(ctx, assetLoader, FLEX_DESIGN, messageBuffers) + val assets = AssetLoader(ctx, ctx, "clocks/", messageBuffers!!.infraMessageBuffer) + FlexClockController(ctx, resources, assets, FLEX_DESIGN, messageBuffers) } else { DefaultClockController( ctx, @@ -82,6 +79,9 @@ class DefaultClockProvider( resources.getString(R.string.clock_default_description), // TODO(b/352049256): Update placeholder to actual resource resources.getDrawable(R.drawable.clock_default_thumbnail, null), + isReactiveToTone = true, + isReactiveToTouch = clockReactiveVariants, + axes = listOf(), // TODO: Ater some picker definition ) } @@ -118,9 +118,9 @@ class DefaultClockProvider( alignment = DigitalAlignment( HorizontalAlignment.CENTER, - VerticalAlignment.CENTER + VerticalAlignment.CENTER, ), - dateTimeFormat = "hh" + dateTimeFormat = "hh", ), DigitalHandLayer( layerBounds = LayerBounds.FIT, @@ -146,9 +146,9 @@ class DefaultClockProvider( alignment = DigitalAlignment( HorizontalAlignment.CENTER, - VerticalAlignment.CENTER + VerticalAlignment.CENTER, ), - dateTimeFormat = "hh" + dateTimeFormat = "hh", ), DigitalHandLayer( layerBounds = LayerBounds.FIT, @@ -174,9 +174,9 @@ class DefaultClockProvider( alignment = DigitalAlignment( HorizontalAlignment.CENTER, - VerticalAlignment.CENTER + VerticalAlignment.CENTER, ), - dateTimeFormat = "mm" + dateTimeFormat = "mm", ), DigitalHandLayer( layerBounds = LayerBounds.FIT, @@ -202,11 +202,11 @@ class DefaultClockProvider( alignment = DigitalAlignment( HorizontalAlignment.CENTER, - VerticalAlignment.CENTER + VerticalAlignment.CENTER, ), - dateTimeFormat = "mm" - ) - ) + dateTimeFormat = "mm", + ), + ), ) ) @@ -230,7 +230,7 @@ class DefaultClockProvider( renderType = RenderType.CHANGE_WEIGHT, ), alignment = DigitalAlignment(HorizontalAlignment.LEFT, null), - dateTimeFormat = "h:mm" + dateTimeFormat = "h:mm", ) ) @@ -239,7 +239,7 @@ class DefaultClockProvider( name = "@string/clock_default_name", description = "@string/clock_default_description", large = ClockFace(layers = largeLayer), - small = ClockFace(layers = smallLayer) + small = ClockFace(layers = smallLayer), ) } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt index ec7779825bda..b8ebd0ff559b 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt @@ -18,7 +18,7 @@ package com.android.systemui.shared.clocks import android.content.Context import android.content.res.Resources -import com.android.systemui.monet.Style as MonetStyle +import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockConfig import com.android.systemui.plugins.clocks.ClockController @@ -27,21 +27,24 @@ import com.android.systemui.plugins.clocks.ClockMessageBuffers import com.android.systemui.plugins.clocks.ClockReactiveSetting import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData +import com.android.systemui.shared.clocks.view.FlexClockView import java.io.PrintWriter import java.util.Locale import java.util.TimeZone -/** Controller for a simple json specified clock */ -class SimpleClockController( +/** Controller for the default flex clock */ +class FlexClockController( private val ctx: Context, - private val assets: AssetLoader, - val design: ClockDesign, + private val resources: Resources, + private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources + val design: ClockDesign, // TODO(b/364680879): Remove when done inlining val messageBuffers: ClockMessageBuffers?, ) : ClockController { override val smallClock = run { val buffer = messageBuffers?.smallClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER - SimpleClockFaceController( + FlexClockFaceController( ctx, + resources, assets.copy(messageBuffer = buffer), design.small ?: design.large!!, false, @@ -51,8 +54,9 @@ class SimpleClockController( override val largeClock = run { val buffer = messageBuffers?.largeClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER - SimpleClockFaceController( + FlexClockFaceController( ctx, + resources, assets.copy(messageBuffer = buffer), design.large ?: design.small!!, true, @@ -62,16 +66,10 @@ class SimpleClockController( override val config: ClockConfig by lazy { ClockConfig( - design.id, - design.name?.let { assets.tryReadString(it) ?: it } ?: "", - design.description?.let { assets.tryReadString(it) ?: it } ?: "", - isReactiveToTone = - design.colorPalette == null || design.colorPalette == MonetStyle.CLOCK, - useAlternateSmartspaceAODTransition = - smallClock.config.hasCustomWeatherDataDisplay || - largeClock.config.hasCustomWeatherDataDisplay, - useCustomClockScene = - smallClock.config.useCustomClockScene || largeClock.config.useCustomClockScene, + DEFAULT_CLOCK_ID, + resources.getString(R.string.clock_default_name), + resources.getString(R.string.clock_default_description), + isReactiveToTone = true, ) } @@ -80,8 +78,8 @@ class SimpleClockController( override var isReactiveTouchInteractionEnabled = false set(value) { field = value - smallClock.events.isReactiveTouchInteractionEnabled = value - largeClock.events.isReactiveTouchInteractionEnabled = value + val view = largeClock.view as FlexClockView + view.isReactiveTouchInteractionEnabled = value } override fun onTimeZoneChanged(timeZone: TimeZone) { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt new file mode 100644 index 000000000000..ef24d2ad3071 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt @@ -0,0 +1,261 @@ +/* + * 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.shared.clocks + +import android.content.Context +import android.content.res.Resources +import android.graphics.Rect +import android.view.Gravity +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.widget.FrameLayout +import com.android.systemui.customization.R +import com.android.systemui.log.core.MessageBuffer +import com.android.systemui.plugins.clocks.AlarmData +import com.android.systemui.plugins.clocks.ClockAnimations +import com.android.systemui.plugins.clocks.ClockEvents +import com.android.systemui.plugins.clocks.ClockFaceConfig +import com.android.systemui.plugins.clocks.ClockFaceController +import com.android.systemui.plugins.clocks.ClockFaceEvents +import com.android.systemui.plugins.clocks.ClockFaceLayout +import com.android.systemui.plugins.clocks.ClockReactiveSetting +import com.android.systemui.plugins.clocks.DefaultClockFaceLayout +import com.android.systemui.plugins.clocks.WeatherData +import com.android.systemui.plugins.clocks.ZenData +import com.android.systemui.shared.clocks.view.FlexClockView +import com.android.systemui.shared.clocks.view.SimpleDigitalClockTextView +import java.util.Locale +import java.util.TimeZone +import kotlin.math.max + +// TODO(b/364680879): Merge w/ ComposedDigitalLayerController +class FlexClockFaceController( + ctx: Context, + private val resources: Resources, + val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources + face: ClockFace, + private val isLargeClock: Boolean, + messageBuffer: MessageBuffer, +) : ClockFaceController { + override val view: View + get() = layerController.view + + override val config = + ClockFaceConfig( + hasCustomPositionUpdatedAnimation = false // TODO(b/364673982) + ) + + private val keyguardLargeClockTopMargin = + resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin) + val layerController: SimpleClockLayerController + val timespecHandler = DigitalTimespecHandler(DigitalTimespec.TIME_FULL_FORMAT, "hh:mm") + + init { + val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + lp.gravity = Gravity.CENTER + + val layer = face.layers[0] + + layerController = + if (isLargeClock) + ComposedDigitalLayerController( + ctx, + resources, + assets, + layer as ComposedDigitalHandLayer, + messageBuffer, + ) + else { + val childView = SimpleDigitalClockTextView(ctx, messageBuffer) + SimpleDigitalHandLayerController( + ctx, + resources, + assets, + layer as DigitalHandLayer, + childView, + messageBuffer, + ) + } + layerController.view.layoutParams = lp + } + + override val layout: ClockFaceLayout = + DefaultClockFaceLayout(view).apply { + views[0].id = + if (isLargeClock) R.id.lockscreen_clock_view_large else R.id.lockscreen_clock_view + } + + override val events = FlexClockFaceEvents() + + // TODO(b/364680879): Remove ClockEvents + inner class FlexClockFaceEvents : ClockEvents, ClockFaceEvents { + override var isReactiveTouchInteractionEnabled = false + get() = field + set(value) { + field = value + layerController.events.isReactiveTouchInteractionEnabled = value + } + + override fun onTimeTick() { + timespecHandler.updateTime() + view.contentDescription = timespecHandler.getContentDescription() + layerController.faceEvents.onTimeTick() + } + + override fun onTimeZoneChanged(timeZone: TimeZone) { + timespecHandler.timeZone = timeZone + layerController.events.onTimeZoneChanged(timeZone) + } + + override fun onTimeFormatChanged(is24Hr: Boolean) { + timespecHandler.is24Hr = is24Hr + layerController.events.onTimeFormatChanged(is24Hr) + } + + override fun onLocaleChanged(locale: Locale) { + timespecHandler.updateLocale(locale) + layerController.events.onLocaleChanged(locale) + } + + override fun onFontSettingChanged(fontSizePx: Float) { + layerController.faceEvents.onFontSettingChanged(fontSizePx) + } + + override fun onColorPaletteChanged(resources: Resources) { + layerController.events.onColorPaletteChanged(resources) + layerController.updateColors() + } + + override fun onSeedColorChanged(seedColor: Int?) { + layerController.events.onSeedColorChanged(seedColor) + layerController.updateColors() + } + + override fun onRegionDarknessChanged(isRegionDark: Boolean) { + layerController.faceEvents.onRegionDarknessChanged(isRegionDark) + } + + override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {} + + /** + * targetRegion passed to all customized clock applies counter translationY of + * KeyguardStatusView and keyguard_large_clock_top_margin from default clock + */ + override fun onTargetRegionChanged(targetRegion: Rect?) { + // When a clock needs to be aligned with screen, like weather clock + // it needs to offset back the translation of keyguard_large_clock_top_margin + if (isLargeClock && (view as FlexClockView).isAlignedWithScreen()) { + val topMargin = keyguardLargeClockTopMargin + targetRegion?.let { + val (_, yDiff) = computeLayoutDiff(view, it, isLargeClock) + // In LS, we use yDiff to counter translate + // the translation of KeyguardLargeClockTopMargin + // With the targetRegion passed from picker, + // we will have yDiff = 0, no translation is needed for weather clock + if (yDiff.toInt() != 0) view.translationY = yDiff - topMargin / 2 + } + return + } + + var maxWidth = 0f + var maxHeight = 0f + + layerController.faceEvents.onTargetRegionChanged(targetRegion) + maxWidth = max(maxWidth, view.layoutParams.width.toFloat()) + maxHeight = max(maxHeight, view.layoutParams.height.toFloat()) + + val lp = + if (maxHeight <= 0 || maxWidth <= 0 || targetRegion == null) { + // No specified width/height. Just match parent size. + FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + } else { + // Scale to fit in targetRegion based on largest child elements. + val ratio = maxWidth / maxHeight + val targetRatio = targetRegion.width() / targetRegion.height().toFloat() + val scale = + if (ratio > targetRatio) targetRegion.width() / maxWidth + else targetRegion.height() / maxHeight + + FrameLayout.LayoutParams( + (maxWidth * scale).toInt(), + (maxHeight * scale).toInt(), + ) + } + + lp.gravity = Gravity.CENTER + view.layoutParams = lp + targetRegion?.let { + val (xDiff, yDiff) = computeLayoutDiff(view, it, isLargeClock) + view.translationX = xDiff + view.translationY = yDiff + } + } + + override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {} + + override fun onWeatherDataChanged(data: WeatherData) { + layerController.events.onWeatherDataChanged(data) + } + + override fun onAlarmDataChanged(data: AlarmData) { + layerController.events.onAlarmDataChanged(data) + } + + override fun onZenDataChanged(data: ZenData) { + layerController.events.onZenDataChanged(data) + } + } + + override val animations = + object : ClockAnimations { + override fun enter() { + layerController.animations.enter() + } + + override fun doze(fraction: Float) { + layerController.animations.doze(fraction) + } + + override fun fold(fraction: Float) { + layerController.animations.fold(fraction) + } + + override fun charge() { + layerController.animations.charge() + } + + override fun onPickerCarouselSwiping(swipingFraction: Float) { + face.pickerScale?.let { + view.scaleX = swipingFraction * (1 - it.scaleX) + it.scaleX + view.scaleY = swipingFraction * (1 - it.scaleY) + it.scaleY + } + if (isLargeClock && !(view as FlexClockView).isAlignedWithScreen()) { + view.translationY = keyguardLargeClockTopMargin / 2F * swipingFraction + } + layerController.animations.onPickerCarouselSwiping(swipingFraction) + view.invalidate() + } + + override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) { + layerController.animations.onPositionUpdated(fromLeft, direction, fraction) + } + + override fun onPositionUpdated(distance: Float, fraction: Float) { + layerController.animations.onPositionUpdated(distance, fraction) + } + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt deleted file mode 100644 index ef398d1a52a0..000000000000 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt +++ /dev/null @@ -1,314 +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.shared.clocks - -import android.content.Context -import android.content.res.Resources -import android.graphics.Rect -import android.view.Gravity -import android.view.View -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.widget.FrameLayout -import com.android.systemui.log.core.MessageBuffer -import com.android.systemui.plugins.clocks.AlarmData -import com.android.systemui.plugins.clocks.ClockAnimations -import com.android.systemui.plugins.clocks.ClockEvents -import com.android.systemui.plugins.clocks.ClockFaceConfig -import com.android.systemui.plugins.clocks.ClockFaceController -import com.android.systemui.plugins.clocks.ClockFaceEvents -import com.android.systemui.plugins.clocks.ClockFaceLayout -import com.android.systemui.plugins.clocks.ClockReactiveSetting -import com.android.systemui.plugins.clocks.ClockTickRate -import com.android.systemui.plugins.clocks.DefaultClockFaceLayout -import com.android.systemui.plugins.clocks.WeatherData -import com.android.systemui.plugins.clocks.ZenData -import com.android.systemui.shared.clocks.view.DigitalClockFaceView -import java.util.Locale -import java.util.TimeZone -import kotlin.math.max - -interface ClockEventUnion : ClockEvents, ClockFaceEvents - -class SimpleClockFaceController( - ctx: Context, - val assets: AssetLoader, - face: ClockFace, - isLargeClock: Boolean, - messageBuffer: MessageBuffer, -) : ClockFaceController { - override val view: View - override val config: ClockFaceConfig by lazy { - ClockFaceConfig( - hasCustomWeatherDataDisplay = layers.any { it.config.hasCustomWeatherDataDisplay }, - hasCustomPositionUpdatedAnimation = - layers.any { it.config.hasCustomPositionUpdatedAnimation }, - tickRate = getTickRate(), - useCustomClockScene = layers.any { it.config.useCustomClockScene }, - ) - } - - val layers = mutableListOf<SimpleClockLayerController>() - - val timespecHandler = DigitalTimespecHandler(DigitalTimespec.TIME_FULL_FORMAT, "hh:mm") - - init { - val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) - lp.gravity = Gravity.CENTER - view = - if (face.layers.size == 1) { - // Optimize a clocks with a single layer by excluding the face level view group. We - // expect the view container from the host process to always be a FrameLayout. - val layer = face.layers[0] - val controller = - SimpleClockLayerController.Factory.create( - ctx, - assets, - layer, - isLargeClock, - messageBuffer, - ) - layers.add(controller) - controller.view.layoutParams = lp - controller.view - } else { - // For multiple views, we use an intermediate RelativeLayout so that we can do some - // intelligent laying out between the children views. - val group = SimpleClockRelativeLayout(ctx, face.faceLayout) - group.layoutParams = lp - group.gravity = Gravity.CENTER - group.clipChildren = false - for (layer in face.layers) { - face.faceLayout?.let { - if (layer is DigitalHandLayer) { - layer.faceLayout = it - } - } - val controller = - SimpleClockLayerController.Factory.create( - ctx, - assets, - layer, - isLargeClock, - messageBuffer, - ) - group.addView(controller.view) - layers.add(controller) - } - group - } - } - - override val layout: ClockFaceLayout = - DefaultClockFaceLayout(view).apply { - views[0].id = - if (isLargeClock) { - assets.getResourcesId("lockscreen_clock_view_large") - } else { - assets.getResourcesId("lockscreen_clock_view") - } - } - - override val events = - object : ClockEventUnion { - override var isReactiveTouchInteractionEnabled = false - get() = field - set(value) { - field = value - layers.forEach { it.events.isReactiveTouchInteractionEnabled = value } - } - - override fun onTimeTick() { - timespecHandler.updateTime() - if ( - config.tickRate == ClockTickRate.PER_MINUTE || - view.contentDescription != timespecHandler.getContentDescription() - ) { - view.contentDescription = timespecHandler.getContentDescription() - } - layers.forEach { it.faceEvents.onTimeTick() } - } - - override fun onTimeZoneChanged(timeZone: TimeZone) { - timespecHandler.timeZone = timeZone - layers.forEach { it.events.onTimeZoneChanged(timeZone) } - } - - override fun onTimeFormatChanged(is24Hr: Boolean) { - timespecHandler.is24Hr = is24Hr - layers.forEach { it.events.onTimeFormatChanged(is24Hr) } - } - - override fun onLocaleChanged(locale: Locale) { - timespecHandler.updateLocale(locale) - layers.forEach { it.events.onLocaleChanged(locale) } - } - - override fun onFontSettingChanged(fontSizePx: Float) { - layers.forEach { it.faceEvents.onFontSettingChanged(fontSizePx) } - } - - override fun onColorPaletteChanged(resources: Resources) { - layers.forEach { - it.events.onColorPaletteChanged(resources) - it.updateColors() - } - } - - override fun onSeedColorChanged(seedColor: Int?) { - layers.forEach { - it.events.onSeedColorChanged(seedColor) - it.updateColors() - } - } - - override fun onRegionDarknessChanged(isRegionDark: Boolean) { - layers.forEach { it.faceEvents.onRegionDarknessChanged(isRegionDark) } - } - - override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {} - - /** - * targetRegion passed to all customized clock applies counter translationY of - * KeyguardStatusView and keyguard_large_clock_top_margin from default clock - */ - override fun onTargetRegionChanged(targetRegion: Rect?) { - // When a clock needs to be aligned with screen, like weather clock - // it needs to offset back the translation of keyguard_large_clock_top_margin - if (view is DigitalClockFaceView && view.isAlignedWithScreen()) { - val topMargin = getKeyguardLargeClockTopMargin(assets) - targetRegion?.let { - val (_, yDiff) = computeLayoutDiff(view, it, isLargeClock) - // In LS, we use yDiff to counter translate - // the translation of KeyguardLargeClockTopMargin - // With the targetRegion passed from picker, - // we will have yDiff = 0, no translation is needed for weather clock - if (yDiff.toInt() != 0) view.translationY = yDiff - topMargin / 2 - } - return - } - - var maxWidth = 0f - var maxHeight = 0f - - for (layer in layers) { - layer.faceEvents.onTargetRegionChanged(targetRegion) - maxWidth = max(maxWidth, layer.view.layoutParams.width.toFloat()) - maxHeight = max(maxHeight, layer.view.layoutParams.height.toFloat()) - } - - val lp = - if (maxHeight <= 0 || maxWidth <= 0 || targetRegion == null) { - // No specified width/height. Just match parent size. - FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) - } else { - // Scale to fit in targetRegion based on largest child elements. - val ratio = maxWidth / maxHeight - val targetRatio = targetRegion.width() / targetRegion.height().toFloat() - val scale = - if (ratio > targetRatio) targetRegion.width() / maxWidth - else targetRegion.height() / maxHeight - - FrameLayout.LayoutParams( - (maxWidth * scale).toInt(), - (maxHeight * scale).toInt(), - ) - } - - lp.gravity = Gravity.CENTER - view.layoutParams = lp - targetRegion?.let { - val (xDiff, yDiff) = computeLayoutDiff(view, it, isLargeClock) - view.translationX = xDiff - view.translationY = yDiff - } - } - - override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {} - - override fun onWeatherDataChanged(data: WeatherData) { - layers.forEach { it.events.onWeatherDataChanged(data) } - } - - override fun onAlarmDataChanged(data: AlarmData) { - layers.forEach { it.events.onAlarmDataChanged(data) } - } - - override fun onZenDataChanged(data: ZenData) { - layers.forEach { it.events.onZenDataChanged(data) } - } - } - - override val animations = - object : ClockAnimations { - override fun enter() { - layers.forEach { it.animations.enter() } - } - - override fun doze(fraction: Float) { - layers.forEach { it.animations.doze(fraction) } - } - - override fun fold(fraction: Float) { - layers.forEach { it.animations.fold(fraction) } - } - - override fun charge() { - layers.forEach { it.animations.charge() } - } - - override fun onPickerCarouselSwiping(swipingFraction: Float) { - face.pickerScale?.let { - view.scaleX = swipingFraction * (1 - it.scaleX) + it.scaleX - view.scaleY = swipingFraction * (1 - it.scaleY) + it.scaleY - } - if (!(view is DigitalClockFaceView && view.isAlignedWithScreen())) { - val topMargin = getKeyguardLargeClockTopMargin(assets) - view.translationY = topMargin / 2F * swipingFraction - } - layers.forEach { it.animations.onPickerCarouselSwiping(swipingFraction) } - view.invalidate() - } - - override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) { - layers.forEach { it.animations.onPositionUpdated(fromLeft, direction, fraction) } - } - - override fun onPositionUpdated(distance: Float, fraction: Float) { - layers.forEach { it.animations.onPositionUpdated(distance, fraction) } - } - } - - private fun getTickRate(): ClockTickRate { - var tickRate = ClockTickRate.PER_MINUTE - for (layer in layers) { - if (layer.config.tickRate.value < tickRate.value) { - tickRate = layer.config.tickRate - } - } - return tickRate - } - - private fun getKeyguardLargeClockTopMargin(assets: AssetLoader): Int { - val topMarginRes = - assets.resolveResourceId(null, "dimen", "keyguard_large_clock_top_margin") - if (topMarginRes != null) { - val (res, id) = topMarginRes - return res.getDimensionPixelSize(id) - } - return 0 - } -} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt index f71543efa650..5d1a2dbc4209 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt @@ -16,25 +16,12 @@ package com.android.systemui.shared.clocks -import android.content.Context import android.view.View import androidx.annotation.VisibleForTesting -import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.clocks.ClockAnimations import com.android.systemui.plugins.clocks.ClockEvents import com.android.systemui.plugins.clocks.ClockFaceConfig import com.android.systemui.plugins.clocks.ClockFaceEvents -import com.android.systemui.shared.clocks.view.SimpleDigitalClockTextView -import kotlin.reflect.KClass - -typealias LayerControllerConstructor = - ( - ctx: Context, - assets: AssetLoader, - layer: ClockLayer, - isLargeClock: Boolean, - messageBuffer: MessageBuffer, - ) -> SimpleClockLayerController interface SimpleClockLayerController { val view: View @@ -48,55 +35,4 @@ interface SimpleClockLayerController { // Called immediately after either onColorPaletteChanged or onSeedColorChanged is called. // Provided for convience to not duplicate color update logic after state updated. fun updateColors() {} - - companion object Factory { - val constructorMap = mutableMapOf<Pair<KClass<*>, KClass<*>?>, LayerControllerConstructor>() - - internal inline fun <reified TLayer> registerConstructor( - noinline constructor: LayerControllerConstructor, - ) where TLayer : ClockLayer { - constructorMap[Pair(TLayer::class, null)] = constructor - } - - inline fun <reified TLayer, reified TStyle> registerTextConstructor( - noinline constructor: LayerControllerConstructor, - ) where TLayer : ClockLayer, TStyle : TextStyle { - constructorMap[Pair(TLayer::class, TStyle::class)] = constructor - } - - init { - registerConstructor<ComposedDigitalHandLayer>(::ComposedDigitalLayerController) - registerTextConstructor<DigitalHandLayer, FontTextStyle>(::createSimpleDigitalLayer) - } - - private fun createSimpleDigitalLayer( - ctx: Context, - assets: AssetLoader, - layer: ClockLayer, - isLargeClock: Boolean, - messageBuffer: MessageBuffer - ): SimpleClockLayerController { - val view = SimpleDigitalClockTextView(ctx, messageBuffer) - return SimpleDigitalHandLayerController( - ctx, - assets, - layer as DigitalHandLayer, - view, - messageBuffer - ) - } - - fun create( - ctx: Context, - assets: AssetLoader, - layer: ClockLayer, - isLargeClock: Boolean, - messageBuffer: MessageBuffer - ): SimpleClockLayerController { - val styleClass = if (layer is DigitalHandLayer) layer.style::class else null - val key = Pair(layer::class, styleClass) - return constructorMap[key]?.invoke(ctx, assets, layer, isLargeClock, messageBuffer) - ?: throw IllegalArgumentException("Unrecognized ClockLayer type: $key") - } - } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt index a3240f81e499..ce1eae48546a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt @@ -42,7 +42,8 @@ private val TAG = SimpleDigitalHandLayerController::class.simpleName!! open class SimpleDigitalHandLayerController<T>( private val ctx: Context, - private val assets: AssetLoader, + private val resources: Resources, + private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources private val layer: DigitalHandLayer, override val view: T, messageBuffer: MessageBuffer, @@ -68,7 +69,7 @@ open class SimpleDigitalHandLayerController<T>( view.layoutParams = RelativeLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT, ) if (layer.alignment != null) { layer.alignment.verticalAlignment?.let { view.verticalAlignment = it } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt index eb7234646a64..81efcb9de4d8 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt @@ -31,6 +31,7 @@ import com.android.systemui.shared.clocks.AssetLoader import com.android.systemui.shared.clocks.LogUtil import java.util.Locale +// TODO(b/364680879): Merge w/ only subclass FlexClockView abstract class DigitalClockFaceView(ctx: Context, messageBuffer: MessageBuffer) : FrameLayout(ctx) { protected val logger = Logger(messageBuffer, this::class.simpleName!!) get() = field ?: LogUtil.FALLBACK_INIT_LOGGER @@ -140,7 +141,6 @@ abstract class DigitalClockFaceView(ctx: Context, messageBuffer: MessageBuffer) open val useCustomClockScene get() = false - // TODO: implement ClockEventUnion? open fun onLocaleChanged(locale: Locale) {} open fun onWeatherDataChanged(data: WeatherData) {} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt index c29c8dac8ba6..25b2ad772b32 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt @@ -35,7 +35,7 @@ import kotlin.math.min fun clamp(value: Float, minVal: Float, maxVal: Float): Float = max(min(value, maxVal), minVal) -class FlexClockView(context: Context, val assetLoader: AssetLoader, messageBuffer: MessageBuffer) : +class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: MessageBuffer) : DigitalClockFaceView(context, messageBuffer) { override var digitalClockTextViewMap = mutableMapOf<Int, SimpleDigitalClockTextView>() val digitLeftTopMap = mutableMapOf<Int, Point>() @@ -57,11 +57,9 @@ class FlexClockView(context: Context, val assetLoader: AssetLoader, messageBuffe private var prevY = 0f private var isDown = false - // TODO(b/340253296): Genericize; json spec private var wght = 603f private var wdth = 100f - // TODO(b/340253296): Json spec private val MAX_WGHT = 950f private val MIN_WGHT = 50f private val WGHT_SCALE = 0.5f @@ -71,7 +69,6 @@ class FlexClockView(context: Context, val assetLoader: AssetLoader, messageBuffe private val WDTH_SCALE = 0.2f override fun onTouchEvent(evt: MotionEvent): Boolean { - // TODO(b/340253296): implement on DigitalClockFaceView? if (!isReactiveTouchInteractionEnabled) { return super.onTouchEvent(evt) } @@ -94,12 +91,11 @@ class FlexClockView(context: Context, val assetLoader: AssetLoader, messageBuffe prevX = evt.x prevY = evt.y - // TODO(b/340253296): Genericize; json spec val fvar = "'wght' $wght, 'wdth' $wdth, 'opsz' 144, 'ROND' 100" digitalClockTextViewMap.forEach { (_, view) -> val textStyle = view.textStyle as FontTextStyle textStyle.fontVariation = fvar - view.applyStyles(assetLoader, textStyle, view.aodStyle) + view.applyStyles(assets, textStyle, view.aodStyle) } requestLayout() diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java index 2bb9e68a357a..00c5577b8017 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java @@ -156,8 +156,12 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { when(mResources.getInteger(R.integer.keyguard_date_weather_view_invisibility)) .thenReturn(INVISIBLE); - when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame); - when(mView.findViewById(R.id.lockscreen_clock_view)).thenReturn(mSmallClockFrame); + when(mView + .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view_large)) + .thenReturn(mLargeClockFrame); + when(mView + .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view)) + .thenReturn(mSmallClockFrame); when(mSmallClockView.getContext()).thenReturn(getContext()); when(mLargeClockView.getContext()).thenReturn(getContext()); diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 0bf9d12a09d5..4ed5fd0a6e71 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -113,8 +113,10 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { }); mKeyguardClockSwitch = (KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null); - mSmallClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view); - mLargeClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view_large); + mSmallClockFrame = mKeyguardClockSwitch + .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view); + mLargeClockFrame = mKeyguardClockSwitch + .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view_large); mStatusArea = mKeyguardClockSwitch.findViewById(R.id.keyguard_status_area); mKeyguardClockSwitch.mChildrenAreLaidOut = true; } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt index 2e41246a62a1..245388c214a5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt @@ -17,10 +17,10 @@ package com.android.keyguard import android.view.View -import android.view.ViewGroup import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.kosmos.Kosmos import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -88,7 +88,7 @@ class KeyguardUnfoldTransitionTest : SysuiTestCase() { underTest.statusViewCentered = true val view = View(context) - whenever(keyguardRootView.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn( + whenever(keyguardRootView.findViewById<View>(customR.id.lockscreen_clock_view_large)).thenReturn( view ) @@ -110,7 +110,7 @@ class KeyguardUnfoldTransitionTest : SysuiTestCase() { whenever(statusBarStateController.getState()).thenReturn(SHADE) val view = View(context) - whenever(keyguardRootView.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn( + whenever(keyguardRootView.findViewById<View>(customR.id.lockscreen_clock_view_large)).thenReturn( view ) @@ -134,7 +134,7 @@ class KeyguardUnfoldTransitionTest : SysuiTestCase() { val view = View(context) whenever( notificationShadeWindowView - .findViewById<View>(R.id.lockscreen_clock_view_large) + .findViewById<View>(customR.id.lockscreen_clock_view_large) ).thenReturn(view) progressListener.onTransitionStarted() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt index 6c6de61c638a..cd8b2e12a3d5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt @@ -440,6 +440,28 @@ class DisplayRepositoryTest : SysuiTestCase() { } @Test + fun displayAdditionEvent_emptyByDefault() = + testScope.runTest { + setDisplays(1, 2, 3) + + val lastAddedDisplay by lastDisplayAdditionEvent() + + assertThat(lastAddedDisplay).isNull() + } + + @Test + fun displayAdditionEvent_displaysAdded_doesNotReplayEventsToNewSubscribers() = + testScope.runTest { + val priorDisplayAdded by lastDisplayAdditionEvent() + setDisplays(1) + sendOnDisplayAdded(1) + assertThat(priorDisplayAdded?.displayId).isEqualTo(1) + + val lastAddedDisplay by collectLastValue(displayRepository.displayAdditionEvent) + assertThat(lastAddedDisplay).isNull() + } + + @Test fun defaultDisplayOff_changes() = testScope.runTest { val defaultDisplayOff by latestDefaultDisplayOffFlowValue() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt index 1c99eff0d328..d94c97af6f14 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt @@ -28,12 +28,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase +import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel -import com.android.systemui.res.R import com.android.systemui.shared.R as sharedR import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import com.android.systemui.util.mockito.any @@ -135,7 +135,7 @@ class SmartspaceSectionTest : SysuiTestCase() { assertThat(smartspaceConstraints.layout.topToBottom).isEqualTo(dateView.id) val dateConstraints = constraintSet.getConstraint(dateView.id) - assertThat(dateConstraints.layout.topToBottom).isEqualTo(R.id.lockscreen_clock_view) + assertThat(dateConstraints.layout.topToBottom).isEqualTo(customR.id.lockscreen_clock_view) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt index 14d60943149f..e5bdc2e56b11 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt @@ -54,7 +54,7 @@ class PaginatedGridRepositoryTest : SysuiTestCase() { private fun setRowsInConfig(rows: Int) = with(kosmos) { testCase.context.orCreateTestableResources.addOverride( - R.integer.quick_settings_max_rows, + R.integer.quick_settings_paginated_grid_num_rows, rows, ) fakeConfigurationRepository.onConfigurationChange() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt index ae6f576bcf3e..cda3d488cb1e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt @@ -54,7 +54,7 @@ class QuickQuickSettingsRowRepositoryTest : SysuiTestCase() { private fun setRowsInConfig(rows: Int) = with(kosmos) { testCase.context.orCreateTestableResources.addOverride( - R.integer.quick_qs_panel_max_rows, + R.integer.quick_qs_paginated_grid_num_rows, rows, ) fakeConfigurationRepository.onConfigurationChange() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt index a1c0ef2789d5..2c894f9aa20f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt @@ -151,7 +151,7 @@ class QuickQuickSettingsViewModelTest : SysuiTestCase() { private fun Kosmos.setRows(rows: Int) { testCase.context.orCreateTestableResources.addOverride( - R.integer.quick_qs_panel_max_rows, + R.integer.quick_qs_paginated_grid_num_rows, rows, ) fakeConfigurationRepository.onConfigurationChange() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 0454317b5f04..06d19d7f9822 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -68,13 +68,13 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.LatencyTracker; +import com.android.keyguard.EmptyLockIconViewController; import com.android.keyguard.KeyguardClockSwitch; import com.android.keyguard.KeyguardClockSwitchController; import com.android.keyguard.KeyguardSliceViewController; import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.LegacyLockIconViewController; import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; @@ -271,6 +271,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardUserSwitcherController mKeyguardUserSwitcherController; @Mock protected KeyguardStatusViewComponent mKeyguardStatusViewComponent; @Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; + @Mock protected EmptyLockIconViewController mLockIconViewController; @Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent; @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController; @Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController; @@ -285,7 +286,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected AmbientState mAmbientState; @Mock protected UserManager mUserManager; @Mock protected UiEventLogger mUiEventLogger; - @Mock protected LegacyLockIconViewController mLockIconViewController; @Mock protected KeyguardViewConfigurator mKeyguardViewConfigurator; @Mock protected KeyguardRootView mKeyguardRootView; @Mock protected View mKeyguardRootViewChild; @@ -397,7 +397,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false); mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); mMainDispatcher = getMainDispatcher(); KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps = @@ -687,6 +686,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(longPressHandlingView.getResources()).thenReturn(longPressHandlingViewRes); when(longPressHandlingViewRes.getString(anyInt())).thenReturn(""); + when(mKeyguardRootView.findViewById(anyInt())).thenReturn(mKeyguardRootViewChild); + when(mKeyguardViewConfigurator.getKeyguardRootView()).thenReturn(mKeyguardRootView); + mNotificationPanelViewController = new NotificationPanelViewController( mView, mMainHandler, @@ -852,7 +854,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel(); mNotificationPanelViewController.cancelHeightAnimator(); leakedAnimators = mNotificationPanelViewController.mTestSetOfAnimatorsUsed.stream() - .filter(Animator::isRunning).toList(); + .filter(Animator::isRunning).toList(); mNotificationPanelViewController.mTestSetOfAnimatorsUsed.forEach(Animator::cancel); } if (mMainHandler != null) { @@ -869,11 +871,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0); when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(stackBottom); when(mNotificationStackScrollLayoutController.getBottom()).thenReturn(stackBottom); - when(mLockIconViewController.getTop()).thenReturn((float) (stackBottom - lockIconPadding)); - when(mKeyguardRootViewChild.getTop()).thenReturn((int) (stackBottom - lockIconPadding)); - when(mKeyguardRootView.findViewById(anyInt())).thenReturn(mKeyguardRootViewChild); - when(mKeyguardViewConfigurator.getKeyguardRootView()).thenReturn(mKeyguardRootView); when(mResources.getDimensionPixelSize(R.dimen.keyguard_indication_bottom_padding)) .thenReturn(indicationPadding); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 43dbb40d7721..ec75972aecfe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -217,7 +217,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(5); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(5); } @@ -235,7 +234,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); } @@ -253,7 +251,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); } @@ -271,7 +268,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(2); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(2); } @@ -289,7 +285,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); } @@ -389,7 +384,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void alternateBouncerVisible_onTouchEvent_notHandled() { - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); // GIVEN alternate bouncer is visible when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index 905301eb38ae..943fb62003cb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -74,6 +75,7 @@ class ShadeControllerImplTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager @Mock private lateinit var statusBarWindowController: StatusBarWindowController + @Mock private lateinit var statusBarWindowControllerStore: StatusBarWindowControllerStore @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController @Mock private lateinit var windowManager: WindowManager @@ -105,6 +107,8 @@ class ShadeControllerImplTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(windowManager.defaultDisplay).thenReturn(display) whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) + whenever(statusBarWindowControllerStore.defaultDisplay) + .thenReturn(statusBarWindowController) shadeController = ShadeControllerImpl( commandQueue, @@ -113,7 +117,7 @@ class ShadeControllerImplTest : SysuiTestCase() { keyguardStateController, statusBarStateController, statusBarKeyguardViewManager, - statusBarWindowController, + statusBarWindowControllerStore, deviceProvisionedController, notificationShadeWindowController, 0, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt index 9142972eabdd..f64387c95e3d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.google.common.truth.Truth.assertThat import kotlin.test.Test import org.junit.Assert.assertThrows @@ -39,7 +40,8 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class StatusBarInitializerTest : SysuiTestCase() { - val windowController = mock(StatusBarWindowController::class.java) + private val windowController = mock(StatusBarWindowController::class.java) + private val windowControllerStore = mock(StatusBarWindowControllerStore::class.java) @Before fun setup() { @@ -52,15 +54,16 @@ class StatusBarInitializerTest : SysuiTestCase() { whenever(fragmentHostManager.fragmentManager).thenReturn(fragmentManager) whenever(fragmentManager.beginTransaction()).thenReturn(transaction) whenever(transaction.replace(any(), any(), any())).thenReturn(transaction) - + whenever(windowControllerStore.defaultDisplay).thenReturn(windowController) whenever(windowController.fragmentHostManager).thenReturn(fragmentHostManager) } val underTest = StatusBarInitializerImpl( - windowController, - { mock(CollapsedStatusBarFragment::class.java) }, - setOf(), + displayId = context.displayId, + statusBarWindowControllerStore = windowControllerStore, + collapsedStatusBarFragmentProvider = { mock(CollapsedStatusBarFragment::class.java) }, + creationListeners = setOf(), ) @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt index 580336539c37..bb3fb1e71f78 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt @@ -44,7 +44,7 @@ import com.android.systemui.statusbar.phone.mockPhoneStatusBarViewController import com.android.systemui.statusbar.window.data.model.StatusBarWindowState import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStateRepositoryStore import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore -import com.android.systemui.statusbar.window.fakeStatusBarWindowController +import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore import com.android.systemui.testKosmos import com.android.wm.shell.bubbles.bubbles import com.google.common.truth.Truth.assertThat @@ -67,7 +67,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() { } private val testScope = kosmos.testScope private val statusBarViewController = kosmos.mockPhoneStatusBarViewController - private val statusBarWindowController = kosmos.fakeStatusBarWindowController + private val statusBarWindowControllerStore = kosmos.fakeStatusBarWindowControllerStore private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository private val pluginDependencyProvider = kosmos.mockPluginDependencyProvider private val notificationShadeWindowViewController = @@ -94,7 +94,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() { fun start_attachesWindow() { orchestrator.start() - assertThat(statusBarWindowController.isAttached).isTrue() + assertThat(statusBarWindowControllerStore.defaultDisplay.isAttached).isTrue() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt index 984bda1c0d21..a629b2447921 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture @@ -53,6 +54,7 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() { @get:Rule val animatorTestRule = AnimatorTestRule(this) @Mock private lateinit var sbWindowController: StatusBarWindowController + @Mock private lateinit var sbWindowControllerStore: StatusBarWindowControllerStore @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider private var testView = TestView(mContext) @@ -61,7 +63,7 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - + whenever(sbWindowControllerStore.defaultDisplay).thenReturn(sbWindowController) // StatusBarWindowController is mocked. The addViewToWindow function needs to be mocked to // ensure that the chip view is added to a parent view whenever(sbWindowController.addViewToWindow(any(), any())).then { @@ -93,8 +95,8 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() { controller = SystemEventChipAnimationController( context = mContext, - statusBarWindowController = sbWindowController, - contentInsetsProvider = insetsProvider + statusBarWindowControllerStore = sbWindowControllerStore, + contentInsetsProvider = insetsProvider, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt index 35e4047109d5..97fa6eb17b5b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt @@ -31,6 +31,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() { private var isStarted = false + private var wasStarted = false private var scrimOffset = 0f private var contentHeight = 0f private var isCurrentGestureOverscroll = false @@ -46,7 +47,10 @@ class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() { minVisibleScrimHeight = { MIN_VISIBLE_SCRIM_HEIGHT }, isCurrentGestureOverscroll = { isCurrentGestureOverscroll }, onStart = { isStarted = true }, - onStop = { isStarted = false }, + onStop = { + wasStarted = true + isStarted = false + }, ) @Test @@ -180,6 +184,7 @@ class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() { ) assertThat(offsetConsumed).isEqualTo(Offset.Zero) + assertThat(wasStarted).isEqualTo(false) assertThat(isStarted).isEqualTo(false) } @@ -196,7 +201,9 @@ class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() { ) assertThat(offsetConsumed).isEqualTo(Offset.Zero) - assertThat(isStarted).isEqualTo(true) + // Returning 0 offset will immediately stop the connection + assertThat(wasStarted).isEqualTo(true) + assertThat(isStarted).isEqualTo(false) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt index 179799503ac0..bac79a9cc520 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt @@ -56,6 +56,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -94,6 +95,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { @Mock private lateinit var activityTransitionAnimator: ActivityTransitionAnimator @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager @Mock private lateinit var statusBarWindowController: StatusBarWindowController + @Mock private lateinit var statusBarWindowControllerStore: StatusBarWindowControllerStore @Mock private lateinit var notifShadeWindowController: NotificationShadeWindowController @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock private lateinit var keyguardStateController: KeyguardStateController @@ -112,6 +114,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + `when`(statusBarWindowControllerStore.defaultDisplay).thenReturn(statusBarWindowController) underTest = LegacyActivityStarterInternalImpl( centralSurfacesOptLazy = { Optional.of(centralSurfaces) }, @@ -128,7 +131,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { context = context, displayId = DISPLAY_ID, lockScreenUserManager = lockScreenUserManager, - statusBarWindowController = statusBarWindowController, + statusBarWindowControllerStore = statusBarWindowControllerStore, wakefulnessLifecycle = wakefulnessLifecycle, keyguardStateController = keyguardStateController, statusBarStateController = statusBarStateController, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt index 597e2e45ea14..e0d9fac0eba5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt @@ -50,6 +50,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.activeNotif import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.time.FakeSystemClock @@ -74,6 +75,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import org.mockito.kotlin.whenever private const val CALL_UID = 900 @@ -106,6 +108,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { @Mock private lateinit var mockActivityStarter: ActivityStarter @Mock private lateinit var mockIActivityManager: IActivityManager @Mock private lateinit var mockStatusBarWindowController: StatusBarWindowController + @Mock private lateinit var mockStatusBarWindowControllerStore: StatusBarWindowControllerStore private lateinit var chipView: View @@ -118,6 +121,8 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) val notificationCollection = mock(CommonNotifCollection::class.java) + whenever(mockStatusBarWindowControllerStore.defaultDisplay) + .thenReturn(mockStatusBarWindowController) controller = OngoingCallController( @@ -131,7 +136,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { mainExecutor, mockIActivityManager, DumpManager(), - mockStatusBarWindowController, + mockStatusBarWindowControllerStore, mockSwipeStatusBarAwayGestureHandler, statusBarModeRepository, logcatLogBuffer("OngoingCallControllerViaListenerTest"), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt index dfe01bf45f38..2ad50cc38b7c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt @@ -52,6 +52,7 @@ import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -93,6 +94,7 @@ class OngoingCallControllerViaRepoTest : SysuiTestCase() { private val mockActivityStarter = kosmos.activityStarter private val mockIActivityManager = mock<IActivityManager>() private val mockStatusBarWindowController = mock<StatusBarWindowController>() + private val mockStatusBarWindowControllerStore = mock<StatusBarWindowControllerStore>() private lateinit var chipView: View @@ -103,6 +105,8 @@ class OngoingCallControllerViaRepoTest : SysuiTestCase() { chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null) } + whenever(mockStatusBarWindowControllerStore.defaultDisplay) + .thenReturn(mockStatusBarWindowController) controller = OngoingCallController( testScope.backgroundScope, @@ -115,7 +119,7 @@ class OngoingCallControllerViaRepoTest : SysuiTestCase() { mainExecutor, mockIActivityManager, DumpManager(), - mockStatusBarWindowController, + mockStatusBarWindowControllerStore, mockSwipeStatusBarAwayGestureHandler, statusBarModeRepository, logcatLogBuffer("OngoingCallControllerViaRepoTest"), diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml index fc9c917c152b..8bef4759c55d 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml @@ -26,7 +26,7 @@ android:clipChildren="false" android:layout_gravity="center_horizontal|top"> <com.android.keyguard.KeyguardClockFrame - android:id="@+id/lockscreen_clock_view" + android:id="@id/lockscreen_clock_view" android:layout_width="wrap_content" android:layout_height="@dimen/small_clock_height" android:layout_alignParentStart="true" @@ -35,7 +35,7 @@ android:paddingStart="@dimen/clock_padding_start" android:visibility="invisible" /> <com.android.keyguard.KeyguardClockFrame - android:id="@+id/lockscreen_clock_view_large" + android:id="@id/lockscreen_clock_view_large" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml index b5efeb5f6b3b..5d5b95546d0d 100644 --- a/packages/SystemUI/res/values-land/config.xml +++ b/packages/SystemUI/res/values-land/config.xml @@ -25,6 +25,12 @@ <integer name="quick_settings_num_columns">4</integer> + <!-- The number of rows in the paginated grid QuickSettings --> + <integer name="quick_settings_paginated_grid_num_rows">2</integer> + + <!-- The number of rows in the paginated grid QuickQuickSettings --> + <integer name="quick_qs_paginated_grid_num_rows">1</integer> + <!-- The number of columns in the infinite grid QuickSettings --> <integer name="quick_settings_infinite_grid_num_columns">8</integer> diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index fc6d20e11d3b..c661846d025f 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -27,6 +27,12 @@ <!-- Whether to use the split 2-column notification shade --> <bool name="config_use_split_notification_shade">true</bool> + <!-- The number of rows in the paginated grid QuickSettings --> + <integer name="quick_settings_paginated_grid_num_rows">3</integer> + + <!-- The number of rows in the paginated grid QuickQuickSettings --> + <integer name="quick_qs_paginated_grid_num_rows">2</integer> + <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">2</integer> diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml index 7daad1a43f73..f556b97eefc2 100644 --- a/packages/SystemUI/res/values-sw600dp-port/config.xml +++ b/packages/SystemUI/res/values-sw600dp-port/config.xml @@ -21,6 +21,12 @@ <!-- The maximum number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">3</integer> + <!-- The number of rows in the paginated grid QuickSettings --> + <integer name="quick_settings_paginated_grid_num_rows">3</integer> + + <!-- The number of rows in the paginated grid QuickQuickSettings --> + <integer name="quick_qs_paginated_grid_num_rows">2</integer> + <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 6f94f9e2a216..16a8bc5b034f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -70,6 +70,12 @@ <!-- The number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">4</integer> + <!-- The number of rows in the paginated grid QuickSettings --> + <integer name="quick_settings_paginated_grid_num_rows">4</integer> + + <!-- The number of rows in the paginated grid QuickQuickSettings --> + <integer name="quick_qs_paginated_grid_num_rows">2</integer> + <!-- The number of columns in the infinite grid QuickSettings --> <integer name="quick_settings_infinite_grid_num_columns">4</integer> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 4a96e9e0845a..c7ea98052b66 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -193,12 +193,16 @@ public class KeyguardClockSwitch extends RelativeLayout { protected void onFinishInflate() { super.onFinishInflate(); if (!MigrateClocksToBlueprint.isEnabled()) { - mSmallClockFrame = findViewById(R.id.lockscreen_clock_view); - mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large); + mSmallClockFrame = findViewById( + com.android.systemui.customization.R.id.lockscreen_clock_view); + mLargeClockFrame = findViewById( + com.android.systemui.customization.R.id.lockscreen_clock_view_large); mStatusArea = findViewById(R.id.keyguard_status_area); } else { - removeView(findViewById(R.id.lockscreen_clock_view)); - removeView(findViewById(R.id.lockscreen_clock_view_large)); + removeView(findViewById( + com.android.systemui.customization.R.id.lockscreen_clock_view)); + removeView(findViewById( + com.android.systemui.customization.R.id.lockscreen_clock_view_large)); } onConfigChanged(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index d468f2f0b0aa..7cba845460ca 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -241,8 +241,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mKeyguardSliceViewController.init(); if (!MigrateClocksToBlueprint.isEnabled()) { - mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view); - mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large); + mSmallClockFrame = mView + .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view); + mLargeClockFrame = mView + .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view_large); } if (!mOnlyClock) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 63a4af949c8c..0684824ea0b8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -534,7 +534,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV // KeyguardClockViewBinder if (customClockAnimation && !MigrateClocksToBlueprint.isEnabled()) { // Find the clock, so we can exclude it from this transition. - FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large); + FrameLayout clockContainerView = mView.findViewById( + com.android.systemui.customization.R.id.lockscreen_clock_view_large); // The clock container can sometimes be null. If it is, just fall back to the // old animation rather than setting up the custom animations. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt index 19d918f5c556..5a02486d5096 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt @@ -18,6 +18,7 @@ package com.android.keyguard import android.content.Context import android.view.View +import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -98,12 +99,12 @@ constructor( viewsIdToTranslate = setOf( ViewIdToTranslate( - viewId = R.id.lockscreen_clock_view_large, + viewId = customR.id.lockscreen_clock_view_large, direction = START, shouldBeAnimated = filterKeyguardAndSplitShadeOnly ), ViewIdToTranslate( - viewId = R.id.lockscreen_clock_view, + viewId = customR.id.lockscreen_clock_view, direction = START, shouldBeAnimated = filterKeyguard ), diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index a30115568842..40c1f0f9895d 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -52,7 +52,7 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import com.android.systemui.tuner.TunerService; import dagger.Lazy; @@ -148,7 +148,7 @@ public class Dependency { @Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy; @Inject Lazy<DialogTransitionAnimator> mDialogTransitionAnimatorLazy; @Inject Lazy<UserTracker> mUserTrackerLazy; - @Inject Lazy<StatusBarWindowController> mStatusBarWindowControllerLazy; + @Inject Lazy<StatusBarWindowControllerStore> mStatusBarWindowControllerStoreLazy; @Inject public Dependency() { @@ -192,7 +192,8 @@ public class Dependency { mProviders.put(SystemUIDialogManager.class, mSystemUIDialogManagerLazy::get); mProviders.put(DialogTransitionAnimator.class, mDialogTransitionAnimatorLazy::get); mProviders.put(UserTracker.class, mUserTrackerLazy::get); - mProviders.put(StatusBarWindowController.class, mStatusBarWindowControllerLazy::get); + mProviders.put( + StatusBarWindowControllerStore.class, mStatusBarWindowControllerStoreLazy::get); Dependency.setInstance(this); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java new file mode 100644 index 000000000000..1950d6bac251 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java @@ -0,0 +1,30 @@ +/* + * 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.dagger.qualifiers; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface Default { +} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt index 6a6913677a0c..034cb31dbc74 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt @@ -33,6 +33,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.DisplayEvent import com.android.systemui.util.Compile +import com.android.systemui.util.kotlin.pairwiseBy import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -41,11 +42,12 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -146,11 +148,6 @@ constructor( override val displayChangeEvent: Flow<Int> = allDisplayEvents.filterIsInstance<DisplayEvent.Changed>().map { event -> event.displayId } - override val displayAdditionEvent: Flow<Display?> = - allDisplayEvents.filterIsInstance<DisplayEvent.Added>().map { - getDisplayFromDisplayManager(it.displayId) - } - override val displayRemovalEvent: Flow<Int> = allDisplayEvents.filterIsInstance<DisplayEvent.Removed>().map { it.displayId } @@ -212,6 +209,17 @@ constructor( */ override val displays: StateFlow<Set<Display>> = enabledDisplays + /** + * Implementation that maps from [displays], instead of [allDisplayEvents] for 2 reasons: + * 1. Guarantee that it emits __after__ [displays] emitted. This way it is guaranteed that + * calling [getDisplay] for the newly added display will be non-null. + * 2. Reuse the existing instance of [Display] without a new call to [DisplayManager]. + */ + override val displayAdditionEvent: Flow<Display?> = + displays + .pairwiseBy { previousDisplays, currentDisplays -> currentDisplays - previousDisplays } + .flatMapLatest { it.asFlow() } + val _ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet()) private val ignoredDisplayIds: Flow<Set<Int>> = _ignoredDisplayIds.debugLog("ignoredDisplayIds") diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index aa1873c7ad41..162047bb3b79 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -135,6 +135,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.EmergencyDialerConstants; @@ -248,7 +249,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final IStatusBarService mStatusBarService; protected final LightBarController mLightBarController; protected final NotificationShadeWindowController mNotificationShadeWindowController; - private final StatusBarWindowController mStatusBarWindowController; + private final StatusBarWindowControllerStore mStatusBarWindowControllerStore; private final IWindowManager mIWindowManager; private final Executor mBackgroundExecutor; private final RingerModeTracker mRingerModeTracker; @@ -364,7 +365,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene IStatusBarService statusBarService, LightBarController lightBarController, NotificationShadeWindowController notificationShadeWindowController, - StatusBarWindowController statusBarWindowController, + StatusBarWindowControllerStore statusBarWindowControllerStore, IWindowManager iWindowManager, @Background Executor backgroundExecutor, UiEventLogger uiEventLogger, @@ -400,7 +401,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mStatusBarService = statusBarService; mLightBarController = lightBarController; mNotificationShadeWindowController = notificationShadeWindowController; - mStatusBarWindowController = statusBarWindowController; + mStatusBarWindowControllerStore = statusBarWindowControllerStore; mIWindowManager = iWindowManager; mBackgroundExecutor = backgroundExecutor; mRingerModeTracker = ringerModeTracker; @@ -708,7 +709,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mLightBarController, mKeyguardStateController, mNotificationShadeWindowController, - mStatusBarWindowController, + mStatusBarWindowControllerStore.getDefaultDisplay(), this::onRefresh, mKeyguardShowing, mPowerAdapter, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 063adc834f30..3230285fcd71 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -30,8 +30,6 @@ import com.android.compose.animation.scene.SceneTransitionLayout import com.android.internal.jank.InteractionJankMonitor import com.android.keyguard.KeyguardStatusView import com.android.keyguard.KeyguardStatusViewController -import com.android.keyguard.LegacyLockIconViewController -import com.android.keyguard.LockIconView import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable import com.android.systemui.biometrics.ui.binder.DeviceEntryUnlockTrackerViewBinder @@ -39,7 +37,6 @@ import com.android.systemui.common.ui.ConfigurationState 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.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder @@ -94,7 +91,6 @@ constructor( private val configuration: ConfigurationState, private val context: Context, private val keyguardIndicationController: KeyguardIndicationController, - private val lockIconViewController: Lazy<LegacyLockIconViewController>, private val shadeInteractor: ShadeInteractor, private val interactionJankMonitor: InteractionJankMonitor, private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, @@ -171,10 +167,6 @@ constructor( private fun initializeViews() { val indicationArea = KeyguardIndicationArea(context, null) keyguardIndicationController.setIndicationArea(indicationArea) - - if (!DeviceEntryUdfpsRefactor.isEnabled) { - lockIconViewController.get().setLockIconView(LockIconView(context, null)) - } } private fun bindKeyguardRootView() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index d28b08f83a4e..fbc76c587be2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -139,7 +139,6 @@ import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.ui.viewmodel.DreamViewModel; import com.android.systemui.dump.DumpManager; @@ -3569,9 +3568,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } // Ensure that keyguard becomes visible if the going away animation is canceled - if (showKeyguard && !KeyguardWmStateRefactor.isEnabled() - && (MigrateClocksToBlueprint.isEnabled() - || DeviceEntryUdfpsRefactor.isEnabled())) { + if (showKeyguard && !KeyguardWmStateRefactor.isEnabled()) { mKeyguardInteractor.showKeyguard(); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt index fb9719142b54..7ca2c2004452 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt @@ -22,7 +22,6 @@ import android.view.View import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launch -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached @@ -34,13 +33,7 @@ object AlternateBouncerUdfpsViewBinder { /** Updates UI for the UDFPS icon on the alternate bouncer. */ @JvmStatic - fun bind( - view: DeviceEntryIconView, - viewModel: AlternateBouncerUdfpsIconViewModel, - ) { - if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) { - return - } + fun bind(view: DeviceEntryIconView, viewModel: AlternateBouncerUdfpsIconViewModel) { val fgIconView = view.iconView val bgView = view.bgView @@ -66,7 +59,7 @@ object AlternateBouncerUdfpsViewBinder { viewModel.fgViewModel.collect { fgViewModel -> fgIconView.setImageState( view.getIconState(fgViewModel.type, fgViewModel.useAodVariant), - /* merge */ false + /* merge */ false, ) fgIconView.imageTintList = ColorStateList.valueOf(fgViewModel.tint) fgIconView.setPadding( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index 76962732ad01..1891af2d04b0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -33,7 +33,6 @@ import com.android.app.tracing.coroutines.launch import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel @@ -95,7 +94,7 @@ constructor( private var alternateBouncerView: ConstraintLayout? = null override fun start() { - if (!DeviceEntryUdfpsRefactor.isEnabled || SceneContainerFlag.isEnabled) { + if (SceneContainerFlag.isEnabled) { return } @@ -182,14 +181,7 @@ constructor( } /** Binds the view to the view-model, continuing to update the former based on the latter. */ - fun bind( - view: ConstraintLayout, - alternateBouncerDependencies: AlternateBouncerDependencies, - ) { - if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) { - return - } - + fun bind(view: ConstraintLayout, alternateBouncerDependencies: AlternateBouncerDependencies) { optionallyAddUdfpsViews( view = view, logger = alternateBouncerDependencies.logger, @@ -287,10 +279,7 @@ constructor( ) } view.addView(udfpsView) - AlternateBouncerUdfpsViewBinder.bind( - udfpsView, - udfpsIconViewModel, - ) + AlternateBouncerUdfpsViewBinder.bind(udfpsView, udfpsIconViewModel) } val constraintSet = ConstraintSet().apply { clone(view) } @@ -310,17 +299,17 @@ constructor( ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, - iconLocation.left + iconLocation.left, ) // udfpsA11yOverlayView: constrainWidth( udfpsA11yOverlayViewId, - ViewGroup.LayoutParams.MATCH_PARENT + ViewGroup.LayoutParams.MATCH_PARENT, ) constrainHeight( udfpsA11yOverlayViewId, - ViewGroup.LayoutParams.MATCH_PARENT + ViewGroup.LayoutParams.MATCH_PARENT, ) } constraintSet.applyTo(view) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index b951b736abf2..a3f3342b9a6c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -30,7 +30,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launch import com.android.systemui.common.ui.view.LongPressHandlingView -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel @@ -68,7 +67,6 @@ object DeviceEntryIconViewBinder { vibratorHelper: VibratorHelper, overrideColor: Color? = null, ): DisposableHandle { - DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode() val disposables = DisposableHandles() val longPressHandlingView = view.longPressHandlingView val fgIconView = view.iconView @@ -79,7 +77,7 @@ object DeviceEntryIconViewBinder { view: View, x: Int, y: Int, - isA11yAction: Boolean + isA11yAction: Boolean, ) { if ( !isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY) @@ -87,14 +85,11 @@ object DeviceEntryIconViewBinder { Log.d( TAG, "Long press rejected because it is not a11yAction " + - "and it is a falseLongTap" + "and it is a falseLongTap", ) return } - vibratorHelper.performHapticFeedback( - view, - HapticFeedbackConstants.CONFIRM, - ) + vibratorHelper.performHapticFeedback(view, HapticFeedbackConstants.CONFIRM) applicationScope.launch { view.clearFocus() view.clearAccessibilityFocus() @@ -192,7 +187,7 @@ object DeviceEntryIconViewBinder { fgViewModel.viewModel.collect { viewModel -> fgIconView.setImageState( view.getIconState(viewModel.type, viewModel.useAodVariant), - /* merge */ false + /* merge */ false, ) if (viewModel.type.contentDescriptionResId != -1) { fgIconView.contentDescription = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index 00aa44fe795b..0470e086ad27 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -23,6 +23,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launch +import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition @@ -32,7 +33,6 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.res.R import com.android.systemui.shared.R as sharedR import com.android.systemui.util.kotlin.pairwise @@ -128,7 +128,7 @@ object KeyguardBlueprintViewBinder { ) { val currentClock = viewModel.currentClock.value if (!DEBUG || currentClock == null) return - val smallClockViewId = R.id.lockscreen_clock_view + val smallClockViewId = customR.id.lockscreen_clock_view val largeClockViewId = currentClock.largeClock.layout.views[0].id val smartspaceDateId = sharedR.id.date_smartspace_view Log.i( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt index 57cb10ff9367..2d225562081a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt @@ -35,7 +35,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launch import com.android.internal.policy.SystemBarUtils -import com.android.systemui.customization.R as customizationR +import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.shared.model.ClockSizeSetting import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection.Companion.getDimen @@ -50,6 +50,8 @@ import kotlin.reflect.KSuspendFunction1 /** Binder for the small clock view, large clock view. */ object KeyguardPreviewClockViewBinder { + val lockId = View.generateViewId() + @JvmStatic fun bind( largeClockHostView: View, @@ -120,36 +122,36 @@ object KeyguardPreviewClockViewBinder { private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) { constraints.apply { - constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT) + constrainWidth(customR.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT) // The following two lines make lockscreen_clock_view_large is constrained to available // height when it goes beyond constraints; otherwise, it use WRAP_CONTENT - constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT) - constrainMaxHeight(R.id.lockscreen_clock_view_large, 0) + constrainHeight(customR.id.lockscreen_clock_view_large, WRAP_CONTENT) + constrainMaxHeight(customR.id.lockscreen_clock_view_large, 0) val largeClockTopMargin = SystemBarUtils.getStatusBarHeight(context) + context.resources.getDimensionPixelSize( - customizationR.dimen.small_clock_padding_top + customR.dimen.small_clock_padding_top ) + context.resources.getDimensionPixelSize( R.dimen.keyguard_smartspace_top_offset ) + getDimen(context, DATE_WEATHER_VIEW_HEIGHT) + getDimen(context, ENHANCED_SMARTSPACE_HEIGHT) - connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin) - connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START) + connect(customR.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin) + connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START) connect( - R.id.lockscreen_clock_view_large, + customR.id.lockscreen_clock_view_large, ConstraintSet.END, PARENT_ID, ConstraintSet.END, ) - // In preview, we'll show UDFPS icon for UDFPS devices - // and nothing for non-UDFPS devices, - // but we need position of device entry icon to constrain clock - if (getConstraint(R.id.lock_icon_view) != null) { - connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP) - } else { + + // In preview, we'll show UDFPS icon for UDFPS devices and nothing for non-UDFPS + // devices, but we need position of device entry icon to constrain clock + if (getConstraint(lockId) != null) { + connect(customR.id.lockscreen_clock_view_large, BOTTOM, lockId, TOP) + } else { // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection val bottomPaddingPx = context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) @@ -159,7 +161,7 @@ object KeyguardPreviewClockViewBinder { val lockIconRadiusPx = (defaultDensity * 36).toInt() val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx connect( - R.id.lockscreen_clock_view_large, + customR.id.lockscreen_clock_view_large, BOTTOM, PARENT_ID, BOTTOM, @@ -167,23 +169,23 @@ object KeyguardPreviewClockViewBinder { ) } - constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT) + constrainWidth(customR.id.lockscreen_clock_view, WRAP_CONTENT) constrainHeight( - R.id.lockscreen_clock_view, - context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height), + customR.id.lockscreen_clock_view, + context.resources.getDimensionPixelSize(customR.dimen.small_clock_height), ) connect( - R.id.lockscreen_clock_view, + customR.id.lockscreen_clock_view, START, PARENT_ID, START, - context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) + + context.resources.getDimensionPixelSize(customR.dimen.clock_padding_start) + context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal), ) val smallClockTopMargin = context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) + Utils.getStatusBarHeaderHeightKeyguard(context) - connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin) + connect(customR.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin) } } 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 ee2ee522cd0e..ea70fd02eed5 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 @@ -52,8 +52,8 @@ import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.view.onApplyWindowInsets import com.android.systemui.common.ui.view.onLayoutChanged import com.android.systemui.common.ui.view.onTouchListener +import com.android.systemui.customization.R as customR import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.MigrateClocksToBlueprint @@ -180,16 +180,12 @@ object KeyguardRootViewBinder { } } - if ( - KeyguardBottomAreaRefactor.isEnabled || DeviceEntryUdfpsRefactor.isEnabled - ) { - launch("$TAG#alpha") { - viewModel.alpha(viewState).collect { alpha -> - view.alpha = alpha - if (KeyguardBottomAreaRefactor.isEnabled) { - childViews[statusViewId]?.alpha = alpha - childViews[burnInLayerId]?.alpha = alpha - } + launch("$TAG#alpha") { + viewModel.alpha(viewState).collect { alpha -> + view.alpha = alpha + if (KeyguardBottomAreaRefactor.isEnabled) { + childViews[statusViewId]?.alpha = alpha + childViews[burnInLayerId]?.alpha = alpha } } } @@ -223,7 +219,6 @@ object KeyguardRootViewBinder { indicationArea, startButton, endButton, - lockIcon, deviceEntryIcon -> { // Do not move these views } @@ -622,12 +617,11 @@ object KeyguardRootViewBinder { private val statusViewId = R.id.keyguard_status_view private val burnInLayerId = R.id.burn_in_layer private val aodNotificationIconContainerId = R.id.aod_notification_icon_container - private val largeClockId = R.id.lockscreen_clock_view_large - private val smallClockId = R.id.lockscreen_clock_view + private val largeClockId = customR.id.lockscreen_clock_view_large + private val smallClockId = customR.id.lockscreen_clock_view private val indicationArea = R.id.keyguard_indication_area private val startButton = R.id.start_button private val endButton = R.id.end_button - private val lockIcon = R.id.lock_icon_view private val deviceEntryIcon = R.id.device_entry_icon_view private val nsslPlaceholderId = R.id.nssl_placeholder private val authInteractionProperties = AuthInteractionProperties() 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 cef9a4eaf2bd..dd8980dabb23 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 @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.preview -import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.app.WallpaperColors import android.content.BroadcastReceiver import android.content.Context @@ -48,6 +47,7 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.core.view.isInvisible +import com.android.app.tracing.coroutines.createCoroutineTracingContext import com.android.internal.policy.SystemBarUtils import com.android.keyguard.ClockEventController import com.android.keyguard.KeyguardClockSwitch @@ -151,10 +151,7 @@ constructor( private val width: Int = bundle.getInt(KEY_VIEW_WIDTH) private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT) private val shouldHighlightSelectedAffordance: Boolean = - bundle.getBoolean( - KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES, - false, - ) + bundle.getBoolean(KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES, false) private val displayId = bundle.getInt(KEY_DISPLAY_ID, DEFAULT_DISPLAY) private val display: Display? = displayManager.getDisplay(displayId) @@ -188,24 +185,26 @@ constructor( private var themeStyle: Style? = null init { - coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job() + createCoroutineTracingContext("KeyguardPreviewRenderer")) + coroutineScope = + CoroutineScope( + applicationScope.coroutineContext + + Job() + + createCoroutineTracingContext("KeyguardPreviewRenderer") + ) disposables += DisposableHandle { coroutineScope.cancel() } clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData()) if (KeyguardBottomAreaRefactor.isEnabled) { quickAffordancesCombinedViewModel.enablePreviewMode( initiallySelectedSlotId = - bundle.getString( - KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID, - ) ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID) + ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, ) } else { bottomAreaViewModel.enablePreviewMode( initiallySelectedSlotId = - bundle.getString( - KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID, - ), + bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID), shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, ) } @@ -218,7 +217,7 @@ constructor( context, displayManager.getDisplay(DEFAULT_DISPLAY), if (hostToken == null) null else InputTransferToken(hostToken), - "KeyguardPreviewRenderer" + "KeyguardPreviewRenderer", ) disposables += DisposableHandle { host.release() } } @@ -247,12 +246,12 @@ constructor( rootView.measure( View.MeasureSpec.makeMeasureSpec( displayInfo?.logicalWidth ?: windowManager.currentWindowMetrics.bounds.width(), - View.MeasureSpec.EXACTLY + View.MeasureSpec.EXACTLY, ), View.MeasureSpec.makeMeasureSpec( displayInfo?.logicalHeight ?: windowManager.currentWindowMetrics.bounds.height(), - View.MeasureSpec.EXACTLY + View.MeasureSpec.EXACTLY, ), ) rootView.layout(0, 0, rootView.measuredWidth, rootView.measuredHeight) @@ -278,9 +277,7 @@ constructor( } } - fun onStartCustomizingQuickAffordances( - initiallySelectedSlotId: String?, - ) { + fun onStartCustomizingQuickAffordances(initiallySelectedSlotId: String?) { quickAffordancesCombinedViewModel.enablePreviewMode( initiallySelectedSlotId = initiallySelectedSlotId, shouldHighlightSelectedAffordance = true, @@ -379,15 +376,9 @@ constructor( @Deprecated("Deprecated as part of b/278057014") private fun setUpBottomArea(parentView: ViewGroup) { val bottomAreaView = - LayoutInflater.from(context) - .inflate( - R.layout.keyguard_bottom_area, - parentView, - false, - ) as KeyguardBottomAreaView - bottomAreaView.init( - viewModel = bottomAreaViewModel, - ) + LayoutInflater.from(context).inflate(R.layout.keyguard_bottom_area, parentView, false) + as KeyguardBottomAreaView + bottomAreaView.init(viewModel = bottomAreaViewModel) parentView.addView( bottomAreaView, FrameLayout.LayoutParams( @@ -433,7 +424,7 @@ constructor( setUpUdfps( previewContext, - if (MigrateClocksToBlueprint.isEnabled) keyguardRootView else rootView + if (MigrateClocksToBlueprint.isEnabled) keyguardRootView else rootView, ) if (KeyguardBottomAreaRefactor.isEnabled) { @@ -466,7 +457,7 @@ constructor( previewContext, it, previewInSplitShade(), - smartspaceViewModel + smartspaceViewModel, ) } setupCommunalTutorialIndicator(keyguardRootView) @@ -515,23 +506,20 @@ constructor( val finger = LayoutInflater.from(previewContext) - .inflate( - R.layout.udfps_keyguard_preview, - parentView, - false, - ) as View + .inflate(R.layout.udfps_keyguard_preview, parentView, false) as View // Place the UDFPS view in the proper sensor location if (MigrateClocksToBlueprint.isEnabled) { - finger.id = R.id.lock_icon_view + val lockId = KeyguardPreviewClockViewBinder.lockId + finger.id = lockId parentView.addView(finger) val cs = ConstraintSet() cs.clone(parentView as ConstraintLayout) cs.apply { - constrainWidth(R.id.lock_icon_view, sensorBounds.width()) - constrainHeight(R.id.lock_icon_view, sensorBounds.height()) - connect(R.id.lock_icon_view, TOP, PARENT_ID, TOP, sensorBounds.top) - connect(R.id.lock_icon_view, START, PARENT_ID, START, sensorBounds.left) + constrainWidth(lockId, sensorBounds.width()) + constrainHeight(lockId, sensorBounds.height()) + connect(lockId, TOP, PARENT_ID, TOP, sensorBounds.top) + connect(lockId, START, PARENT_ID, START, sensorBounds.left) } cs.applyTo(parentView) } else { @@ -541,7 +529,7 @@ constructor( sensorBounds.left, sensorBounds.top, sensorBounds.right, - sensorBounds.bottom + sensorBounds.bottom, ) parentView.addView(finger, fingerprintLayoutParams) } @@ -565,7 +553,7 @@ constructor( FrameLayout.LayoutParams.WRAP_CONTENT, resources.getDimensionPixelSize( com.android.systemui.customization.R.dimen.small_clock_height - ) + ), ) layoutParams.topMargin = SystemBarUtils.getStatusBarHeight(previewContext) + @@ -579,7 +567,7 @@ constructor( ), /* top = */ 0, /* end = */ 0, - /* bottom = */ 0 + /* bottom = */ 0, ) smallClockHostView.clipChildren = false parentView.addView(smallClockHostView) @@ -703,9 +691,7 @@ constructor( private suspend fun fetchThemeStyleFromSetting(): Style { val overlayPackageJson = withContext(backgroundDispatcher) { - secureSettings.getString( - Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, - ) + secureSettings.getString(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES) } return if (!overlayPackageJson.isNullOrEmpty()) { try { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index d74552231209..ee4f41ddd5a0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -30,7 +30,7 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.constraintlayout.widget.ConstraintSet.VISIBLE import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT -import com.android.systemui.customization.R as custR +import com.android.systemui.customization.R as customR import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor @@ -153,7 +153,7 @@ constructor( R.id.weather_clock_bc_smartspace_bottom, Barrier.BOTTOM, getDimen(ENHANCED_SMARTSPACE_HEIGHT), - (custR.id.weather_clock_time), + (customR.id.weather_clock_time), ) if ( rootViewModel.isNotifIconContainerVisible.value.value && @@ -184,40 +184,40 @@ constructor( if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID else R.id.split_shade_guideline constraints.apply { - connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START) - connect(R.id.lockscreen_clock_view_large, END, guideline, END) - connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.device_entry_icon_view, TOP) + connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START) + connect(customR.id.lockscreen_clock_view_large, END, guideline, END) + connect(customR.id.lockscreen_clock_view_large, BOTTOM, R.id.device_entry_icon_view, TOP) val largeClockTopMargin = keyguardClockViewModel.getLargeClockTopMargin() + getDimen(DATE_WEATHER_VIEW_HEIGHT) + getDimen(ENHANCED_SMARTSPACE_HEIGHT) - connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin) - constrainWidth(R.id.lockscreen_clock_view_large, WRAP_CONTENT) + connect(customR.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin) + constrainWidth(customR.id.lockscreen_clock_view_large, WRAP_CONTENT) // The following two lines make lockscreen_clock_view_large is constrained to available // height when it goes beyond constraints; otherwise, it use WRAP_CONTENT - constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT) - constrainMaxHeight(R.id.lockscreen_clock_view_large, 0) - constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT) + constrainHeight(customR.id.lockscreen_clock_view_large, WRAP_CONTENT) + constrainMaxHeight(customR.id.lockscreen_clock_view_large, 0) + constrainWidth(customR.id.lockscreen_clock_view, WRAP_CONTENT) constrainHeight( - R.id.lockscreen_clock_view, - context.resources.getDimensionPixelSize(custR.dimen.small_clock_height), + customR.id.lockscreen_clock_view, + context.resources.getDimensionPixelSize(customR.dimen.small_clock_height), ) connect( - R.id.lockscreen_clock_view, + customR.id.lockscreen_clock_view, START, PARENT_ID, START, - context.resources.getDimensionPixelSize(custR.dimen.clock_padding_start) + + context.resources.getDimensionPixelSize(customR.dimen.clock_padding_start) + context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal), ) val smallClockTopMargin = keyguardClockViewModel.getSmallClockTopMargin() create(R.id.small_clock_guideline_top, ConstraintSet.HORIZONTAL_GUIDELINE) setGuidelineBegin(R.id.small_clock_guideline_top, smallClockTopMargin) - connect(R.id.lockscreen_clock_view, TOP, R.id.small_clock_guideline_top, BOTTOM) + connect(customR.id.lockscreen_clock_view, TOP, R.id.small_clock_guideline_top, BOTTOM) // Explicitly clear pivot to force recalculate pivot instead of using legacy value - setTransformPivot(R.id.lockscreen_clock_view_large, Float.NaN, Float.NaN) + setTransformPivot(customR.id.lockscreen_clock_view_large, Float.NaN, Float.NaN) val smallClockBottom = keyguardClockViewModel.getSmallClockTopMargin() + diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index 782d37b1929c..8d2bfb5bdf40 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -27,11 +27,8 @@ import android.view.WindowManager import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet -import com.android.keyguard.LockIconView -import com.android.keyguard.LockIconViewController import com.android.systemui.biometrics.AuthController import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardBottomAreaRefactor @@ -66,7 +63,6 @@ constructor( private val context: Context, private val notificationPanelView: NotificationPanelView, private val featureFlags: FeatureFlags, - private val lockIconViewController: Lazy<LockIconViewController>, private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>, private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>, private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>, @@ -78,70 +74,44 @@ constructor( private var disposableHandle: DisposableHandle? = null override fun addViews(constraintLayout: ConstraintLayout) { - if ( - !KeyguardBottomAreaRefactor.isEnabled && - !MigrateClocksToBlueprint.isEnabled && - !DeviceEntryUdfpsRefactor.isEnabled - ) { + if (!KeyguardBottomAreaRefactor.isEnabled && !MigrateClocksToBlueprint.isEnabled) { return } - notificationPanelView.findViewById<View>(R.id.lock_icon_view).let { - notificationPanelView.removeView(it) - } - val view = - if (DeviceEntryUdfpsRefactor.isEnabled) { - DeviceEntryIconView( - context, - null, - logger = - LongPressHandlingViewLogger( - logBuffer = logBuffer, - TAG - ) - ) - .apply { id = deviceEntryIconViewId } - } else { - // KeyguardBottomAreaRefactor.isEnabled or MigrateClocksToBlueprint.isEnabled - LockIconView(context, null).apply { id = R.id.lock_icon_view } - } + DeviceEntryIconView( + context, + null, + logger = LongPressHandlingViewLogger(logBuffer = logBuffer, TAG), + ) + .apply { id = deviceEntryIconViewId } + constraintLayout.addView(view) } override fun bindData(constraintLayout: ConstraintLayout) { - if (DeviceEntryUdfpsRefactor.isEnabled) { - constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let { - disposableHandle?.dispose() - disposableHandle = - DeviceEntryIconViewBinder.bind( - applicationScope, - it, - deviceEntryIconViewModel.get(), - deviceEntryForegroundViewModel.get(), - deviceEntryBackgroundViewModel.get(), - falsingManager.get(), - vibratorHelper.get(), - ) - } - } else { - constraintLayout.findViewById<LockIconView?>(R.id.lock_icon_view)?.let { - lockIconViewController.get().setLockIconView(it) - } + constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let { + disposableHandle?.dispose() + disposableHandle = + DeviceEntryIconViewBinder.bind( + applicationScope, + it, + deviceEntryIconViewModel.get(), + deviceEntryForegroundViewModel.get(), + deviceEntryBackgroundViewModel.get(), + falsingManager.get(), + vibratorHelper.get(), + ) } } override fun applyConstraints(constraintSet: ConstraintSet) { - val isUdfpsSupported = - if (DeviceEntryUdfpsRefactor.isEnabled) { - Log.d( - "DefaultDeviceEntrySection", - "isUdfpsSupported=${deviceEntryIconViewModel.get().isUdfpsSupported.value}" - ) - deviceEntryIconViewModel.get().isUdfpsSupported.value - } else { - authController.isUdfpsSupported - } + Log.d( + "DefaultDeviceEntrySection", + "isUdfpsSupported=${deviceEntryIconViewModel.get().isUdfpsSupported.value}", + ) + val isUdfpsSupported = deviceEntryIconViewModel.get().isUdfpsSupported.value + val scaleFactor: Float = authController.scaleFactor val mBottomPaddingPx = context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) @@ -160,31 +130,24 @@ constructor( val iconRadiusPx = (defaultDensity * 36).toInt() if (isUdfpsSupported) { - if (DeviceEntryUdfpsRefactor.isEnabled) { - deviceEntryIconViewModel.get().udfpsLocation.value?.let { udfpsLocation -> - Log.d( - "DeviceEntrySection", - "udfpsLocation=$udfpsLocation, " + - "scaledLocation=(${udfpsLocation.centerX},${udfpsLocation.centerY}), " + - "unusedAuthController=${authController.udfpsLocation}" - ) - centerIcon( - Point(udfpsLocation.centerX.toInt(), udfpsLocation.centerY.toInt()), - udfpsLocation.radius, - constraintSet - ) - } - } else { - authController.udfpsLocation?.let { udfpsLocation -> - Log.d("DeviceEntrySection", "udfpsLocation=$udfpsLocation") - centerIcon(udfpsLocation, authController.udfpsRadius, constraintSet) - } + deviceEntryIconViewModel.get().udfpsLocation.value?.let { udfpsLocation -> + Log.d( + "DeviceEntrySection", + "udfpsLocation=$udfpsLocation, " + + "scaledLocation=(${udfpsLocation.centerX},${udfpsLocation.centerY}), " + + "unusedAuthController=${authController.udfpsLocation}", + ) + centerIcon( + Point(udfpsLocation.centerX.toInt(), udfpsLocation.centerY.toInt()), + udfpsLocation.radius, + constraintSet, + ) } } else { centerIcon( Point( (widthPixels / 2).toInt(), - (heightPixels - ((mBottomPaddingPx + iconRadiusPx) * scaleFactor)).toInt() + (heightPixels - ((mBottomPaddingPx + iconRadiusPx) * scaleFactor)).toInt(), ), iconRadiusPx * scaleFactor, constraintSet, @@ -193,12 +156,8 @@ constructor( } override fun removeViews(constraintLayout: ConstraintLayout) { - if (DeviceEntryUdfpsRefactor.isEnabled) { - constraintLayout.removeView(deviceEntryIconViewId) - disposableHandle?.dispose() - } else { - constraintLayout.removeView(R.id.lock_icon_view) - } + constraintLayout.removeView(deviceEntryIconViewId) + disposableHandle?.dispose() } @VisibleForTesting @@ -213,12 +172,7 @@ constructor( ) } - val iconId = - if (DeviceEntryUdfpsRefactor.isEnabled) { - deviceEntryIconViewId - } else { - R.id.lock_icon_view - } + val iconId = deviceEntryIconViewId constraintSet.apply { constrainWidth(iconId, sensorRect.right - sensorRect.left) @@ -228,14 +182,14 @@ constructor( ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, - sensorRect.top + sensorRect.top, ) connect( iconId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, - sensorRect.left + sensorRect.left, ) } @@ -243,8 +197,8 @@ constructor( // Without this logic, the lock icon location changes but the KeyguardBottomAreaView is not // updated and visible ui layout jank occurs. This is due to AmbientIndicationContainer // being in NPVC and laying out prior to the KeyguardRootView. - // Remove when both DeviceEntryUdfpsRefactor and KeyguardBottomAreaRefactor are enabled. - if (DeviceEntryUdfpsRefactor.isEnabled && !KeyguardBottomAreaRefactor.isEnabled) { + // Remove when KeyguardBottomAreaRefactor is enabled. + if (!KeyguardBottomAreaRefactor.isEnabled) { with(notificationPanelView) { val isUdfpsSupported = deviceEntryIconViewModel.get().isUdfpsSupported.value val bottomAreaViewRight = findViewById<View>(R.id.keyguard_bottom_area)?.right ?: 0 @@ -256,7 +210,7 @@ constructor( ambientLeft, sensorRect.bottom, bottomAreaViewRight - ambientLeft, - ambientTop + it.measuredHeight + ambientTop + it.measuredHeight, ) } else { // make bottom of ambient indication view the top of the lock icon @@ -264,7 +218,7 @@ constructor( ambientLeft, sensorRect.top - it.measuredHeight, bottomAreaViewRight - ambientLeft, - sensorRect.top + sensorRect.top, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt index b33d55244037..604318a2751c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt @@ -22,6 +22,7 @@ import android.view.ViewGroup import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R @@ -67,7 +68,7 @@ constructor( connect( R.id.keyguard_slice_view, ConstraintSet.TOP, - R.id.lockscreen_clock_view, + customR.id.lockscreen_clock_view, ConstraintSet.BOTTOM ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt index edcf97a81ea4..620cc13a0c3a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt @@ -55,11 +55,7 @@ constructor( R.id.nssl_placeholder_barrier_bottom, Barrier.TOP, 0, - *intArrayOf( - R.id.device_entry_icon_view, - R.id.lock_icon_view, - R.id.ambient_indication_container - ) + *intArrayOf(R.id.device_entry_icon_view, R.id.ambient_indication_container), ) connect(placeHolderId, BOTTOM, R.id.nssl_placeholder_barrier_bottom, TOP) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 99160f8a9158..6ddcae38ce92 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -23,6 +23,7 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.customization.R as customR import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.MigrateClocksToBlueprint @@ -157,7 +158,7 @@ constructor( connect( sharedR.id.date_smartspace_view, ConstraintSet.TOP, - R.id.lockscreen_clock_view, + customR.id.lockscreen_clock_view, ConstraintSet.BOTTOM ) connect( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt index 4d914c721d0c..c11005d38986 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt @@ -29,6 +29,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver.OnPreDrawListener import com.android.app.animation.Interpolators +import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_DOWN_MILLIS @@ -242,11 +243,11 @@ class ClockSizeTransition( } ?: run { Log.e(TAG, "No large clock set, falling back") - addTarget(R.id.lockscreen_clock_view_large) + addTarget(customR.id.lockscreen_clock_view_large) } } else { if (DEBUG) Log.i(TAG, "Adding small clock") - addTarget(R.id.lockscreen_clock_view) + addTarget(customR.id.lockscreen_clock_view) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt index 26b2e2b7bd66..424be90ba2ec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt @@ -38,6 +38,6 @@ constructor( ) { val rows = configurationRepository.onConfigurationChange.emitOnStart().map { - resources.getInteger(R.integer.quick_settings_max_rows) + resources.getInteger(R.integer.quick_settings_paginated_grid_num_rows) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt index f7c71ceb9e6c..ee0cfb304db0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt @@ -34,6 +34,6 @@ constructor( ) { val rows = configurationRepository.onConfigurationChange.emitOnStart().map { - resources.getInteger(R.integer.quick_qs_panel_max_rows) + resources.getInteger(R.integer.quick_qs_paginated_grid_num_rows) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt new file mode 100644 index 000000000000..b9994d7bb821 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt @@ -0,0 +1,60 @@ +/* + * 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.qs.panels.ui.compose + +import com.android.compose.animation.Bounceable +import com.android.systemui.qs.panels.shared.model.SizedTile +import com.android.systemui.qs.panels.ui.model.GridCell +import com.android.systemui.qs.panels.ui.model.TileGridCell +import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel + +data class BounceableInfo( + val bounceable: BounceableTileViewModel, + val previousTile: Bounceable?, + val nextTile: Bounceable?, + val bounceEnd: Boolean, +) + +fun List<Pair<GridCell, BounceableTileViewModel>>.bounceableInfo( + index: Int, + columns: Int, +): BounceableInfo { + val cell = this[index].first as TileGridCell + // Only look for neighbor bounceables if they are on the same row + val onLastColumn = cell.onLastColumn(cell.column, columns) + val previousTile = getOrNull(index - 1)?.takeIf { cell.column != 0 } + val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn } + return BounceableInfo(this[index].second, previousTile?.second, nextTile?.second, !onLastColumn) +} + +fun List<BounceableTileViewModel>.bounceableInfo( + sizedTile: SizedTile<TileViewModel>, + index: Int, + column: Int, + columns: Int, +): BounceableInfo { + // Only look for neighbor bounceables if they are on the same row + val onLastColumn = sizedTile.onLastColumn(column, columns) + val previousTile = getOrNull(index - 1)?.takeIf { column != 0 } + val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn } + return BounceableInfo(this[index], previousTile, nextTile, !onLastColumn) +} + +private fun <T> SizedTile<T>.onLastColumn(column: Int, columns: Int): Boolean { + return column == columns - width +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt index 770fd785723a..74fa0fef21d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt @@ -116,9 +116,9 @@ class EditTileListState(tiles: List<SizedTile<EditTileViewModel>>, private val c regenerateGrid() _tiles.add(insertionIndex.coerceIn(0, _tiles.size), cell) } else { - // Add the tile with a temporary row which will get reassigned when + // Add the tile with a temporary row/col which will get reassigned when // regenerating spacers - _tiles.add(insertionIndex.coerceIn(0, _tiles.size), TileGridCell(draggedTile, 0)) + _tiles.add(insertionIndex.coerceIn(0, _tiles.size), TileGridCell(draggedTile, 0, 0)) } regenerateGrid() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index a645b51404e7..f36f45c7942d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -20,6 +20,8 @@ import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.util.fastMap @@ -29,6 +31,7 @@ import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.qs.composefragment.ui.GridAnchor import com.android.systemui.qs.panels.ui.compose.infinitegrid.Tile +import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey import com.android.systemui.res.R @@ -41,7 +44,9 @@ fun SceneScope.QuickQuickSettings( val sizedTiles by viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList()) val tiles = sizedTiles.fastMap { it.tile } + val bounceables = remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } } val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle() + val scope = rememberCoroutineScope() DisposableEffect(tiles) { val token = Any() @@ -49,6 +54,7 @@ fun SceneScope.QuickQuickSettings( onDispose { tiles.forEach { it.stopListening(token) } } } val columns by viewModel.columns.collectAsStateWithLifecycle() + var cellIndex = 0 Box(modifier = modifier) { GridAnchor() VerticalSpannedGrid( @@ -59,11 +65,15 @@ fun SceneScope.QuickQuickSettings( modifier = Modifier.sysuiResTag("qqs_tile_layout"), ) { spanIndex -> val it = sizedTiles[spanIndex] + val column = cellIndex % columns + cellIndex += it.width Tile( tile = it.tile, iconOnly = it.isIcon, modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), squishiness = { squishiness }, + coroutineScope = scope, + bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt index 9ec5a82a18d7..71fa0ac30fb7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -54,6 +55,7 @@ import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.semantics.toggleableState +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.android.compose.modifiers.background import com.android.compose.modifiers.thenIf @@ -64,7 +66,6 @@ import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState import com.android.systemui.res.R -import kotlinx.coroutines.delay private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target" @@ -138,13 +139,20 @@ fun LargeTileLabels( accessibilityUiState: AccessibilityUiState? = null, ) { Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) { - Text(label, color = colors.label, modifier = Modifier.tileMarquee()) + Text( + label, + style = MaterialTheme.typography.labelLarge, + color = colors.label, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) if (!TextUtils.isEmpty(secondaryLabel)) { Text( secondaryLabel ?: "", color = colors.secondaryLabel, + style = MaterialTheme.typography.bodyMedium, modifier = - Modifier.tileMarquee().thenIf( + Modifier.thenIf( accessibilityUiState?.stateDescription?.contains(secondaryLabel ?: "") == true ) { @@ -182,10 +190,7 @@ fun SmallTileContent( rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true) } else { var atEnd by remember(icon.res) { mutableStateOf(false) } - LaunchedEffect(key1 = icon.res) { - delay(350) - atEnd = true - } + LaunchedEffect(key1 = icon.res) { atEnd = true } rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd) } } 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 45c1e48840ee..5c2a2bd2b78c 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 @@ -22,13 +22,13 @@ import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.animateIntAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope @@ -46,7 +46,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyGridItemScope import androidx.compose.foundation.lazy.grid.LazyGridScope import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.rememberScrollState @@ -66,19 +65,17 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInRoot @@ -98,23 +95,26 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastMap -import androidx.compose.ui.zIndex +import com.android.compose.animation.bounceable import com.android.compose.modifiers.background import com.android.compose.modifiers.height import com.android.systemui.common.ui.compose.load import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.SizedTileImpl +import com.android.systemui.qs.panels.ui.compose.BounceableInfo import com.android.systemui.qs.panels.ui.compose.DragAndDropState import com.android.systemui.qs.panels.ui.compose.EditTileListState +import com.android.systemui.qs.panels.ui.compose.bounceableInfo import com.android.systemui.qs.panels.ui.compose.dragAndDropRemoveZone import com.android.systemui.qs.panels.ui.compose.dragAndDropTileList import com.android.systemui.qs.panels.ui.compose.dragAndDropTileSource import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileArrangementPadding +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.ToggleTargetSize import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.CurrentTilesGridPadding import com.android.systemui.qs.panels.ui.compose.selection.MutableSelectionState -import com.android.systemui.qs.panels.ui.compose.selection.ResizingHandle +import com.android.systemui.qs.panels.ui.compose.selection.ResizableTileContainer import com.android.systemui.qs.panels.ui.compose.selection.TileWidths import com.android.systemui.qs.panels.ui.compose.selection.clearSelectionTile import com.android.systemui.qs.panels.ui.compose.selection.rememberSelectionState @@ -122,11 +122,14 @@ import com.android.systemui.qs.panels.ui.compose.selection.selectableTile import com.android.systemui.qs.panels.ui.model.GridCell import com.android.systemui.qs.panels.ui.model.SpacerGridCell import com.android.systemui.qs.panels.ui.model.TileGridCell +import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.shared.model.groupAndSort import com.android.systemui.res.R +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay +import kotlinx.coroutines.launch object TileType @@ -241,15 +244,20 @@ private fun CurrentTilesGrid( onSetTiles: (List<TileSpec>) -> Unit, ) { val currentListState by rememberUpdatedState(listState) - val tileHeight = CommonTileDefaults.TileHeight val totalRows = listState.tiles.lastOrNull()?.row ?: 0 val totalHeight by animateDpAsState( - gridHeight(totalRows + 1, tileHeight, TileArrangementPadding, CurrentTilesGridPadding), + gridHeight(totalRows + 1, TileHeight, TileArrangementPadding, CurrentTilesGridPadding), label = "QSEditCurrentTilesGridHeight", ) val gridState = rememberLazyGridState() var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) } + val coroutineScope = rememberCoroutineScope() + + val cells = + remember(listState.tiles) { + listState.tiles.fastMap { Pair(it, BounceableTileViewModel()) } + } TileLazyGrid( state = gridState, @@ -272,7 +280,7 @@ private fun CurrentTilesGrid( } .testTag(CURRENT_TILES_GRID_TEST_TAG), ) { - EditTiles(listState.tiles, listState, selectionState) { spec -> + EditTiles(cells, columns, listState, selectionState, coroutineScope) { spec -> // Toggle the current size of the tile currentListState.isIcon(spec)?.let { onResize(spec, !it) } } @@ -286,10 +294,10 @@ private fun AvailableTileGrid( columns: Int, dragAndDropState: DragAndDropState, ) { - // Available tiles aren't visible during drag and drop, so the row isn't needed + // Available tiles aren't visible during drag and drop, so the row/col isn't needed val groupedTiles = remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) { - groupAndSort(tiles.fastMap { TileGridCell(it, 0) }) + groupAndSort(tiles.fastMap { TileGridCell(it, 0, 0) }) } val labelColors = EditModeTileDefaults.editTileColors() @@ -349,24 +357,26 @@ private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any { /** * Adds a list of [GridCell] to the lazy grid * - * @param cells the list of [GridCell] + * @param cells the pairs of [GridCell] to [BounceableTileViewModel] * @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 */ fun LazyGridScope.EditTiles( - cells: List<GridCell>, + cells: List<Pair<GridCell, BounceableTileViewModel>>, + columns: Int, dragAndDropState: DragAndDropState, selectionState: MutableSelectionState, + coroutineScope: CoroutineScope, onToggleSize: (spec: TileSpec) -> Unit, ) { items( count = cells.size, - key = { cells[it].key(it, dragAndDropState) }, - span = { cells[it].span }, + key = { cells[it].first.key(it, dragAndDropState) }, + span = { cells[it].first.span }, contentType = { TileType }, ) { index -> - when (val cell = cells[index]) { + when (val cell = cells[index].first) { is TileGridCell -> if (dragAndDropState.isMoving(cell.tile.tileSpec)) { // If the tile is being moved, replace it with a visible spacer @@ -385,6 +395,9 @@ fun LazyGridScope.EditTiles( dragAndDropState = dragAndDropState, selectionState = selectionState, onToggleSize = onToggleSize, + coroutineScope = coroutineScope, + bounceableInfo = cells.bounceableInfo(index, columns), + modifier = Modifier.animateItem(), ) } is SpacerGridCell -> SpacerGridCell() @@ -393,12 +406,15 @@ fun LazyGridScope.EditTiles( } @Composable -private fun LazyGridItemScope.TileGridCell( +private fun TileGridCell( cell: TileGridCell, index: Int, dragAndDropState: DragAndDropState, selectionState: MutableSelectionState, onToggleSize: (spec: TileSpec) -> Unit, + coroutineScope: CoroutineScope, + bounceableInfo: BounceableInfo, + modifier: Modifier = Modifier, ) { val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1) var selected by remember { mutableStateOf(false) } @@ -407,6 +423,9 @@ private fun LazyGridItemScope.TileGridCell( targetValue = if (selected) 1f else 0f, label = "QSEditTileSelectionAlpha", ) + val selectionColor = MaterialTheme.colorScheme.primary + val colors = EditModeTileDefaults.editTileColors() + val currentBounceableInfo by rememberUpdatedState(bounceableInfo) LaunchedEffect(selectionState.selection?.tileSpec) { selectionState.selection?.let { @@ -420,152 +439,61 @@ private fun LazyGridItemScope.TileGridCell( selected = selectionState.selection?.tileSpec == cell.tile.tileSpec } - val modifier = - Modifier.animateItem() - .semantics(mergeDescendants = true) { - this.stateDescription = stateDescription - contentDescription = cell.tile.label.text - customActions = - listOf( - // TODO(b/367748260): Add final accessibility actions - CustomAccessibilityAction("Toggle size") { - onToggleSize(cell.tile.tileSpec) - true - } - ) - } - .height(CommonTileDefaults.TileHeight) - .fillMaxWidth() - - val content = - @Composable { - EditTile( - tileViewModel = cell.tile, - iconOnly = cell.isIcon, - selectionAlpha = { selectionAlpha }, - modifier = - Modifier.fillMaxSize() - .selectableTile(cell.tile.tileSpec, selectionState) - .dragAndDropTileSource( - SizedTileImpl(cell.tile, cell.width), - dragAndDropState, - selectionState::unSelect, - ), - ) - } - - if (selected) { - SelectedTile( - isIcon = cell.isIcon, - selectionAlpha = { selectionAlpha }, - selectionState = selectionState, - modifier = modifier.zIndex(2f), // 2f to display this tile over neighbors when dragged - content = content, - ) - } else { - UnselectedTile( - selectionAlpha = { selectionAlpha }, - selectionState = selectionState, - modifier = modifier, - content = content, - ) - } -} - -@Composable -private fun SelectedTile( - isIcon: Boolean, - selectionAlpha: () -> Float, - selectionState: MutableSelectionState, - modifier: Modifier = Modifier, - content: @Composable () -> Unit, -) { // Current base, min and max width of this tile var tileWidths: TileWidths? by remember { mutableStateOf(null) } - - // Animated diff between the current width and the resized width of the tile. We can't use - // animateContentSize here as the tile is sometimes unbounded. - val remainingOffset by - animateIntAsState( - selectionState.resizingState?.let { tileWidths?.base?.minus(it.width) ?: 0 } ?: 0, - label = "QSEditTileWidthOffset", - ) - val padding = with(LocalDensity.current) { TileArrangementPadding.roundToPx() } - Box( - modifier.onSizeChanged { - val min = if (isIcon) it.width else (it.width - padding) / 2 - val max = if (isIcon) (it.width * 2) + padding else it.width - tileWidths = TileWidths(it.width, min, max) - } + + ResizableTileContainer( + selected = selected, + selectionState = selectionState, + selectionAlpha = { selectionAlpha }, + selectionColor = selectionColor, + tileWidths = { tileWidths }, + modifier = + modifier + .height(TileHeight) + .fillMaxWidth() + .onSizeChanged { + // Grab the size before the bounceable to get the idle width + val min = if (cell.isIcon) it.width else (it.width - padding) / 2 + val max = if (cell.isIcon) (it.width * 2) + padding else it.width + tileWidths = TileWidths(it.width, min, max) + } + .bounceable( + bounceable = currentBounceableInfo.bounceable, + previousBounceable = currentBounceableInfo.previousTile, + nextBounceable = currentBounceableInfo.nextTile, + orientation = Orientation.Horizontal, + bounceEnd = currentBounceableInfo.bounceEnd, + ), ) { - val handle = - @Composable { - ResizingHandle( - enabled = true, - selectionState = selectionState, - transition = selectionAlpha, - tileWidths = { tileWidths }, + Box( + modifier + .fillMaxSize() + .semantics(mergeDescendants = true) { + this.stateDescription = stateDescription + contentDescription = cell.tile.label.text + customActions = + listOf( + // TODO(b/367748260): Add final accessibility actions + CustomAccessibilityAction("Toggle size") { + onToggleSize(cell.tile.tileSpec) + true + } + ) + } + .selectableTile(cell.tile.tileSpec, selectionState) { + coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() } + } + .dragAndDropTileSource( + SizedTileImpl(cell.tile, cell.width), + dragAndDropState, + selectionState::unSelect, ) - } - - Layout(contents = listOf(content, handle)) { - (contentMeasurables, handleMeasurables), - constraints -> - // Grab the width from the resizing state if a resize is in progress, otherwise fill the - // max width - val width = - selectionState.resizingState?.width ?: (constraints.maxWidth - remainingOffset) - val contentPlaceable = - contentMeasurables.first().measure(constraints.copy(maxWidth = width)) - val handlePlaceable = handleMeasurables.first().measure(constraints) - - // Place the dot vertically centered on the right edge - val handleX = contentPlaceable.width - (handlePlaceable.width / 2) - val handleY = (contentPlaceable.height / 2) - (handlePlaceable.height / 2) - - layout(constraints.maxWidth, constraints.maxHeight) { - contentPlaceable.place(0, 0) - handlePlaceable.place(handleX, handleY) - } - } - } -} - -@Composable -private fun UnselectedTile( - selectionAlpha: () -> Float, - selectionState: MutableSelectionState, - modifier: Modifier = Modifier, - content: @Composable () -> Unit, -) { - val handle = - @Composable { - ResizingHandle( - enabled = false, - selectionState = selectionState, - transition = selectionAlpha, - ) - } - - Box(modifier) { - Layout(contents = listOf(content, handle)) { - (contentMeasurables, handleMeasurables), - constraints -> - val contentPlaceable = - contentMeasurables - .first() - .measure(constraints.copy(maxWidth = constraints.maxWidth)) - val handlePlaceable = handleMeasurables.first().measure(constraints) - - // Place the dot vertically centered on the right edge - val handleX = contentPlaceable.width - (handlePlaceable.width / 2) - val handleY = (contentPlaceable.height / 2) - (handlePlaceable.height / 2) - - layout(constraints.maxWidth, constraints.maxHeight) { - contentPlaceable.place(0, 0) - handlePlaceable.place(handleX, handleY) - } + .tileBackground(colors.background) + .tilePadding() + ) { + EditTile(tile = cell.tile, iconOnly = cell.isIcon) } } } @@ -588,19 +516,19 @@ private fun AvailableTileGridCell( verticalArrangement = spacedBy(CommonTileDefaults.TilePadding, Alignment.Top), modifier = modifier, ) { - EditTileContainer( - colors = colors, - modifier = - Modifier.fillMaxWidth() - .height(CommonTileDefaults.TileHeight) - .clearSelectionTile(selectionState) - .semantics(mergeDescendants = true) { - onClick(onClickActionName) { false } - this.stateDescription = stateDescription - } - .dragAndDropTileSource(SizedTileImpl(cell.tile, cell.width), dragAndDropState) { - selectionState.unSelect() - }, + Box( + Modifier.fillMaxWidth() + .height(TileHeight) + .clearSelectionTile(selectionState) + .semantics(mergeDescendants = true) { + onClick(onClickActionName) { false } + this.stateDescription = stateDescription + } + .dragAndDropTileSource(SizedTileImpl(cell.tile, cell.width), dragAndDropState) { + selectionState.unSelect() + } + .tileBackground(colors.background) + .tilePadding() ) { // Icon SmallTileContent( @@ -626,16 +554,14 @@ private fun AvailableTileGridCell( @Composable private fun SpacerGridCell(modifier: Modifier = Modifier) { // By default, spacers are invisible and exist purely to catch drag movements - Box(modifier.height(CommonTileDefaults.TileHeight).fillMaxWidth()) + Box(modifier.height(TileHeight).fillMaxWidth()) } @Composable -fun EditTile( - tileViewModel: EditTileViewModel, +fun BoxScope.EditTile( + tile: EditTileViewModel, iconOnly: Boolean, - modifier: Modifier = Modifier, colors: TileColors = EditModeTileDefaults.editTileColors(), - selectionAlpha: () -> Float = { 1f }, ) { // Animated horizontal alignment from center (0f) to start (-1f) val alignmentValue by @@ -646,68 +572,36 @@ fun EditTile( val alignment by remember { derivedStateOf { BiasAlignment(horizontalBias = alignmentValue, verticalBias = 0f) } } + // Icon + Box(Modifier.size(ToggleTargetSize).align(alignment)) { + SmallTileContent( + icon = tile.icon, + color = colors.icon, + animateToEnd = true, + modifier = Modifier.align(Alignment.Center), + ) + } - EditTileContainer(colors = colors, selectionAlpha = selectionAlpha, modifier = modifier) { - // Icon - Box(Modifier.size(ToggleTargetSize).align(alignment)) { - SmallTileContent( - icon = tileViewModel.icon, - color = colors.icon, - animateToEnd = true, - modifier = Modifier.align(Alignment.Center), - ) - } - - // Labels, positioned after the icon - AnimatedVisibility(visible = !iconOnly, enter = fadeIn(), exit = fadeOut()) { - LargeTileLabels( - label = tileViewModel.label.text, - secondaryLabel = tileViewModel.appName?.text, - colors = colors, - modifier = Modifier.padding(start = ToggleTargetSize + TileArrangementPadding), - ) - } + // Labels, positioned after the icon + AnimatedVisibility(visible = !iconOnly, enter = fadeIn(), exit = fadeOut()) { + LargeTileLabels( + label = tile.label.text, + secondaryLabel = tile.appName?.text, + colors = colors, + modifier = Modifier.padding(start = ToggleTargetSize + TileArrangementPadding), + ) } } -@Composable -private fun EditTileContainer( - colors: TileColors, - modifier: Modifier = Modifier, - selectionAlpha: () -> Float = { 0f }, - selectionColor: Color = MaterialTheme.colorScheme.primary, - content: @Composable BoxScope.() -> Unit = {}, -) { - Box( - Modifier.wrapContentSize().drawWithContent { - drawContent() - drawRoundRect( - SolidColor(selectionColor), - cornerRadius = CornerRadius(InactiveCornerRadius.toPx()), - style = Stroke(EditModeTileDefaults.SelectedBorderWidth.toPx()), - alpha = selectionAlpha(), - ) - } - ) { - Box( - modifier = - modifier - .drawBehind { - drawRoundRect( - SolidColor(colors.background), - cornerRadius = CornerRadius(InactiveCornerRadius.toPx()), - ) - } - .tilePadding(), - content = content, - ) +private fun Modifier.tileBackground(color: Color): Modifier { + return drawBehind { + drawRoundRect(SolidColor(color), cornerRadius = CornerRadius(InactiveCornerRadius.toPx())) } } private object EditModeTileDefaults { const val PLACEHOLDER_ALPHA = .3f val EditGridHeaderHeight = 60.dp - val SelectedBorderWidth = 2.dp val CurrentTilesGridPadding = 8.dp @Composable diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 6920e498bdde..e5c213519415 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.util.fastMap @@ -29,7 +30,9 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.qs.panels.shared.model.SizedTileImpl import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout +import com.android.systemui.qs.panels.ui.compose.bounceableInfo import com.android.systemui.qs.panels.ui.compose.rememberEditListState +import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel import com.android.systemui.qs.panels.ui.viewmodel.QSColumnsViewModel @@ -62,7 +65,11 @@ constructor( } val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) } + val bounceables = + remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } } val squishiness by squishinessViewModel.squishiness.collectAsStateWithLifecycle() + val scope = rememberCoroutineScope() + var cellIndex = 0 VerticalSpannedGrid( columns = columns, @@ -71,11 +78,15 @@ constructor( spans = sizedTiles.fastMap { it.width }, ) { spanIndex -> val it = sizedTiles[spanIndex] + val column = cellIndex % columns + cellIndex += it.width Tile( tile = it.tile, iconOnly = iconTilesViewModel.isIconTile(it.tile.spec), modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), squishiness = { squishiness }, + coroutineScope = scope, + bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt index 4bd5b2d68c4c..52d526123430 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt @@ -23,8 +23,8 @@ import android.service.quicksettings.Tile.STATE_ACTIVE import android.service.quicksettings.Tile.STATE_INACTIVE import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box @@ -44,6 +44,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -61,11 +62,13 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Expandable +import com.android.compose.animation.bounceable import com.android.compose.modifiers.thenIf import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.panels.ui.compose.BounceableInfo import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel import com.android.systemui.qs.panels.ui.viewmodel.TileUiState @@ -74,6 +77,8 @@ import com.android.systemui.qs.panels.ui.viewmodel.toUiState import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.res.R import java.util.function.Supplier +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch private const val TEST_TAG_SMALL = "qs_tile_small" private const val TEST_TAG_LARGE = "qs_tile_large" @@ -102,9 +107,12 @@ fun Tile( tile: TileViewModel, iconOnly: Boolean, squishiness: () -> Float, + coroutineScope: CoroutineScope, + bounceableInfo: BounceableInfo, modifier: Modifier = Modifier, ) { val state by tile.state.collectAsStateWithLifecycle(tile.currentState) + val currentBounceableInfo by rememberUpdatedState(bounceableInfo) val resources = resources() val uiState = remember(state, resources) { state.toUiState(resources) } val colors = TileDefaults.getColorForState(uiState) @@ -112,7 +120,7 @@ fun Tile( // TODO(b/361789146): Draw the shapes instead of clipping val tileShape = TileDefaults.animateTileShape(uiState.state) - TileContainer( + TileExpandable( color = if (iconOnly || !uiState.handlesSecondaryClick) { colors.iconBackground @@ -120,93 +128,101 @@ fun Tile( colors.background }, shape = tileShape, - iconOnly = iconOnly, - onClick = tile::onClick, - onLongClick = tile::onLongClick, - uiState = uiState, squishiness = squishiness, - modifier = modifier, + modifier = + modifier + .fillMaxWidth() + .bounceable( + bounceable = currentBounceableInfo.bounceable, + previousBounceable = currentBounceableInfo.previousTile, + nextBounceable = currentBounceableInfo.nextTile, + orientation = Orientation.Horizontal, + bounceEnd = currentBounceableInfo.bounceEnd, + ), ) { expandable -> - val icon = getTileIcon(icon = uiState.icon) - if (iconOnly) { - SmallTileContent( - icon = icon, - color = colors.icon, - modifier = Modifier.align(Alignment.Center), - ) - } else { - val iconShape = TileDefaults.animateIconShape(uiState.state) - LargeTileContent( - label = uiState.label, - secondaryLabel = uiState.secondaryLabel, - icon = icon, - colors = colors, - iconShape = iconShape, - toggleClickSupported = state.handlesSecondaryClick, - onClick = { - if (state.handlesSecondaryClick) { - tile.onSecondaryClick() - } - }, - onLongClick = { tile.onLongClick(expandable) }, - accessibilityUiState = uiState.accessibilityUiState, - squishiness = squishiness, - ) + TileContainer( + onClick = { + tile.onClick(expandable) + if (uiState.accessibilityUiState.toggleableState != null) { + coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() } + } + }, + onLongClick = { tile.onLongClick(expandable) }, + uiState = uiState, + iconOnly = iconOnly, + ) { + val icon = getTileIcon(icon = uiState.icon) + if (iconOnly) { + SmallTileContent( + icon = icon, + color = colors.icon, + modifier = Modifier.align(Alignment.Center), + ) + } else { + val iconShape = TileDefaults.animateIconShape(uiState.state) + LargeTileContent( + label = uiState.label, + secondaryLabel = uiState.secondaryLabel, + icon = icon, + colors = colors, + iconShape = iconShape, + toggleClickSupported = state.handlesSecondaryClick, + onClick = { + if (state.handlesSecondaryClick) { + tile.onSecondaryClick() + } + }, + onLongClick = { tile.onLongClick(expandable) }, + accessibilityUiState = uiState.accessibilityUiState, + squishiness = squishiness, + ) + } } } } @Composable -private fun TileContainer( +private fun TileExpandable( color: Color, shape: Shape, - iconOnly: Boolean, - uiState: TileUiState, squishiness: () -> Float, modifier: Modifier = Modifier, - onClick: (Expandable) -> Unit = {}, - onLongClick: (Expandable) -> Unit = {}, - content: @Composable BoxScope.(Expandable) -> Unit, + content: @Composable (Expandable) -> Unit, ) { Expandable( color = color, shape = shape, modifier = modifier.clip(shape).verticalSquish(squishiness), ) { - val longPressLabel = longPressLabel() - Box( - modifier = - Modifier.height(CommonTileDefaults.TileHeight) - .fillMaxWidth() - .combinedClickable( - onClick = { onClick(it) }, - onLongClick = { onLongClick(it) }, - onClickLabel = uiState.accessibilityUiState.clickLabel, - onLongClickLabel = longPressLabel, - ) - .semantics { - role = uiState.accessibilityUiState.accessibilityRole - if (uiState.accessibilityUiState.accessibilityRole == Role.Switch) { - uiState.accessibilityUiState.toggleableState?.let { - toggleableState = it - } - } - stateDescription = uiState.accessibilityUiState.stateDescription - } - .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE) - .thenIf(iconOnly) { - Modifier.semantics { - contentDescription = uiState.accessibilityUiState.contentDescription - } - } - .tilePadding() - ) { - content(it) - } + content(it) } } @Composable +fun TileContainer( + onClick: () -> Unit, + onLongClick: () -> Unit, + uiState: TileUiState, + iconOnly: Boolean, + content: @Composable BoxScope.() -> Unit, +) { + Box( + modifier = + Modifier.height(CommonTileDefaults.TileHeight) + .fillMaxWidth() + .tileCombinedClickable( + onClick = onClick, + onLongClick = onLongClick, + uiState = uiState, + iconOnly = iconOnly, + ) + .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE) + .tilePadding(), + content = content, + ) +} + +@Composable private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon { val context = LocalContext.current return icon.get()?.let { @@ -222,14 +238,38 @@ fun tileHorizontalArrangement(): Arrangement.Horizontal { return spacedBy(space = CommonTileDefaults.TileArrangementPadding, alignment = Alignment.Start) } -fun Modifier.tileMarquee(): Modifier { - return basicMarquee(iterations = 1, initialDelayMillis = 200) -} - fun Modifier.tilePadding(): Modifier { return padding(CommonTileDefaults.TilePadding) } +@Composable +fun Modifier.tileCombinedClickable( + onClick: () -> Unit, + onLongClick: () -> Unit, + uiState: TileUiState, + iconOnly: Boolean, +): Modifier { + val longPressLabel = longPressLabel() + return combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + onClickLabel = uiState.accessibilityUiState.clickLabel, + onLongClickLabel = longPressLabel, + ) + .semantics { + role = uiState.accessibilityUiState.accessibilityRole + if (uiState.accessibilityUiState.accessibilityRole == Role.Switch) { + uiState.accessibilityUiState.toggleableState?.let { toggleableState = it } + } + stateDescription = uiState.accessibilityUiState.stateDescription + } + .thenIf(iconOnly) { + Modifier.semantics { + contentDescription = uiState.accessibilityUiState.contentDescription + } + } +} + data class TileColors( val background: Color, val iconBackground: Color, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt index 441d96289d86..1d36aee4eb85 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt @@ -89,8 +89,11 @@ class MutableSelectionState( * Listens for click events to select/unselect the given [TileSpec]. Use this on current tiles as * they can be selected. */ -@Composable -fun Modifier.selectableTile(tileSpec: TileSpec, selectionState: MutableSelectionState): Modifier { +fun Modifier.selectableTile( + tileSpec: TileSpec, + selectionState: MutableSelectionState, + onClick: () -> Unit = {}, +): Modifier { return pointerInput(Unit) { detectTapGestures( onTap = { @@ -99,6 +102,7 @@ fun Modifier.selectableTile(tileSpec: TileSpec, selectionState: MutableSelection } else { selectionState.select(tileSpec, manual = true) } + onClick() } ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt index e0f0b6aa8919..9f13a3788f53 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt @@ -16,63 +16,117 @@ package com.android.systemui.qs.panels.ui.compose.selection +import androidx.compose.animation.core.animateIntAsState import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.size import androidx.compose.foundation.systemGestureExclusion import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.layout +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.toSize +import androidx.compose.ui.zIndex +import com.android.compose.modifiers.thenIf +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.ResizingDotSize +import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.SelectedBorderWidth /** - * Dot handling resizing drag events. Use this on the selected tile to resize it + * Places a dot to handle resizing drag events. Use this on tiles to resize. * - * @param enabled whether resizing drag events should be handled + * The dot is placed vertically centered on the right border. The [content] will have a border when + * selected. + * + * @param selected whether resizing drag events should be handled * @param selectionState the [MutableSelectionState] on the grid - * @param transition the animated value for the dot, used for its alpha and scale + * @param selectionAlpha the animated value for the dot and border alpha + * @param selectionColor the [Color] of the dot and border * @param tileWidths the [TileWidths] of the selected tile - * @param onResize the callback when the drag passes the resizing threshold */ @Composable -fun ResizingHandle( +fun ResizableTileContainer( + selected: Boolean, + selectionState: MutableSelectionState, + selectionAlpha: () -> Float, + selectionColor: Color, + tileWidths: () -> TileWidths?, + modifier: Modifier = Modifier, + content: @Composable BoxScope.() -> Unit = {}, +) { + Box( + modifier + .resizable(selected, selectionState, tileWidths) + .selectionBorder(selectionColor, selectionAlpha) + ) { + content() + ResizingHandle( + enabled = selected, + selectionState = selectionState, + transition = selectionAlpha, + tileWidths = tileWidths, + modifier = + // Higher zIndex to make sure the handle is drawn above the content + Modifier.zIndex(2f), + ) + } +} + +@Composable +private fun ResizingHandle( enabled: Boolean, selectionState: MutableSelectionState, transition: () -> Float, - tileWidths: () -> TileWidths? = { null }, + tileWidths: () -> TileWidths?, + modifier: Modifier = Modifier, ) { - if (enabled) { - // Manually creating the touch target around the resizing dot to ensure that the next tile - // does - // not receive the touch input accidentally. - val minTouchTargetSize = LocalMinimumInteractiveComponentSize.current - Box( - Modifier.size(minTouchTargetSize) - .systemGestureExclusion { Rect(Offset.Zero, it.size.toSize()) } - .pointerInput(Unit) { - detectHorizontalDragGestures( - onHorizontalDrag = { _, offset -> selectionState.onResizingDrag(offset) }, - onDragStart = { - tileWidths()?.let { selectionState.onResizingDragStart(it) } - }, - onDragEnd = selectionState::onResizingDragEnd, - onDragCancel = selectionState::onResizingDragEnd, + // Manually creating the touch target around the resizing dot to ensure that the next tile + // does not receive the touch input accidentally. + val minTouchTargetSize = LocalMinimumInteractiveComponentSize.current + Box( + modifier + .layout { measurable, constraints -> + val size = minTouchTargetSize.roundToPx() + val placeable = measurable.measure(Constraints(size, size, size, size)) + layout(placeable.width, placeable.height) { + placeable.place( + x = constraints.maxWidth - placeable.width / 2, + y = constraints.maxHeight / 2 - placeable.height / 2, ) } - ) { - ResizingDot(transition = transition, modifier = Modifier.align(Alignment.Center)) - } - } else { - ResizingDot(transition = transition) + } + .thenIf(enabled) { + Modifier.systemGestureExclusion { Rect(Offset.Zero, it.size.toSize()) } + .pointerInput(Unit) { + detectHorizontalDragGestures( + onHorizontalDrag = { _, offset -> + selectionState.onResizingDrag(offset) + }, + onDragStart = { + tileWidths()?.let { selectionState.onResizingDragStart(it) } + }, + onDragEnd = selectionState::onResizingDragEnd, + onDragCancel = selectionState::onResizingDragEnd, + ) + } + } + ) { + ResizingDot(transition = transition, modifier = Modifier.align(Alignment.Center)) } } @@ -88,6 +142,45 @@ private fun ResizingDot( } } +private fun Modifier.selectionBorder( + selectionColor: Color, + selectionAlpha: () -> Float = { 0f }, +): Modifier { + return drawWithContent { + drawContent() + drawRoundRect( + SolidColor(selectionColor), + cornerRadius = CornerRadius(InactiveCornerRadius.toPx()), + style = Stroke(SelectedBorderWidth.toPx()), + alpha = selectionAlpha(), + ) + } +} + +@Composable +private fun Modifier.resizable( + selected: Boolean, + selectionState: MutableSelectionState, + tileWidths: () -> TileWidths?, +): Modifier { + if (!selected) return zIndex(1f) + + // Animated diff between the current width and the resized width of the tile. We can't use + // animateContentSize here as the tile is sometimes unbounded. + val remainingOffset by + animateIntAsState( + selectionState.resizingState?.let { tileWidths()?.base?.minus(it.width) ?: 0 } ?: 0, + label = "QSEditTileWidthOffset", + ) + return zIndex(2f).layout { measurable, constraints -> + // Grab the width from the resizing state if a resize is in progress + val width = selectionState.resizingState?.width ?: (constraints.maxWidth - remainingOffset) + val placeable = measurable.measure(constraints.copy(minWidth = width, maxWidth = width)) + layout(constraints.maxWidth, placeable.height) { placeable.place(0, 0) } + } +} + private object SelectionDefaults { val ResizingDotSize = 16.dp + val SelectedBorderWidth = 2.dp } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt index b1841c4c5ffa..c0441f8a38a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt @@ -31,8 +31,8 @@ sealed interface GridCell { } /** - * Represents a [EditTileViewModel] from a grid associated with a tile format and the row it's - * positioned at + * Represents a [EditTileViewModel] from a grid associated with a tile format and the row and column + * it's positioned at */ @Immutable data class TileGridCell( @@ -41,13 +41,15 @@ data class TileGridCell( override val width: Int, override val span: GridItemSpan = GridItemSpan(width), override val s: String = "${tile.tileSpec.spec}-$row-$width", + val column: Int, ) : GridCell, SizedTile<EditTileViewModel>, CategoryAndName by tile { val key: String = "${tile.tileSpec.spec}-$row" constructor( sizedTile: SizedTile<EditTileViewModel>, row: Int, - ) : this(tile = sizedTile.tile, row = row, width = sizedTile.width) + column: Int, + ) : this(tile = sizedTile.tile, row = row, column = column, width = sizedTile.width) } /** Represents an empty space used to fill incomplete rows. Will always display as a 1x1 tile */ @@ -73,7 +75,13 @@ fun List<SizedTile<EditTileViewModel>>.toGridCells( return splitInRowsSequence(this, columns) .flatMapIndexed { rowIndex, sizedTiles -> val correctedRowIndex = rowIndex + startingRow - val row: List<GridCell> = sizedTiles.map { TileGridCell(it, correctedRowIndex) } + var column = 0 + val row: List<GridCell> = + sizedTiles.map { + TileGridCell(it, correctedRowIndex, column).also { cell -> + column += cell.width + } + } // Fill the incomplete rows with spacers val numSpacers = columns - sizedTiles.sumOf { it.width } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/BounceableTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/BounceableTileViewModel.kt new file mode 100644 index 000000000000..506b05256880 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/BounceableTileViewModel.kt @@ -0,0 +1,38 @@ +/* + * 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.qs.panels.ui.viewmodel + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.VectorConverter +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.android.compose.animation.Bounceable + +class BounceableTileViewModel : Bounceable { + private val animatableBounce = Animatable(0.dp, Dp.VectorConverter) + override val bounce: Dp + get() = animatableBounce.value + + suspend fun animateBounce() { + animatableBounce.animateTo(BounceSize) + animatableBounce.animateTo(0.dp) + } + + private companion object { + val BounceSize = 8.dp + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt index ee12736f6db4..be6ce5c5b4f4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt @@ -43,13 +43,13 @@ data class UnloadedEditTileViewModel( ) { fun load(context: Context): EditTileViewModel { return EditTileViewModel( - tileSpec, - icon, - label.toAnnotatedString(context) ?: AnnotatedString(tileSpec.spec), - appName?.toAnnotatedString(context), - isCurrent, - availableEditActions, - category, + tileSpec = tileSpec, + icon = icon, + label = label.toAnnotatedString(context) ?: AnnotatedString(tileSpec.spec), + appName = appName?.toAnnotatedString(context), + isCurrent = isCurrent, + availableEditActions = availableEditActions, + category = category, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 757e37e9341f..d652d5bec27b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -123,7 +123,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpsysTableLogger; @@ -1859,16 +1858,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Returns space between top of lock icon and bottom of NotificationStackScrollLayout. */ private float getLockIconPadding() { float lockIconPadding = 0f; - if (DeviceEntryUdfpsRefactor.isEnabled()) { - View deviceEntryIconView = mKeyguardViewConfigurator.getKeyguardRootView() - .findViewById(R.id.device_entry_icon_view); - if (deviceEntryIconView != null) { - lockIconPadding = mNotificationStackScrollLayoutController.getBottom() - - deviceEntryIconView.getTop(); - } - } else if (mLockIconViewController.getTop() != 0f) { + View deviceEntryIconView = mKeyguardViewConfigurator.getKeyguardRootView() + .findViewById(R.id.device_entry_icon_view); + if (deviceEntryIconView != null) { lockIconPadding = mNotificationStackScrollLayoutController.getBottom() - - mLockIconViewController.getTop(); + - deviceEntryIconView.getTop(); } return lockIconPadding; } @@ -5059,8 +5053,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return false; } - if (DeviceEntryUdfpsRefactor.isEnabled() - && mAlternateBouncerInteractor.isVisibleState()) { + if (mAlternateBouncerInteractor.isVisibleState()) { // never send touches to shade if the alternate bouncer is showing return false; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index b7a95e989317..0e30f2b4bb30 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -38,7 +38,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import dagger.Lazy; @@ -62,7 +62,7 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl { private final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarStateController mStatusBarStateController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private final StatusBarWindowController mStatusBarWindowController; + private final StatusBarWindowControllerStore mStatusBarWindowControllerStore; private final DeviceProvisionedController mDeviceProvisionedController; private final Lazy<NotificationShadeWindowViewController> mNotifShadeWindowViewController; @@ -83,7 +83,7 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl { KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, - StatusBarWindowController statusBarWindowController, + StatusBarWindowControllerStore statusBarWindowControllerStore, DeviceProvisionedController deviceProvisionedController, NotificationShadeWindowController notificationShadeWindowController, @DisplayId int displayId, @@ -102,7 +102,7 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl { mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor; mNpvc = shadeViewControllerLazy; mStatusBarStateController = statusBarStateController; - mStatusBarWindowController = statusBarWindowController; + mStatusBarWindowControllerStore = statusBarWindowControllerStore; mDeviceProvisionedController = deviceProvisionedController; mGutsManager = gutsManager; mNotificationShadeWindowController = notificationShadeWindowController; @@ -315,7 +315,7 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl { // Update the visibility of notification shade and status bar window. mNotificationShadeWindowController.setPanelVisible(false); - mStatusBarWindowController.setForceStatusBarVisible(false); + mStatusBarWindowControllerStore.getDefaultDisplay().setForceStatusBarVisible(false); // Close any guts that might be visible mGutsManager.get().closeAndSaveGuts( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt index 3ad76b719470..7eff8124368b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.core import android.app.Fragment import androidx.annotation.VisibleForTesting import com.android.systemui.CoreStartable -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.res.R import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener @@ -27,9 +26,11 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions import com.android.systemui.statusbar.phone.PhoneStatusBarViewController import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent -import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import java.lang.IllegalStateException -import javax.inject.Inject import javax.inject.Provider /** @@ -65,13 +66,17 @@ interface StatusBarInitializer { statusBarTransitions: PhoneStatusBarTransitions, ) } + + interface Factory { + fun create(displayId: Int): StatusBarInitializer + } } -@SysUISingleton class StatusBarInitializerImpl -@Inject +@AssistedInject constructor( - private val windowController: StatusBarWindowController, + @Assisted private val displayId: Int, + private val statusBarWindowControllerStore: StatusBarWindowControllerStore, private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>, private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>, ) : CoreStartable, StatusBarInitializer { @@ -106,7 +111,7 @@ constructor( private fun doStart() { initialized = true - windowController.fragmentHostManager + statusBarWindowControllerStore.defaultDisplay.fragmentHostManager .addTagListener( CollapsedStatusBarFragment.TAG, object : FragmentHostManager.FragmentListener { @@ -137,4 +142,9 @@ constructor( ) .commit() } + + @AssistedFactory + interface Factory : StatusBarInitializer.Factory { + override fun create(displayId: Int): StatusBarInitializerImpl + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt new file mode 100644 index 000000000000..8d044bb9ce87 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.core + +import android.view.Display +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.display.data.repository.DisplayRepository +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** Provides per display instances of [StatusBarInitializer]. */ +interface StatusBarInitializerStore { + /** + * The instance for the default/main display of the device. For example, on a phone or a tablet, + * the default display is the internal/built-in display of the device. + * + * Note that the id of the default display is [Display.DEFAULT_DISPLAY]. + */ + val defaultDisplay: StatusBarInitializer + + /** + * Returns an instance for a specific display id. + * + * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing + * displays. + */ + fun forDisplay(displayId: Int): StatusBarInitializer +} + +@SysUISingleton +class MultiDisplayStatusBarInitializerStore +@Inject +constructor( + @Background private val backgroundApplicationScope: CoroutineScope, + private val factory: StatusBarInitializer.Factory, + private val displayRepository: DisplayRepository, +) : StatusBarInitializerStore, CoreStartable { + + init { + StatusBarConnectedDisplays.assertInNewMode() + } + + private val perDisplayInitializers = ConcurrentHashMap<Int, StatusBarInitializer>() + + override val defaultDisplay: StatusBarInitializer + get() = forDisplay(Display.DEFAULT_DISPLAY) + + override fun forDisplay(displayId: Int): StatusBarInitializer { + if (displayRepository.getDisplay(displayId) == null) { + throw IllegalArgumentException("Display with id $displayId doesn't exist.") + } + return perDisplayInitializers.computeIfAbsent(displayId) { factory.create(displayId) } + } + + override fun start() { + backgroundApplicationScope.launch( + CoroutineName("MultiDisplayStatusBarInitializerStore#start") + ) { + displayRepository.displayRemovalEvent.collect { removedDisplayId -> + perDisplayInitializers.remove(removedDisplayId) + } + } + } +} + +@SysUISingleton +class SingleDisplayStatusBarInitializerStore +@Inject +constructor(factory: StatusBarInitializerImpl.Factory) : StatusBarInitializerStore { + + init { + StatusBarConnectedDisplays.assertInLegacyMode() + } + + private val defaultInstance = factory.create(Display.DEFAULT_DISPLAY) + + override val defaultDisplay: StatusBarInitializer = defaultInstance + + override fun forDisplay(displayId: Int): StatusBarInitializer = defaultDisplay +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt index 8bd990b83a63..d372eb29b27b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt @@ -36,7 +36,7 @@ import com.android.systemui.statusbar.phone.AutoHideController import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions import com.android.systemui.statusbar.phone.PhoneStatusBarViewController -import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.statusbar.window.data.model.StatusBarWindowState import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore import com.android.wm.shell.bubbles.Bubbles @@ -63,8 +63,8 @@ class StatusBarOrchestrator constructor( @Application private val applicationScope: CoroutineScope, private val statusBarInitializer: StatusBarInitializer, - private val statusBarWindowController: StatusBarWindowController, private val statusBarModeRepository: StatusBarModeRepositoryStore, + private val statusBarWindowControllerStore: StatusBarWindowControllerStore, private val demoModeController: DemoModeController, private val pluginDependencyProvider: PluginDependencyProvider, private val autoHideController: AutoHideController, @@ -157,7 +157,7 @@ constructor( private fun createAndAddWindow() { initializeStatusBarFragment() - statusBarWindowController.attach() + statusBarWindowControllerStore.defaultDisplay.attach() } private fun initializeStatusBarFragment() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index cd1642eee4b4..5aad11fe1034 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -77,16 +77,6 @@ abstract class StatusBarModule { @Provides @SysUISingleton - fun defaultStatusBarWindowController( - context: Context, - viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, - factory: StatusBarWindowControllerImpl.Factory, - ): StatusBarWindowController { - return factory.create(context, viewCaptureAwareWindowManager) - } - - @Provides - @SysUISingleton fun windowControllerStore( multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>, singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt index 118f5f0515be..bf7e879bb72f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt @@ -34,7 +34,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.res.R import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider -import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.animation.AnimationUtil.Companion.frames import javax.inject.Inject import kotlin.math.roundToInt @@ -44,8 +44,8 @@ import kotlin.math.roundToInt */ class SystemEventChipAnimationController @Inject constructor( private val context: Context, - private val statusBarWindowController: StatusBarWindowController, - private val contentInsetsProvider: StatusBarContentInsetsProvider + private val statusBarWindowControllerStore: StatusBarWindowControllerStore, + private val contentInsetsProvider: StatusBarContentInsetsProvider, ) : SystemStatusAnimationCallback { private lateinit var animationWindowView: FrameLayout @@ -244,7 +244,7 @@ class SystemEventChipAnimationController @Inject constructor( val height = themedContext.resources.getDimensionPixelSize(R.dimen.status_bar_height) val lp = FrameLayout.LayoutParams(MATCH_PARENT, height) lp.gravity = Gravity.END or Gravity.TOP - statusBarWindowController.addViewToWindow(animationWindowView, lp) + statusBarWindowControllerStore.defaultDisplay.addViewToWindow(animationWindowView, lp) animationWindowView.clipToPadding = false animationWindowView.clipChildren = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt index f0e60dd2ce54..e34f61df8e88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt @@ -23,7 +23,7 @@ import androidx.core.animation.AnimatorListenerAdapter import androidx.core.animation.AnimatorSet import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager -import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.Assert import com.android.systemui.util.time.SystemClock import java.io.PrintWriter @@ -65,7 +65,7 @@ open class SystemStatusAnimationSchedulerImpl constructor( private val coordinator: SystemEventCoordinator, private val chipAnimationController: SystemEventChipAnimationController, - private val statusBarWindowController: StatusBarWindowController, + private val statusBarWindowControllerStore: StatusBarWindowControllerStore, dumpManager: DumpManager, private val systemClock: SystemClock, @Application private val coroutineScope: CoroutineScope, @@ -277,7 +277,7 @@ constructor( private fun runChipAppearAnimation() { Assert.isMainThread() if (hasPersistentDot) { - statusBarWindowController.setForceStatusBarVisible(true) + statusBarWindowControllerStore.defaultDisplay.setForceStatusBarVisible(true) } animationState.value = ANIMATING_IN @@ -311,7 +311,7 @@ constructor( scheduledEvent.value != null -> ANIMATION_QUEUED else -> IDLE } - statusBarWindowController.setForceStatusBarVisible(false) + statusBarWindowControllerStore.defaultDisplay.setForceStatusBarVisible(false) } } ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt index 693ae6617feb..ebaa3c436de4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt @@ -20,7 +20,7 @@ import android.content.Context import android.view.MotionEvent import com.android.systemui.dagger.SysUISingleton import com.android.systemui.settings.DisplayTracker -import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import javax.inject.Inject /** A class to detect when a user swipes away the status bar. */ @@ -31,10 +31,11 @@ constructor( context: Context, displayTracker: DisplayTracker, logger: SwipeUpGestureLogger, - private val statusBarWindowController: StatusBarWindowController, + private val statusBarWindowControllerStore: StatusBarWindowControllerStore, ) : SwipeUpGestureHandler(context, displayTracker, logger, loggerTag = LOGGER_TAG) { override fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean { // Gesture starts just below the status bar + val statusBarWindowController = statusBarWindowControllerStore.defaultDisplay return ev.y >= statusBarWindowController.statusBarHeight && ev.y <= 3 * statusBarWindowController.statusBarHeight } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt index b90aa107d617..71f9c07ba2d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row import android.widget.flags.Flags.notifLinearlayoutOptimized import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.statusbar.notification.row.icon.NotificationRowIconViewInflaterFactory import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing import javax.inject.Inject import javax.inject.Provider @@ -35,6 +36,7 @@ constructor( bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory, optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory, notificationViewFlipperFactory: Provider<NotificationViewFlipperFactory>, + notificationRowIconViewInflaterFactory: NotificationRowIconViewInflaterFactory, ) : NotifRemoteViewsFactoryContainer { override val factories: Set<NotifRemoteViewsFactory> = buildSet { add(precomputedTextViewFactory) @@ -47,5 +49,8 @@ constructor( if (NotificationViewFlipperPausing.isEnabled) { add(notificationViewFlipperFactory.get()) } + if (android.app.Flags.notificationsRedesignAppIcons()) { + add(notificationRowIconViewInflaterFactory) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index e10fd8f3b3fb..41abac1d47f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -342,7 +342,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder * Cancel any pending content view frees from {@link #freeNotificationView} for the provided * content views. * - * @param row top level notification row containing the content views + * @param row top level notification row containing the content views * @param contentViews content views to cancel pending frees on */ private void cancelContentViewFrees( @@ -478,6 +478,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_HEADS_UP)); setRemoteViewsInflaterFactory(result.newPublicView, notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_PUBLIC)); + if (android.app.Flags.notificationsRedesignAppIcons()) { + setRemoteViewsInflaterFactory(result.mNewGroupHeaderView, + notifLayoutInflaterFactoryProvider.provide(row, FLAG_GROUP_SUMMARY_HEADER)); + setRemoteViewsInflaterFactory(result.mNewMinimizedGroupHeaderView, + notifLayoutInflaterFactoryProvider.provide(row, + FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)); + } } private static void setRemoteViewsInflaterFactory(RemoteViews remoteViews, @@ -516,6 +523,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder logger.logAsyncTaskProgress(entry, "contracted view applied"); result.inflatedContentView = v; } + @Override public RemoteViews getRemoteView() { return result.newContentView; @@ -1406,6 +1414,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder @VisibleForTesting abstract static class ApplyCallback { public abstract void setResultView(View v); + public abstract RemoteViews getRemoteView(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java index 84f2f6670839..43ade5cc5a7f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.row; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.notification.row.icon.AppIconProviderModule; import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor; import dagger.Binds; @@ -28,7 +29,7 @@ import javax.inject.Provider; /** * Dagger Module containing notification row and view inflation implementations. */ -@Module +@Module(includes = {AppIconProviderModule.class}) public abstract class NotificationRowModule { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt new file mode 100644 index 000000000000..24b5cf1aa33b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.icon + +import android.app.ActivityManager +import android.app.Flags +import android.content.Context +import android.content.pm.PackageManager.NameNotFoundException +import android.graphics.Color +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.util.Log +import com.android.internal.R +import com.android.launcher3.icons.BaseIconFactory +import com.android.systemui.dagger.SysUISingleton +import dagger.Module +import dagger.Provides +import javax.inject.Inject +import javax.inject.Provider + +/** A provider used to cache and fetch app icons used by notifications. */ +interface AppIconProvider { + @Throws(NameNotFoundException::class) + fun getOrFetchAppIcon(packageName: String, context: Context): Drawable +} + +@SysUISingleton +class AppIconProviderImpl @Inject constructor(private val sysuiContext: Context) : AppIconProvider { + private val iconFactory: BaseIconFactory + get() { + val isLowRam = ActivityManager.isLowRamDeviceStatic() + val res = sysuiContext.resources + val iconSize: Int = + res.getDimensionPixelSize( + if (isLowRam) R.dimen.notification_small_icon_size_low_ram + else R.dimen.notification_small_icon_size + ) + return BaseIconFactory(sysuiContext, res.configuration.densityDpi, iconSize) + } + + override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable { + val icon = context.packageManager.getApplicationIcon(packageName) + return BitmapDrawable( + context.resources, + iconFactory.createScaledBitmap(icon, BaseIconFactory.MODE_HARDWARE), + ) + } +} + +class NoOpIconProvider : AppIconProvider { + companion object { + const val TAG = "NoOpIconProvider" + } + + override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable { + Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.") + return ColorDrawable(Color.WHITE) + } +} + +@Module +class AppIconProviderModule { + @Provides + @SysUISingleton + fun provideImpl(realImpl: Provider<AppIconProviderImpl>): AppIconProvider = + if (Flags.notificationsRedesignAppIcons()) { + realImpl.get() + } else { + NoOpIconProvider() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt new file mode 100644 index 000000000000..2522e58a08a5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.icon + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.View +import com.android.internal.widget.NotificationRowIconView +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.NotifRemoteViewsFactory +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder +import javax.inject.Inject + +/** + * A factory which owns the construction of any NotificationRowIconView inside of Notifications in + * SystemUI. This allows overriding the small icon with the app icon in notifications. + */ +class NotificationRowIconViewInflaterFactory +@Inject +constructor(private val appIconProvider: AppIconProvider) : NotifRemoteViewsFactory { + override fun instantiate( + row: ExpandableNotificationRow, + @NotificationRowContentBinder.InflationFlag layoutType: Int, + parent: View?, + name: String, + context: Context, + attrs: AttributeSet, + ): View? { + return when (name) { + NotificationRowIconView::class.java.name -> + NotificationRowIconView(context, attrs).also { view -> + val sbn = row.entry.sbn + view.setIconProvider( + object : NotificationRowIconView.NotificationIconProvider { + override fun shouldShowAppIcon(): Boolean { + // TODO(b/371174789): implement me + return true + } + + override fun getAppIcon(): Drawable { + return appIconProvider.getOrFetchAppIcon(sbn.packageName, context) + } + } + ) + } + else -> null + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt index 5b37468c9da6..d1338eadb6b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt @@ -58,7 +58,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor -import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.kotlin.getOrNull @@ -93,7 +93,7 @@ constructor( @Main private val mainExecutor: DelayableExecutor, private val shadeControllerLazy: Lazy<ShadeController>, private val communalSceneInteractor: CommunalSceneInteractor, - private val statusBarWindowController: StatusBarWindowController, + private val statusBarWindowControllerStore: StatusBarWindowControllerStore, private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>, private val shadeAnimationInteractor: ShadeAnimationInteractor, private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>, @@ -562,7 +562,7 @@ constructor( } val rootView = animationController.transitionContainer.rootView val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> = - statusBarWindowController.wrapAnimationControllerIfInStatusBar( + statusBarWindowControllerStore.defaultDisplay.wrapAnimationControllerIfInStatusBar( rootView, animationController ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 7e5b45543e9e..3d7cd9c9fbcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -128,7 +128,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.emergency.EmergencyGesture; import com.android.systemui.emergency.EmergencyGestureModule.EmergencyGestureIntentFactory; import com.android.systemui.flags.FeatureFlags; @@ -226,7 +225,7 @@ import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; -import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape; import com.android.systemui.util.DumpUtilsKt; @@ -372,7 +371,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING; private final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarInitializer mStatusBarInitializer; - private final StatusBarWindowController mStatusBarWindowController; + private final StatusBarWindowControllerStore mStatusBarWindowControllerStore; private final StatusBarModeRepositoryStore mStatusBarModeRepository; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @VisibleForTesting @@ -610,7 +609,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { LightBarController lightBarController, AutoHideController autoHideController, StatusBarInitializer statusBarInitializer, - StatusBarWindowController statusBarWindowController, + StatusBarWindowControllerStore statusBarWindowControllerStore, StatusBarWindowStateController statusBarWindowStateController, StatusBarModeRepositoryStore statusBarModeRepository, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -716,7 +715,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mLightBarController = lightBarController; mAutoHideController = autoHideController; mStatusBarInitializer = statusBarInitializer; - mStatusBarWindowController = statusBarWindowController; + mStatusBarWindowControllerStore = statusBarWindowControllerStore; mStatusBarModeRepository = statusBarModeRepository; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mPulseExpansionHandler = pulseExpansionHandler; @@ -1894,7 +1893,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // When the StatusBarSimpleFragment flag is enabled, this logic will be done in // StatusBarOrchestrator if (!StatusBarSimpleFragment.isEnabled()) { - mStatusBarWindowController.attach(); + mStatusBarWindowControllerStore.getDefaultDisplay().attach(); } } @@ -2825,23 +2824,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScrimController.setExpansionAffectsAlpha(!unlocking); if (mAlternateBouncerInteractor.isVisibleState()) { - if (DeviceEntryUdfpsRefactor.isEnabled()) { - if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) - && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED - || mTransitionToFullShadeProgress > 0f)) { - // Assume scrim state for shade is already correct and do nothing - } else { - // Safeguard which prevents the scrim from being stuck in the wrong state - mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); - } + if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) + && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED + || mTransitionToFullShadeProgress > 0f)) { + // Assume scrim state for shade is already correct and do nothing } else { - if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) - && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED - || mTransitionToFullShadeProgress > 0f)) { - mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE); - } else { - mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED); - } + // Safeguard which prevents the scrim from being stuck in the wrong state + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); } // This will cancel the keyguardFadingAway animation if it is running. We need to do // this as otherwise it can remain pending and leave keyguard in a weird state. @@ -3168,12 +3157,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { public void onDozeAmountChanged(float linear, float eased) { if (!lightRevealMigration() && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { - if (DeviceEntryUdfpsRefactor.isEnabled()) { - // If wakeAndUnlocking, this is handled in AuthRippleInteractor - if (!mBiometricUnlockController.isWakeAndUnlock()) { - mLightRevealScrim.setRevealAmount(1f - linear); - } - } else { + // If wakeAndUnlocking, this is handled in AuthRippleInteractor + if (!mBiometricUnlockController.isWakeAndUnlock()) { mLightRevealScrim.setRevealAmount(1f - linear); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt index 460423378dff..1cca3ae0a2c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt @@ -57,7 +57,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.kotlin.getOrNull import dagger.Lazy @@ -85,7 +85,7 @@ constructor( private val context: Context, @DisplayId private val displayId: Int, private val lockScreenUserManager: NotificationLockscreenUserManager, - private val statusBarWindowController: StatusBarWindowController, + private val statusBarWindowControllerStore: StatusBarWindowControllerStore, private val wakefulnessLifecycle: WakefulnessLifecycle, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val deviceProvisionedController: DeviceProvisionedController, @@ -525,7 +525,7 @@ constructor( } val rootView = animationController.transitionContainer.rootView val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> = - statusBarWindowController.wrapAnimationControllerIfInStatusBar( + statusBarWindowControllerStore.defaultDisplay.wrapAnimationControllerIfInStatusBar( rootView, animationController ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index d6716a0c4701..e7d9717defa7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -40,7 +40,7 @@ import com.android.systemui.Flags; import com.android.systemui.Gefingerpoken; import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; -import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder; import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; import com.android.systemui.util.leak.RotationUtils; @@ -49,7 +49,7 @@ import java.util.Objects; public class PhoneStatusBarView extends FrameLayout { private static final String TAG = "PhoneStatusBarView"; - private final StatusBarWindowController mStatusBarWindowController; + private final StatusBarWindowControllerStore mStatusBarWindowControllerStore; private int mRotationOrientation = -1; @Nullable @@ -75,7 +75,7 @@ public class PhoneStatusBarView extends FrameLayout { public PhoneStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); - mStatusBarWindowController = Dependency.get(StatusBarWindowController.class); + mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class); } void setTouchEventHandler(Gefingerpoken handler) { @@ -326,7 +326,7 @@ public class PhoneStatusBarView extends FrameLayout { if (Flags.statusBarStopUpdatingWindowHeight()) { return; } - mStatusBarWindowController.refreshStatusBarHeight(); + mStatusBarWindowControllerStore.getDefaultDisplay().refreshStatusBarHeight(); } interface HasCornerCutoutFetcher { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java index 8f2d4f931b91..7145ffe1f515 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java @@ -28,7 +28,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; -import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import javax.inject.Inject; @@ -38,7 +38,7 @@ import javax.inject.Inject; @SysUISingleton public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, CoreStartable { private final NotificationShadeWindowController mNotificationShadeWindowController; - private final StatusBarWindowController mStatusBarWindowController; + private final StatusBarWindowControllerStore mStatusBarWindowControllerStore; private final ShadeViewController mShadeViewController; private final PanelExpansionInteractor mPanelExpansionInteractor; private final NotificationStackScrollLayoutController mNsslController; @@ -50,7 +50,7 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, @Inject StatusBarHeadsUpChangeListener( NotificationShadeWindowController notificationShadeWindowController, - StatusBarWindowController statusBarWindowController, + StatusBarWindowControllerStore statusBarWindowControllerStore, ShadeViewController shadeViewController, PanelExpansionInteractor panelExpansionInteractor, NotificationStackScrollLayoutController nsslController, @@ -59,7 +59,7 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, StatusBarStateController statusBarStateController, NotificationRemoteInputManager notificationRemoteInputManager) { mNotificationShadeWindowController = notificationShadeWindowController; - mStatusBarWindowController = statusBarWindowController; + mStatusBarWindowControllerStore = statusBarWindowControllerStore; mShadeViewController = shadeViewController; mPanelExpansionInteractor = panelExpansionInteractor; mNsslController = nsslController; @@ -78,7 +78,7 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) { if (inPinnedMode) { mNotificationShadeWindowController.setHeadsUpShowing(true); - mStatusBarWindowController.setForceStatusBarVisible(true); + mStatusBarWindowControllerStore.getDefaultDisplay().setForceStatusBarVisible(true); if (mPanelExpansionInteractor.isFullyCollapsed()) { mShadeViewController.updateTouchableRegion(); } @@ -93,7 +93,9 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, // open artificially. mNotificationShadeWindowController.setHeadsUpShowing(false); if (bypassKeyguard) { - mStatusBarWindowController.setForceStatusBarVisible(false); + mStatusBarWindowControllerStore + .getDefaultDisplay() + .setForceStatusBarVisible(false); } } else { // we need to keep the panel open artificially, let's wait until the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt index 5b0319883b5f..5f864e5dc53a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt @@ -15,12 +15,18 @@ */ package com.android.systemui.statusbar.phone.dagger +import android.view.Display import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Default import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.core.CommandQueueInitializer +import com.android.systemui.statusbar.core.MultiDisplayStatusBarInitializerStore +import com.android.systemui.statusbar.core.SingleDisplayStatusBarInitializerStore +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.core.StatusBarInitializer import com.android.systemui.statusbar.core.StatusBarInitializerImpl +import com.android.systemui.statusbar.core.StatusBarInitializerStore import com.android.systemui.statusbar.core.StatusBarOrchestrator import com.android.systemui.statusbar.core.StatusBarSimpleFragment import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks @@ -47,15 +53,31 @@ interface StatusBarPhoneModule { impl: CentralSurfacesCommandQueueCallbacks ): CommandQueue.Callbacks + @Binds + fun initializerFactory( + implFactory: StatusBarInitializerImpl.Factory + ): StatusBarInitializer.Factory + /** Binds {@link StatusBarInitializer} as a {@link CoreStartable}. */ @Binds @IntoMap @ClassKey(StatusBarInitializerImpl::class) - fun bindStatusBarInitializer(impl: StatusBarInitializerImpl): CoreStartable + fun bindStatusBarInitializer(@Default impl: StatusBarInitializerImpl): CoreStartable - @Binds fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer + @Binds fun statusBarInitializer(@Default impl: StatusBarInitializerImpl): StatusBarInitializer companion object { + // Dagger doesn't support providing AssistedInject types, without a qualifier. Using the + // Default qualifier for this reason. + @Default + @Provides + @SysUISingleton + fun statusBarInitializerImpl( + implFactory: StatusBarInitializerImpl.Factory + ): StatusBarInitializerImpl { + return implFactory.create(displayId = Display.DEFAULT_DISPLAY) + } + @Provides @SysUISingleton @IntoMap @@ -83,5 +105,32 @@ interface StatusBarPhoneModule { CoreStartable.NOP } } + + @Provides + @SysUISingleton + @IntoMap + @ClassKey(StatusBarInitializerStore::class) + fun initializerStoreAsCoreStartable( + multiDisplayStoreLazy: Lazy<MultiDisplayStatusBarInitializerStore> + ): CoreStartable { + return if (StatusBarConnectedDisplays.isEnabled) { + multiDisplayStoreLazy.get() + } else { + CoreStartable.NOP + } + } + + @Provides + @SysUISingleton + fun initializerStore( + singleDisplayStoreLazy: Lazy<SingleDisplayStatusBarInitializerStore>, + multiDisplayStoreLazy: Lazy<MultiDisplayStatusBarInitializerStore>, + ): StatusBarInitializerStore { + return if (StatusBarConnectedDisplays.isEnabled) { + multiDisplayStoreLazy.get() + } else { + singleDisplayStoreLazy.get() + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index f026b99af49c..cf877a741d6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -29,6 +29,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarLocation; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import dagger.Module; import dagger.Provides; @@ -125,9 +126,9 @@ public interface StatusBarFragmentModule { @StatusBarFragmentScope static PhoneStatusBarTransitions providePhoneStatusBarTransitions( @RootView PhoneStatusBarView view, - StatusBarWindowController statusBarWindowController - ) { - return new PhoneStatusBarTransitions(view, statusBarWindowController.getBackgroundView()); + StatusBarWindowControllerStore statusBarWindowControllerStore) { + return new PhoneStatusBarTransitions( + view, statusBarWindowControllerStore.getDefaultDisplay().getBackgroundView()); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index bd6a1c05ddc9..3cf8c3f48409 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -52,7 +52,7 @@ import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.policy.CallbackController -import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.time.SystemClock import java.io.PrintWriter import java.util.concurrent.Executor @@ -75,7 +75,7 @@ constructor( @Main private val mainExecutor: Executor, private val iActivityManager: IActivityManager, private val dumpManager: DumpManager, - private val statusBarWindowController: StatusBarWindowController, + private val statusBarWindowControllerStore: StatusBarWindowControllerStore, private val swipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler, private val statusBarModeRepository: StatusBarModeRepositoryStore, @OngoingCallLog private val logger: LogBuffer, @@ -205,7 +205,9 @@ constructor( this.chipView = chipView val backgroundView: ChipBackgroundContainer? = chipView.findViewById(R.id.ongoing_activity_chip_background) - backgroundView?.maxHeightFetcher = { statusBarWindowController.statusBarHeight } + backgroundView?.maxHeightFetcher = { + statusBarWindowControllerStore.defaultDisplay.statusBarHeight + } if (hasOngoingCall()) { updateChip() } @@ -339,7 +341,8 @@ constructor( // But, this class still needs to do the non-display logic regardless of the flag. uidObserver.registerWithUid(currentCallNotificationInfo.uid) if (!currentCallNotificationInfo.statusBarSwipedAway) { - statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(true) + statusBarWindowControllerStore.defaultDisplay + .setOngoingProcessRequiresStatusBarVisible(true) } updateGestureListening() sendStateChangeEvent() @@ -405,7 +408,9 @@ constructor( if (!Flags.statusBarScreenSharingChips()) { tearDownChipView() } - statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false) + statusBarWindowControllerStore.defaultDisplay.setOngoingProcessRequiresStatusBarVisible( + false + ) swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG) sendStateChangeEvent() uidObserver.unregister() @@ -429,7 +434,9 @@ constructor( private fun onSwipeAwayGestureDetected() { logger.log(TAG, LogLevel.DEBUG, {}, { "Swipe away gesture detected" }) callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true) - statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false) + statusBarWindowControllerStore.defaultDisplay.setOngoingProcessRequiresStatusBarVisible( + false + ) swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt index 5f30b3719aa7..7d0dadcf8c6e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.window +import android.content.Context import android.view.Display import android.view.WindowManager import com.android.app.viewcapture.ViewCaptureAwareWindowManager @@ -105,12 +106,19 @@ constructor( @SysUISingleton class SingleDisplayStatusBarWindowControllerStore @Inject -constructor(private val controller: StatusBarWindowController) : StatusBarWindowControllerStore { +constructor( + context: Context, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, + factory: StatusBarWindowControllerImpl.Factory, +) : StatusBarWindowControllerStore { init { StatusBarConnectedDisplays.assertInLegacyMode() } + private val controller: StatusBarWindowController = + factory.create(context, viewCaptureAwareWindowManager) + override val defaultDisplay = controller override fun forDisplay(displayId: Int) = controller diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index ae635b8cbfd4..df50f765349c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -77,6 +77,7 @@ import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.RingerModeLiveData; @@ -125,6 +126,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private LightBarController mLightBarController; @Mock private NotificationShadeWindowController mNotificationShadeWindowController; @Mock private StatusBarWindowController mStatusBarWindowController; + @Mock private StatusBarWindowControllerStore mStatusBarWindowControllerStore; @Mock private IWindowManager mWindowManager; @Mock private Executor mBackgroundExecutor; @Mock private UiEventLogger mUiEventLogger; @@ -155,7 +157,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { when(mUserContextProvider.getUserContext()).thenReturn(mContext); when(mResources.getConfiguration()).thenReturn( getContext().getResources().getConfiguration()); - + when(mStatusBarWindowControllerStore.getDefaultDisplay()) + .thenReturn(mStatusBarWindowController); mGlobalSettings = new FakeGlobalSettings(); mSecureSettings = new FakeSettings(); mInteractor = mKosmos.getGlobalActionsInteractor(); @@ -184,7 +187,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mStatusBarService, mLightBarController, mNotificationShadeWindowController, - mStatusBarWindowController, + mStatusBarWindowControllerStore, mWindowManager, mBackgroundExecutor, mUiEventLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt index 96a0aadacbc3..ecc62e908a4f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt @@ -298,7 +298,7 @@ class ClockSectionTest : SysuiTestCase() { underTest.applyDefaultConstraints(cs) val referencedIds = cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom) - referencedIds.contentEquals(intArrayOf(R.id.lockscreen_clock_view)) + referencedIds.contentEquals(intArrayOf(customR.id.lockscreen_clock_view)) } @Test @@ -323,7 +323,7 @@ class ClockSectionTest : SysuiTestCase() { } private fun assertLargeClockTop(cs: ConstraintSet, expectedLargeClockTopMargin: Int) { - val largeClockConstraint = cs.getConstraint(R.id.lockscreen_clock_view_large) + val largeClockConstraint = cs.getConstraint(customR.id.lockscreen_clock_view_large) assertThat(largeClockConstraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) assertThat(largeClockConstraint.layout.topMargin).isEqualTo(expectedLargeClockTopMargin) } @@ -332,7 +332,7 @@ class ClockSectionTest : SysuiTestCase() { val smallClockGuidelineConstraint = cs.getConstraint(R.id.small_clock_guideline_top) assertThat(smallClockGuidelineConstraint.layout.topToTop).isEqualTo(-1) - val smallClockConstraint = cs.getConstraint(R.id.lockscreen_clock_view) + val smallClockConstraint = cs.getConstraint(customR.id.lockscreen_clock_view) assertThat(smallClockConstraint.layout.topToBottom) .isEqualTo(R.id.small_clock_guideline_top) assertThat(smallClockConstraint.layout.topMargin).isEqualTo(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt index bfb8a57e6271..cea51a89a378 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt @@ -18,13 +18,11 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.graphics.Point -import android.platform.test.annotations.DisableFlags import android.view.WindowManager import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.keyguard.LegacyLockIconViewController import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController @@ -60,7 +58,6 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager @Mock private lateinit var notificationPanelView: NotificationPanelView private lateinit var featureFlags: FakeFeatureFlags - @Mock private lateinit var lockIconViewController: LegacyLockIconViewController @Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var deviceEntryIconViewModel: DeviceEntryIconViewModel private lateinit var underTest: DefaultDeviceEntrySection @@ -81,7 +78,6 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { context, notificationPanelView, featureFlags, - { lockIconViewController }, { deviceEntryIconViewModel }, { mock(DeviceEntryForegroundViewModel::class.java) }, { mock(DeviceEntryBackgroundViewModel::class.java) }, @@ -102,37 +98,13 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { @Test fun addViewsConditionally_migrateAndRefactorFlagsOn() { mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val constraintLayout = ConstraintLayout(context, null) underTest.addViews(constraintLayout) assertThat(constraintLayout.childCount).isGreaterThan(0) } @Test - @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun addViewsConditionally_migrateFlagOff() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - val constraintLayout = ConstraintLayout(context, null) - underTest.addViews(constraintLayout) - assertThat(constraintLayout.childCount).isEqualTo(0) - } - - @Test - fun applyConstraints_udfps_refactor_off() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - val cs = ConstraintSet() - underTest.applyConstraints(cs) - - val constraint = cs.getConstraint(R.id.lock_icon_view) - - assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) - assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) - } - - @Test - fun applyConstraints_udfps_refactor_on() { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + fun applyConstraints() { whenever(deviceEntryIconViewModel.isUdfpsSupported).thenReturn(MutableStateFlow(false)) val cs = ConstraintSet() underTest.applyConstraints(cs) @@ -144,24 +116,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { } @Test - fun testCenterIcon_udfps_refactor_off() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - val cs = ConstraintSet() - underTest.centerIcon(Point(5, 6), 1F, cs) - - val constraint = cs.getConstraint(R.id.lock_icon_view) - - assertThat(constraint.layout.mWidth).isEqualTo(2) - assertThat(constraint.layout.mHeight).isEqualTo(2) - assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) - assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) - assertThat(constraint.layout.topMargin).isEqualTo(5) - assertThat(constraint.layout.startMargin).isEqualTo(4) - } - - @Test - fun testCenterIcon_udfps_refactor_on() { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + fun testCenterIcon() { val cs = ConstraintSet() underTest.centerIcon(Point(5, 6), 1F, cs) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt new file mode 100644 index 000000000000..0d1d37af7e5b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.core + +import android.platform.test.annotations.EnableFlags +import android.view.Display +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.display.data.repository.displayRepository +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.unconfinedTestDispatcher +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) +class MultiDisplayStatusBarInitializerStoreTest : SysuiTestCase() { + + private val kosmos = + testKosmos().also { + // Using unconfinedTestDispatcher to avoid having to call `runCurrent` in the tests. + it.testDispatcher = it.unconfinedTestDispatcher + } + private val testScope = kosmos.testScope + private val fakeDisplayRepository = kosmos.displayRepository + private val store = kosmos.multiDisplayStatusBarInitializerStore + + @Before + fun start() { + store.start() + } + + @Before + fun addDisplays() = runBlocking { + fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY_ID) + fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID) + } + + @Test + fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() = + testScope.runTest { + val controller = store.defaultDisplay + + assertThat(store.defaultDisplay).isSameInstanceAs(controller) + } + + @Test + fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() = + testScope.runTest { + val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID) + + assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(controller) + } + + @Test + fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() = + testScope.runTest { + val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID) + + fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID) + fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID) + + assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(controller) + } + + @Test(expected = IllegalArgumentException::class) + fun forDisplay_nonExistingDisplayId_throws() = + testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) } + + companion object { + private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY + private const val NON_DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1 + private const val NON_EXISTING_DISPLAY_ID = Display.DEFAULT_DISPLAY + 2 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index 376873d19624..5d8a8fd03bc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.statusbar.BatteryStatusChip import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock @@ -63,6 +64,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { @Mock private lateinit var systemEventCoordinator: SystemEventCoordinator @Mock private lateinit var statusBarWindowController: StatusBarWindowController + @Mock private lateinit var statusBarWindowControllerStore: StatusBarWindowControllerStore @Mock private lateinit var statusBarContentInsetProvider: StatusBarContentInsetsProvider @@ -82,12 +84,14 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) + whenever(statusBarWindowControllerStore.defaultDisplay) + .thenReturn(statusBarWindowController) systemClock = FakeSystemClock() chipAnimationController = SystemEventChipAnimationController( mContext, - statusBarWindowController, - statusBarContentInsetProvider + statusBarWindowControllerStore, + statusBarContentInsetProvider, ) // StatusBarContentInsetProvider is mocked. Ensure that it returns some mocked values. @@ -660,7 +664,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { SystemStatusAnimationSchedulerImpl( systemEventCoordinator, chipAnimationController, - statusBarWindowController, + statusBarWindowControllerStore, dumpManager, systemClock, CoroutineScope(StandardTestDispatcher(testScope.testScheduler)), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 15ea811287b8..44d81a7abfe5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -21,7 +21,6 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED; import static android.provider.Settings.Global.HEADS_UP_ON; -import static com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR; import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE; import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION; import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT; @@ -170,6 +169,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.core.StatusBarInitializerImpl; import com.android.systemui.statusbar.core.StatusBarOrchestrator; +import com.android.systemui.statusbar.core.StatusBarSimpleFragment; import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -194,6 +194,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.util.FakeEventLog; import com.android.systemui.util.WallpaperController; @@ -292,6 +293,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private KeyguardBypassController mKeyguardBypassController; @Mock private AutoHideController mAutoHideController; @Mock private StatusBarWindowController mStatusBarWindowController; + @Mock private StatusBarWindowControllerStore mStatusBarWindowControllerStore; @Mock private Provider<CollapsedStatusBarFragment> mCollapsedStatusBarFragmentProvider; @Mock private StatusBarWindowStateController mStatusBarWindowStateController; @Mock private Bubbles mBubbles; @@ -383,6 +385,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mBubbles.canShowBubbleNotification()).thenReturn(true); + when(mStatusBarWindowControllerStore.getDefaultDisplay()) + .thenReturn(mStatusBarWindowController); + mVisualInterruptionDecisionProvider = VisualInterruptionDecisionProviderTestUtil.INSTANCE.createProviderByFlag( mAmbientDisplayConfiguration, @@ -465,7 +470,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager, - mStatusBarWindowController, + mStatusBarWindowControllerStore, mDeviceProvisionedController, mNotificationShadeWindowController, 0, @@ -507,10 +512,11 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mLightBarController, mAutoHideController, new StatusBarInitializerImpl( - mStatusBarWindowController, + mContext.getDisplayId(), + mStatusBarWindowControllerStore, mCollapsedStatusBarFragmentProvider, emptySet()), - mStatusBarWindowController, + mStatusBarWindowControllerStore, mStatusBarWindowStateController, new FakeStatusBarModeRepository(), mKeyguardUpdateMonitor, @@ -852,34 +858,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void testSetDozingNotUnlocking_transitionToAuthScrimmed_cancelKeyguardFadingAway() { - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true); - - mCentralSurfaces.updateScrimController(); - - verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE)); - verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway(); - } - - @Test - @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void testOccludingQSNotExpanded_flagOff_transitionToAuthScrimmed() { - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // GIVEN device occluded and panel is NOT expanded - mCentralSurfaces.setBarStateForTest(SHADE); // occluding on LS has StatusBarState = SHADE - when(mKeyguardStateController.isOccluded()).thenReturn(true); - when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false); - - mCentralSurfaces.updateScrimController(); - - verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED)); - } - - @Test - @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void testNotOccluding_QSNotExpanded_flagOn_doesNotTransitionScrimState() { when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); @@ -895,7 +873,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void testNotOccluding_QSExpanded_flagOn_doesTransitionScrimStateToKeyguard() { when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); @@ -911,21 +888,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void testOccludingQSExpanded_transitionToAuthScrimmedShade() { - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // GIVEN device occluded and qs IS expanded - mCentralSurfaces.setBarStateForTest(SHADE); // occluding on LS has StatusBarState = SHADE - when(mKeyguardStateController.isOccluded()).thenReturn(true); - when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true); - - mCentralSurfaces.updateScrimController(); - - verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE)); - } - - @Test public void testEnteringGlanceableHub_updatesScrim() { // Transition to the glanceable hub. mKosmos.getCommunalRepository() @@ -1149,6 +1111,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(StatusBarSimpleFragment.FLAG_NAME) public void bubbleBarVisibility() { createCentralSurfaces(); mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt index 68df7488ee10..ee79ca0df9d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.Gefingerpoken import com.android.systemui.SysuiTestCase import com.android.systemui.res.R import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -57,10 +58,15 @@ class PhoneStatusBarViewTest : SysuiTestCase() { get() = view.requireViewById(R.id.system_icons) private val windowController = mock<StatusBarWindowController>() + private val windowControllerStore = mock<StatusBarWindowControllerStore>() @Before fun setUp() { - mDependency.injectTestDependency(StatusBarWindowController::class.java, windowController) + whenever(windowControllerStore.defaultDisplay).thenReturn(windowController) + mDependency.injectTestDependency( + StatusBarWindowControllerStore::class.java, + windowControllerStore, + ) context.ensureTestableResources() view = spy(createStatusBarView()) whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 9cfb0bb3900b..1ceb20adeebd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -46,6 +46,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; @@ -197,6 +198,8 @@ import com.android.wm.shell.taskview.TaskView; import com.android.wm.shell.taskview.TaskViewTransitions; import com.android.wm.shell.transition.Transitions; +import kotlin.Lazy; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -215,7 +218,6 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.Executor; -import kotlin.Lazy; import platform.test.runner.parameterized.ParameterizedAndroidJunit4; import platform.test.runner.parameterized.Parameters; @@ -2470,6 +2472,52 @@ public class BubblesTest extends SysuiTestCase { verify(stackView, never()).showOverflow(anyBoolean()); } + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void testEventLogging_bubbleBar_addBubble() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + mEntryListener.onEntryAdded(mRow); + + verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()), + eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED)); + } + + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void testEventLogging_bubbleBar_updateBubble() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + mEntryListener.onEntryAdded(mRow); + // Mark the notification as updated + NotificationEntryHelper.modifyRanking(mRow).setTextChanged(true).build(); + mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true); + + verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()), + eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_UPDATED)); + } + + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void testEventLogging_bubbleBar_dragBubbleToDismiss() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + mEntryListener.onEntryAdded(mRow); + mBubbleController.dragBubbleToDismiss(mRow.getKey(), 1L); + + verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()), + eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_BUBBLE)); + } + /** Creates a bubble using the userId and package. */ private Bubble createBubble(int userId, String pkg) { final UserHandle userHandle = new UserHandle(userId); @@ -2655,6 +2703,10 @@ public class BubblesTest extends SysuiTestCase { assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded); } + private Bubble eqBubbleWithKey(String key) { + return argThat(b -> b.getKey().equals(key)); + } + private static class FakeBubbleStateListener implements Bubbles.BubbleStateListener { int mStateChangeCalls = 0; diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/wmshell/OWNERS new file mode 100644 index 000000000000..eae8629231f6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/OWNERS @@ -0,0 +1,5 @@ +# Bubbles team +madym@google.com +atsjenk@google.com +liranb@google.com +mpodolian@google.com
\ No newline at end of file diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt index fcc83b3e579f..78ea70086605 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt @@ -53,6 +53,10 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository { private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0) private val displayRemovalEventFlow = MutableSharedFlow<Int>(replay = 0) + suspend fun addDisplay(displayId: Int, type: Int = Display.TYPE_EXTERNAL) { + addDisplay(display(type, id = displayId)) + } + suspend fun addDisplay(display: Display) { flow.value += display displayAdditionEventFlow.emit(display) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt index ddcc6d60f993..b9f0c9a70d3d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt @@ -37,7 +37,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.deviceProvisionedController -import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.mockito.mock import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -67,7 +67,7 @@ val Kosmos.shadeControllerImpl by mock<KeyguardStateController>(), statusBarStateController, statusBarKeyguardViewManager, - mock<StatusBarWindowController>(), + mock<StatusBarWindowControllerStore>(), deviceProvisionedController, mock<NotificationShadeWindowController>(), 0, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt new file mode 100644 index 000000000000..73ed228f5aaa --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.core + +import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions +import com.android.systemui.statusbar.phone.PhoneStatusBarViewController + +class FakeStatusBarInitializerFactory( + private val statusBarViewController: PhoneStatusBarViewController, + private val statusBarTransitions: PhoneStatusBarTransitions, +) : StatusBarInitializer.Factory { + + override fun create(displayId: Int): StatusBarInitializer = + FakeStatusBarInitializer(statusBarViewController, statusBarTransitions) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt index d10320004454..7ad715ba5a89 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt @@ -16,7 +16,9 @@ package com.android.systemui.statusbar.core +import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.statusbar.phone.phoneStatusBarTransitions import com.android.systemui.statusbar.phone.phoneStatusBarViewController @@ -26,3 +28,20 @@ val Kosmos.fakeStatusBarInitializer by } var Kosmos.statusBarInitializer by Kosmos.Fixture { fakeStatusBarInitializer } + +val Kosmos.fakeStatusBarInitializerFactory by + Kosmos.Fixture { + FakeStatusBarInitializerFactory(phoneStatusBarViewController, phoneStatusBarTransitions) + } + +var Kosmos.statusBarInitializerFactory: StatusBarInitializer.Factory by + Kosmos.Fixture { fakeStatusBarInitializerFactory } + +val Kosmos.multiDisplayStatusBarInitializerStore by + Kosmos.Fixture { + MultiDisplayStatusBarInitializerStore( + applicationCoroutineScope, + fakeStatusBarInitializerFactory, + displayRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt index c53e44d514f7..54de293b8911 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt @@ -28,7 +28,7 @@ import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepositor import com.android.systemui.statusbar.mockNotificationRemoteInputManager import com.android.systemui.statusbar.phone.mockAutoHideController import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore -import com.android.systemui.statusbar.window.fakeStatusBarWindowController +import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore import com.android.wm.shell.bubbles.bubblesOptional val Kosmos.statusBarOrchestrator by @@ -36,8 +36,8 @@ val Kosmos.statusBarOrchestrator by StatusBarOrchestrator( applicationCoroutineScope, fakeStatusBarInitializer, - fakeStatusBarWindowController, fakeStatusBarModeRepository, + fakeStatusBarWindowControllerStore, mockDemoModeController, mockPluginDependencyProvider, mockAutoHideController, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt index fc4f05df26ed..a7a61957d104 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt @@ -69,6 +69,8 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag +import com.android.systemui.statusbar.notification.row.icon.AppIconProviderImpl +import com.android.systemui.statusbar.notification.row.icon.NotificationRowIconViewInflaterFactory import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger import com.android.systemui.statusbar.phone.KeyguardBypassController @@ -99,7 +101,7 @@ import org.mockito.Mockito class ExpandableNotificationRowBuilder( private val context: Context, dependency: TestableDependency, - private val featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic() + private val featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic(), ) { private val mMockLogger: ExpandableNotificationRowLogger @@ -161,21 +163,21 @@ class ExpandableNotificationRowBuilder( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP, "true", - true + true, ) setProperty( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.SSIN_ENABLED, "true", - true + true, ) setProperty( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, "false", - true + true, ) - } + }, ) val remoteViewsFactories = getNotifRemoteViewsFactoryContainer(featureFlags) val remoteInputManager = Mockito.mock(NotificationRemoteInputManager::class.java, STUB_ONLY) @@ -192,21 +194,21 @@ class ExpandableNotificationRowBuilder( Mockito.mock(KeyguardDismissUtil::class.java, STUB_ONLY), remoteInputManager = remoteInputManager, smartReplyController = mSmartReplyController, - context = context + context = context, ), smartActionsInflater = SmartActionInflaterImpl( constants = mSmartReplyConstants, activityStarter = Mockito.mock(ActivityStarter::class.java, STUB_ONLY), smartReplyController = mSmartReplyController, - headsUpManager = mHeadsUpManager - ) + headsUpManager = mHeadsUpManager, + ), ) val notifLayoutInflaterFactoryProvider = object : NotifLayoutInflaterFactory.Provider { override fun provide( row: ExpandableNotificationRow, - layoutType: Int + layoutType: Int, ): NotifLayoutInflaterFactory = NotifLayoutInflaterFactory(row, layoutType, remoteViewsFactories) } @@ -270,14 +272,14 @@ class ExpandableNotificationRowBuilder( whenever( mOnUserInteractionCallback.registerFutureDismissal( ArgumentMatchers.any(), - ArgumentMatchers.anyInt() + ArgumentMatchers.anyInt(), ) ) .thenReturn(mFutureDismissalRunnable) } private fun getNotifRemoteViewsFactoryContainer( - featureFlags: FeatureFlags, + featureFlags: FeatureFlags ): NotifRemoteViewsFactoryContainer { return NotifRemoteViewsFactoryContainerImpl( featureFlags, @@ -285,6 +287,7 @@ class ExpandableNotificationRowBuilder( BigPictureLayoutInflaterFactory(), NotificationOptimizedLinearLayoutFactory(), { Mockito.mock(NotificationViewFlipperFactory::class.java) }, + NotificationRowIconViewInflaterFactory(AppIconProviderImpl(context)), ) } @@ -293,7 +296,7 @@ class ExpandableNotificationRowBuilder( NotificationChannel( notification.channelId, notification.channelId, - NotificationManager.IMPORTANCE_DEFAULT + NotificationManager.IMPORTANCE_DEFAULT, ) channel.isBlockable = true val entry = @@ -321,7 +324,7 @@ class ExpandableNotificationRowBuilder( private fun generateRow( entry: NotificationEntry, - @InflationFlag extraInflationFlags: Int + @InflationFlag extraInflationFlags: Int, ): ExpandableNotificationRow { // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be // set, but we do not want to override an existing value that is needed by a specific test. @@ -329,7 +332,7 @@ class ExpandableNotificationRowBuilder( val rowInflaterTask = RowInflaterTask( mFakeSystemClock, - Mockito.mock(RowInflaterTaskLogger::class.java, STUB_ONLY) + Mockito.mock(RowInflaterTaskLogger::class.java, STUB_ONLY), ) val row = rowInflaterTask.inflateSynchronously(context, null, entry) @@ -364,7 +367,7 @@ class ExpandableNotificationRowBuilder( mSmartReplyController, featureFlags, Mockito.mock(IStatusBarService::class.java, STUB_ONLY), - Mockito.mock(UiEventLogger::class.java, STUB_ONLY) + Mockito.mock(UiEventLogger::class.java, STUB_ONLY), ) row.setAboveShelfChangedListener { aboveShelf: Boolean -> } mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt new file mode 100644 index 000000000000..08c6bbab6dd6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.icon + +import android.content.applicationContext +import com.android.systemui.kosmos.Kosmos + +val Kosmos.appIconProvider by Kosmos.Fixture { AppIconProviderImpl(applicationContext) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt index d19e3227027c..35f95b6fab8f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt @@ -22,10 +22,10 @@ class FakeStatusBarWindowControllerStore : StatusBarWindowControllerStore { private val perDisplayControllers = mutableMapOf<Int, FakeStatusBarWindowController>() - override val defaultDisplay + override val defaultDisplay: FakeStatusBarWindowController get() = forDisplay(Display.DEFAULT_DISPLAY) - override fun forDisplay(displayId: Int): StatusBarWindowController { + override fun forDisplay(displayId: Int): FakeStatusBarWindowController { return perDisplayControllers.computeIfAbsent(displayId) { FakeStatusBarWindowController() } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt index 6c6f243f3953..78caf93d4618 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt @@ -20,7 +20,8 @@ import com.android.systemui.kosmos.Kosmos val Kosmos.fakeStatusBarWindowController by Kosmos.Fixture { FakeStatusBarWindowController() } -var Kosmos.statusBarWindowController by Kosmos.Fixture { fakeStatusBarWindowController } +var Kosmos.statusBarWindowController: StatusBarWindowController by + Kosmos.Fixture { fakeStatusBarWindowController } val Kosmos.fakeStatusBarWindowControllerStore by Kosmos.Fixture { FakeStatusBarWindowControllerStore() } diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 8896d772ea4d..bfa801f30955 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -8,7 +8,7 @@ package { // OWNER: g/ravenwood // Bug component: 25698 - default_team: "trendy_team_framework_backstage_power", + default_team: "trendy_team_ravenwood", } filegroup { diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java index 908e5903122e..5894476b9201 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -335,7 +335,11 @@ public class RavenwoodRuntimeEnvironmentController { } android.os.Process.reset$ravenwood(); - ResourcesManager.setInstance(null); // Better structure needed. + try { + ResourcesManager.setInstance(null); // Better structure needed. + } catch (Exception e) { + // AOSP-CHANGE: AOSP doesn't support resources yet. + } if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) { maybeThrowPendingUncaughtException(true); diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java index 57c643bb08a1..a7aab49cde56 100644 --- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java @@ -160,11 +160,13 @@ public class ContentSuggestionsManagerService extends HardwareBuffer snapshotBuffer = null; int colorSpaceId = 0; + TaskSnapshot snapshot = null; // Skip taking TaskSnapshot when bitmap is provided. if (!imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) { // Can block, so call before acquiring the lock. - TaskSnapshot snapshot = - mActivityTaskManagerInternal.getTaskSnapshotBlocking(taskId, false); + snapshot = mActivityTaskManagerInternal.getTaskSnapshotBlocking( + taskId, false /* isLowResolution */, + TaskSnapshot.REFERENCE_CONTENT_SUGGESTION); if (snapshot != null) { snapshotBuffer = snapshot.getHardwareBuffer(); ColorSpace colorSpace = snapshot.getColorSpace(); @@ -185,6 +187,9 @@ public class ContentSuggestionsManagerService extends } } } + if (snapshot != null) { + snapshot.removeReference(TaskSnapshot.REFERENCE_CONTENT_SUGGESTION); + } } @Override diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 05d07ae761c1..485bf31dfb64 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -1523,7 +1523,7 @@ public class BinaryTransparencyService extends SystemService { @Override public void onApexStaged(ApexStagedEvent event) throws RemoteException { Slog.d(TAG, "A new APEX has been staged for update. There are currently " - + event.stagedApexModuleNames.length + " APEX(s) staged for update. " + + event.stagedApexInfos.length + " APEX(s) staged for update. " + "Scheduling measurement..."); UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext, BinaryTransparencyService.this); diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index f8857d3e152a..d13dd2f2e1fc 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -443,6 +443,11 @@ final class UiModeManagerService extends SystemService { mDreamsDisabledByAmbientModeSuppression = disabledByAmbientModeSuppression; } + @VisibleForTesting + void setCurrentUser(int currentUserId) { + mCurrentUser = currentUserId; + } + @Override public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { mCurrentUser = to.getUserIdentifier(); @@ -864,9 +869,9 @@ final class UiModeManagerService extends SystemService { throw new IllegalArgumentException("Unknown mode: " + mode); } - final int user = UserHandle.getCallingUserId(); - enforceCurrentUserIfVisibleBackgroundEnabled(user); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); + final int user = UserHandle.getCallingUserId(); final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -929,7 +934,7 @@ final class UiModeManagerService extends SystemService { @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) { setAttentionModeThemeOverlay_enforcePermission(); - enforceCurrentUserIfVisibleBackgroundEnabled(UserHandle.getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); synchronized (mLock) { if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) { @@ -1020,16 +1025,16 @@ final class UiModeManagerService extends SystemService { return false; } final int user = Binder.getCallingUserHandle().getIdentifier(); - enforceCurrentUserIfVisibleBackgroundEnabled(user); - if (user != mCurrentUser && getContext().checkCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) { Slog.e(TAG, "Target user is not current user," + " INTERACT_ACROSS_USERS permission is required"); return false; - } + + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); + // Store the last requested bedtime night mode state so that we don't need to notify // anyone if the user decides to switch to the night mode to bedtime. if (modeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) { @@ -1078,9 +1083,10 @@ final class UiModeManagerService extends SystemService { Slog.e(TAG, "Set custom time start, requires MODIFY_DAY_NIGHT_MODE permission"); return; } - final int user = UserHandle.getCallingUserId(); - enforceCurrentUserIfVisibleBackgroundEnabled(user); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); + + final int user = UserHandle.getCallingUserId(); final long ident = Binder.clearCallingIdentity(); try { LocalTime newTime = LocalTime.ofNanoOfDay(time * 1000); @@ -1108,9 +1114,10 @@ final class UiModeManagerService extends SystemService { Slog.e(TAG, "Set custom time end, requires MODIFY_DAY_NIGHT_MODE permission"); return; } - final int user = UserHandle.getCallingUserId(); - enforceCurrentUserIfVisibleBackgroundEnabled(user); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); + + final int user = UserHandle.getCallingUserId(); final long ident = Binder.clearCallingIdentity(); try { LocalTime newTime = LocalTime.ofNanoOfDay(time * 1000); @@ -1131,7 +1138,7 @@ final class UiModeManagerService extends SystemService { assertLegit(callingPackage); assertSingleProjectionType(projectionType); enforceProjectionTypePermissions(projectionType); - enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); synchronized (mLock) { if (mProjectionHolders == null) { @@ -1177,7 +1184,7 @@ final class UiModeManagerService extends SystemService { assertLegit(callingPackage); assertSingleProjectionType(projectionType); enforceProjectionTypePermissions(projectionType); - enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); return releaseProjectionUnchecked(projectionType, callingPackage); } @@ -1219,7 +1226,7 @@ final class UiModeManagerService extends SystemService { return; } - enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); synchronized (mLock) { if (mProjectionListeners == null) { diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 12e8c57228d6..947f6b73d32a 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -48,7 +48,6 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; -import android.net.vcn.Flags; import android.net.vcn.IVcnManagementService; import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; @@ -447,22 +446,16 @@ public class VcnManagementService extends IVcnManagementService.Stub { } final UserHandle userHandle = UserHandle.getUserHandleForUid(uid); + final UserManager userManager = mContext.getSystemService(UserManager.class); - if (Flags.enforceMainUser()) { - final UserManager userManager = mContext.getSystemService(UserManager.class); - - Binder.withCleanCallingIdentity( - () -> { - if (!Objects.equals(userManager.getMainUser(), userHandle)) { - throw new SecurityException( - "VcnManagementService can only be used by callers running as" - + " the main user"); - } - }); - } else if (!userHandle.isSystem()) { - throw new SecurityException( - "VcnManagementService can only be used by callers running as the primary user"); - } + Binder.withCleanCallingIdentity( + () -> { + if (!Objects.equals(userManager.getMainUser(), userHandle)) { + throw new SecurityException( + "VcnManagementService can only be used by callers running as" + + " the main user"); + } + }); } private void enforceCallingUserAndCarrierPrivilege( diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index f5a297bfd4f5..65a2c187f1c8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -246,7 +246,7 @@ final class ActivityManagerConstants extends ContentObserver { static final int DEFAULT_MAX_SERVICE_CONNECTIONS_PER_PROCESS = 3000; - private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false; + private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = Flags.oomadjusterCachedAppTiers(); private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000; /** diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index adf0e640f6bf..d67fea09f945 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -225,3 +225,11 @@ flag { description: "Migrate OomAdjuster pulled device state to a push model" bug: "302575389" } + +flag { + name: "oomadjuster_cached_app_tiers" + namespace: "system_performance" + is_fixed_read_only: true + description: "Assign cached oom_score_adj in tiers." + bug: "369893532" +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 020cef170e0c..37a2fba8fcb5 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -69,6 +69,7 @@ import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume; import static com.android.media.audio.Flags.equalScoLeaVcIndexRange; import static com.android.media.audio.Flags.replaceStreamBtSco; import static com.android.media.audio.Flags.ringerModeAffectsAlarm; +import static com.android.media.audio.Flags.ringMyCar; import static com.android.media.audio.Flags.setStreamVolumeOrder; import static com.android.media.audio.Flags.vgsVssSyncMuteOrder; import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE; @@ -761,7 +762,7 @@ public class AudioService extends IAudioService.Stub /** Streams that can be muted by system. Do not resolve to aliases when checking. * @see System#MUTE_STREAMS_AFFECTED */ - private int mMuteAffectedStreams; + protected int mMuteAffectedStreams; /** Streams that can be muted by user. Do not resolve to aliases when checking. * @see System#MUTE_STREAMS_AFFECTED */ @@ -1465,7 +1466,8 @@ public class AudioService extends IAudioService.Stub mPlaybackMonitor = new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM], - device -> onMuteAwaitConnectionTimeout(device)); + device -> onMuteAwaitConnectionTimeout(device), + stream -> isStreamMute(stream)); mPlaybackMonitor.registerPlaybackCallback(mPlaybackActivityMonitor, true); mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor); @@ -4846,6 +4848,8 @@ public class AudioService extends IAudioService.Stub + replaceStreamBtSco()); pw.println("\tcom.android.media.audio.equalScoLeaVcIndexRange:" + equalScoLeaVcIndexRange()); + pw.println("\tcom.android.media.audio.ringMyCar:" + + ringMyCar()); } private void dumpAudioMode(PrintWriter pw) { @@ -8695,9 +8699,14 @@ public class AudioService extends IAudioService.Stub // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted. // This allows RX path muting by the audio HAL only when explicitly muted but not when // index is just set to 0 to repect BT requirements + boolean muted = false; if (mHasValidStreamType && isVssMuteBijective(mPublicStreamType) && getVssForStreamOrDefault(mPublicStreamType).isFullyMuted()) { - index = 0; + if (ringMyCar()) { + muted = true; + } else { + index = 0; + } } else if (isStreamBluetoothSco(mPublicStreamType) && index == 0) { index = 1; } @@ -8707,13 +8716,14 @@ public class AudioService extends IAudioService.Stub / getVssForStreamOrDefault(mPublicStreamType).getIndexStepFactor()); } + if (DEBUG_VOL) { Log.d(TAG, "setVolumeIndexInt(" + mAudioVolumeGroup.getId() + ", " + index + ", " - + device + ")"); + + muted + ", " + device + ")"); } // Set the volume index - mAudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device); + mAudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, muted, device); } @GuardedBy("AudioService.VolumeStreamState.class") @@ -9297,6 +9307,13 @@ public class AudioService extends IAudioService.Stub } } + /** + * Sends the new volume index on the given device to native. + * + * <p>Make sure the index is consistent with the muting state. When ringMyCar is enabled + * will send the non-zero index together with muted state. Otherwise, index 0 will be sent + * to native for signalising a muted stream. + **/ @GuardedBy("VolumeStreamState.class") private void setStreamVolumeIndex(int index, int device) { // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted. @@ -9311,18 +9328,19 @@ public class AudioService extends IAudioService.Stub / 10; } + boolean muted = ringMyCar() ? isFullyMuted() : false; if (DEBUG_VOL) { - Log.d(TAG, "setStreamVolumeIndexAS(" + mStreamType + ", " + index + ", " + device - + ")"); + Log.d(TAG, "setStreamVolumeIndexAS(streamType=" + mStreamType + ", index=" + index + + ", muted=" + muted + ", device=" + device + ")"); } - mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, device); + mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, muted, device); } // must be called while synchronized VolumeStreamState.class @GuardedBy("VolumeStreamState.class") /*package*/ void applyDeviceVolume_syncVSS(int device) { int index; - if (isFullyMuted()) { + if (isFullyMuted() && !ringMyCar()) { index = 0; } else if (isAbsoluteVolumeDevice(device) || isA2dpAbsoluteVolumeDevice(device) @@ -9356,7 +9374,7 @@ public class AudioService extends IAudioService.Stub for (int i = 0; i < mIndexMap.size(); i++) { final int device = mIndexMap.keyAt(i); if (device != AudioSystem.DEVICE_OUT_DEFAULT) { - if (isFullyMuted()) { + if (isFullyMuted() && !ringMyCar()) { index = 0; } else if (isAbsoluteVolumeDevice(device) || isA2dpAbsoluteVolumeDevice(device) @@ -9391,7 +9409,7 @@ public class AudioService extends IAudioService.Stub } // apply default volume last: by convention , default device volume will be used // by audio policy manager if no explicit volume is present for a given device type - if (isFullyMuted()) { + if (isFullyMuted() && !ringMyCar()) { index = 0; } else { index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10; diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 5cabddea9c17..e86c34cab88a 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -547,13 +547,14 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, * @param device * @return */ - public int setStreamVolumeIndexAS(int stream, int index, int device) { - return AudioSystem.setStreamVolumeIndexAS(stream, index, device); + public int setStreamVolumeIndexAS(int stream, int index, boolean muted, int device) { + return AudioSystem.setStreamVolumeIndexAS(stream, index, muted, device); } /** Same as {@link AudioSystem#setVolumeIndexForAttributes(AudioAttributes, int, int)} */ - public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, int device) { - return AudioSystem.setVolumeIndexForAttributes(attributes, index, device); + public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, boolean muted, + int device) { + return AudioSystem.setVolumeIndexForAttributes(attributes, index, muted, device); } /** diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index a734e73d213b..b63b07f0453e 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -72,6 +72,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.Consumer; +import java.util.function.Function; /** * Class to receive and dispatch updates from AudioSystem about recording configurations. @@ -160,18 +161,22 @@ public final class PlaybackActivityMonitor private final Context mContext; private int mSavedAlarmVolume = -1; + private boolean mSavedAlarmMuted = false; + private final Function<Integer, Boolean> mIsStreamMutedCb; private final int mMaxAlarmVolume; private int mPrivilegedAlarmActiveCount = 0; private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb; private final FadeOutManager mFadeOutManager = new FadeOutManager(); PlaybackActivityMonitor(Context context, int maxAlarmVolume, - Consumer<AudioDeviceAttributes> muteTimeoutCallback) { + Consumer<AudioDeviceAttributes> muteTimeoutCallback, + Function<Integer, Boolean> isStreamMutedCb) { mContext = context; mMaxAlarmVolume = maxAlarmVolume; PlayMonitorClient.sListenerDeathMonitor = this; AudioPlaybackConfiguration.sPlayerDeathMonitor = this; mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback; + mIsStreamMutedCb = isStreamMutedCb; initEventHandler(); } @@ -332,8 +337,9 @@ public final class PlaybackActivityMonitor if (mPrivilegedAlarmActiveCount++ == 0) { mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex( AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER); + mSavedAlarmMuted = mIsStreamMutedCb.apply(AudioSystem.STREAM_ALARM); AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM, - mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER); + mMaxAlarmVolume, /*muted=*/false, AudioSystem.DEVICE_OUT_SPEAKER); } } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED && apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { @@ -342,7 +348,8 @@ public final class PlaybackActivityMonitor AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) == mMaxAlarmVolume) { AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM, - mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER); + mSavedAlarmVolume, mSavedAlarmMuted, + AudioSystem.DEVICE_OUT_SPEAKER); } } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index acf4db30ba93..0807c70d9922 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -292,6 +292,13 @@ final class DisplayDeviceInfo { */ public float renderFrameRate; + + /** + * If {@code true}, this Display supports adaptive refresh rates. + * @see android.view.DisplayInfo#hasArrSupport for more details. + */ + public boolean hasArrSupport; + /** * The default mode of the display. */ @@ -540,7 +547,8 @@ final class DisplayDeviceInfo { other.brightnessDefault) || !Objects.equals(roundedCorners, other.roundedCorners) || installOrientation != other.installOrientation - || !Objects.equals(displayShape, other.displayShape)) { + || !Objects.equals(displayShape, other.displayShape) + || hasArrSupport != other.hasArrSupport) { diff |= DIFF_OTHER; } return diff; @@ -558,6 +566,7 @@ final class DisplayDeviceInfo { height = other.height; modeId = other.modeId; renderFrameRate = other.renderFrameRate; + hasArrSupport = other.hasArrSupport; defaultModeId = other.defaultModeId; userPreferredModeId = other.userPreferredModeId; supportedModes = other.supportedModes; @@ -602,6 +611,7 @@ final class DisplayDeviceInfo { sb.append(width).append(" x ").append(height); sb.append(", modeId ").append(modeId); sb.append(", renderFrameRate ").append(renderFrameRate); + sb.append(", hasArrSupport ").append(hasArrSupport); sb.append(", defaultModeId ").append(defaultModeId); sb.append(", userPreferredModeId ").append(userPreferredModeId); sb.append(", supportedModes ").append(Arrays.toString(supportedModes)); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 5edea0a8b031..f9c3a46828b9 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -246,6 +246,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { private int mActiveModeId = INVALID_MODE_ID; private boolean mDisplayModeSpecsInvalid; private int mActiveColorMode; + private boolean mHasArrSupport; private Display.HdrCapabilities mHdrCapabilities; private boolean mAllmSupported; private boolean mGameContentTypeSupported; @@ -311,6 +312,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { changed |= updateHdrCapabilitiesLocked(dynamicInfo.hdrCapabilities); changed |= updateAllmSupport(dynamicInfo.autoLowLatencyModeSupported); changed |= updateGameContentTypeSupport(dynamicInfo.gameContentTypeSupported); + changed |= updateHasArrSupportLocked(dynamicInfo.hasArrSupport); if (changed) { mHavePendingChanges = true; @@ -602,6 +604,14 @@ final class LocalDisplayAdapter extends DisplayAdapter { return true; } + private boolean updateHasArrSupportLocked(boolean newHasArrSupport) { + if (mHasArrSupport == newHasArrSupport) { + return false; + } + mHasArrSupport = newHasArrSupport; + return true; + } + private boolean updateAllmSupport(boolean supported) { if (mAllmSupported == supported) { return false; @@ -684,6 +694,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mInfo.supportedColorModes[i] = mSupportedColorModes.get(i); } mInfo.hdrCapabilities = mHdrCapabilities; + mInfo.hasArrSupport = mHasArrSupport; mInfo.appVsyncOffsetNanos = mActiveSfDisplayMode.appVsyncOffsetNanos; mInfo.presentationDeadlineNanos = mActiveSfDisplayMode.presentationDeadlineNanos; mInfo.state = mState; @@ -1274,6 +1285,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { pw.println("mActiveColorMode=" + mActiveColorMode); pw.println("mDefaultModeId=" + mDefaultModeId); pw.println("mUserPreferredModeId=" + mUserPreferredModeId); + pw.println("mHasArrSupport=" + mHasArrSupport); pw.println("mState=" + Display.stateToString(mState)); pw.println("mCommittedState=" + Display.stateToString(mCommittedState)); pw.println("mBrightnessState=" + mBrightnessState); diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 007e3a8fde2f..074a4d851aef 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -506,12 +506,13 @@ final class LogicalDisplay { mBaseDisplayInfo.rotation = Surface.ROTATION_0; mBaseDisplayInfo.modeId = deviceInfo.modeId; mBaseDisplayInfo.renderFrameRate = deviceInfo.renderFrameRate; + mBaseDisplayInfo.hasArrSupport = deviceInfo.hasArrSupport; mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId; mBaseDisplayInfo.userPreferredModeId = deviceInfo.userPreferredModeId; mBaseDisplayInfo.supportedModes = Arrays.copyOf( deviceInfo.supportedModes, deviceInfo.supportedModes.length); mBaseDisplayInfo.appsSupportedModes = syntheticModeManager.createAppSupportedModes( - config, mBaseDisplayInfo.supportedModes + config, mBaseDisplayInfo.supportedModes, mBaseDisplayInfo.hasArrSupport ); mBaseDisplayInfo.colorMode = deviceInfo.colorMode; mBaseDisplayInfo.supportedColorModes = Arrays.copyOf( diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java index 9a0ee034a8f2..a4804e1887fe 100644 --- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java +++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java @@ -50,8 +50,10 @@ public final class BrightnessReason { public static final int MODIFIER_THROTTLED = 0x8; public static final int MODIFIER_MIN_LUX = 0x10; public static final int MODIFIER_MIN_USER_SET_LOWER_BOUND = 0x20; + public static final int MODIFIER_STYLUS_UNDER_USE = 0x40; public static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR - | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_MIN_USER_SET_LOWER_BOUND; + | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_MIN_USER_SET_LOWER_BOUND + | MODIFIER_STYLUS_UNDER_USE; // ADJUSTMENT_* // These things can happen at any point, even if the main brightness reason doesn't @@ -158,6 +160,9 @@ public final class BrightnessReason { if ((mModifier & MODIFIER_MIN_USER_SET_LOWER_BOUND) != 0) { sb.append(" user_min_pref"); } + if ((mModifier & MODIFIER_STYLUS_UNDER_USE) != 0) { + sb.append(" stylus_under_use"); + } int strlen = sb.length(); if (sb.charAt(strlen - 1) == '[') { sb.setLength(strlen - 2); diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java index 71fdaf3f85b6..4bd980822e46 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -110,6 +110,9 @@ public final class DisplayBrightnessController { @VisibleForTesting AutomaticBrightnessController mAutomaticBrightnessController; + // True if the stylus is being used + private boolean mIsStylusBeingUsed; + /** * The constructor of DisplayBrightnessController. */ @@ -460,6 +463,8 @@ public final class DisplayBrightnessController { writer.println(" mScreenBrightnessDefault=" + mScreenBrightnessDefault); writer.println(" mPersistBrightnessNitsForDefaultDisplay=" + mPersistBrightnessNitsForDefaultDisplay); + writer.println(" mIsStylusBeingUsed=" + + mIsStylusBeingUsed); synchronized (mLock) { writer.println(" mPendingScreenBrightness=" + mPendingScreenBrightness); writer.println(" mCurrentScreenBrightness=" + mCurrentScreenBrightness); @@ -505,7 +510,12 @@ public final class DisplayBrightnessController { * Notifies if the stylus is currently being used or not. */ public void setStylusBeingUsed(boolean isEnabled) { - // Todo(b/369977976) - Disable the auto-brightness strategy + mIsStylusBeingUsed = isEnabled; + } + + @VisibleForTesting + boolean isStylusBeingUsed() { + return mIsStylusBeingUsed; } @VisibleForTesting @@ -626,13 +636,14 @@ public final class DisplayBrightnessController { lastUserSetScreenBrightness = mLastUserSetScreenBrightness; } return new StrategySelectionRequest(displayPowerRequest, targetDisplayState, - lastUserSetScreenBrightness, userSetBrightnessChanged, displayOffloadSession); + lastUserSetScreenBrightness, userSetBrightnessChanged, displayOffloadSession, + mIsStylusBeingUsed); } private StrategyExecutionRequest constructStrategyExecutionRequest( DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) { float currentScreenBrightness = getCurrentBrightness(); return new StrategyExecutionRequest(displayPowerRequest, currentScreenBrightness, - mUserSetScreenBrightnessUpdated); + mUserSetScreenBrightnessUpdated, mIsStylusBeingUsed); } } diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java index 06890e72f068..ded7447c5fbc 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -306,7 +306,8 @@ public class DisplayBrightnessStrategySelector { strategySelectionRequest.getDisplayPowerRequest().useNormalBrightnessForDoze, strategySelectionRequest.getLastUserSetScreenBrightness(), strategySelectionRequest.isUserSetBrightnessChanged()); - return mAutomaticBrightnessStrategy1.isAutoBrightnessValid(); + return !strategySelectionRequest.isStylusBeingUsed() + && mAutomaticBrightnessStrategy1.isAutoBrightnessValid(); } private StrategySelectionNotifyRequest constructStrategySelectionNotifyRequest( diff --git a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java index 304640b884ef..7a07c4fc22bf 100644 --- a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java +++ b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java @@ -32,11 +32,15 @@ public final class StrategyExecutionRequest { // Represents if the user set screen brightness was changed or not. private boolean mUserSetBrightnessChanged; + private boolean mIsStylusBeingUsed; + public StrategyExecutionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, - float currentScreenBrightness, boolean userSetBrightnessChanged) { + float currentScreenBrightness, boolean userSetBrightnessChanged, + boolean isStylusBeingUsed) { mDisplayPowerRequest = displayPowerRequest; mCurrentScreenBrightness = currentScreenBrightness; mUserSetBrightnessChanged = userSetBrightnessChanged; + mIsStylusBeingUsed = isStylusBeingUsed; } public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() { @@ -51,6 +55,10 @@ public final class StrategyExecutionRequest { return mUserSetBrightnessChanged; } + public boolean isStylusBeingUsed() { + return mIsStylusBeingUsed; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof StrategyExecutionRequest)) { @@ -59,12 +67,13 @@ public final class StrategyExecutionRequest { StrategyExecutionRequest other = (StrategyExecutionRequest) obj; return Objects.equals(mDisplayPowerRequest, other.getDisplayPowerRequest()) && mCurrentScreenBrightness == other.getCurrentScreenBrightness() - && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged(); + && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged() + && mIsStylusBeingUsed == other.isStylusBeingUsed(); } @Override public int hashCode() { return Objects.hash(mDisplayPowerRequest, mCurrentScreenBrightness, - mUserSetBrightnessChanged); + mUserSetBrightnessChanged, mIsStylusBeingUsed); } } diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java index aa2f23ef9ec1..5c1f03d877a6 100644 --- a/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java +++ b/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java @@ -40,15 +40,19 @@ public final class StrategySelectionRequest { private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; + private boolean mIsStylusBeingUsed; + public StrategySelectionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int targetDisplayState, float lastUserSetScreenBrightness, boolean userSetBrightnessChanged, - DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) { + DisplayManagerInternal.DisplayOffloadSession displayOffloadSession, + boolean isStylusBeingUsed) { mDisplayPowerRequest = displayPowerRequest; mTargetDisplayState = targetDisplayState; mLastUserSetScreenBrightness = lastUserSetScreenBrightness; mUserSetBrightnessChanged = userSetBrightnessChanged; mDisplayOffloadSession = displayOffloadSession; + mIsStylusBeingUsed = isStylusBeingUsed; } public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() { @@ -72,6 +76,10 @@ public final class StrategySelectionRequest { return mDisplayOffloadSession; } + public boolean isStylusBeingUsed() { + return mIsStylusBeingUsed; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof StrategySelectionRequest)) { @@ -82,12 +90,14 @@ public final class StrategySelectionRequest { && mTargetDisplayState == other.getTargetDisplayState() && mLastUserSetScreenBrightness == other.getLastUserSetScreenBrightness() && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged() - && mDisplayOffloadSession.equals(other.getDisplayOffloadSession()); + && mDisplayOffloadSession.equals(other.getDisplayOffloadSession()) + && mIsStylusBeingUsed == other.isStylusBeingUsed(); } @Override public int hashCode() { return Objects.hash(mDisplayPowerRequest, mTargetDisplayState, - mLastUserSetScreenBrightness, mUserSetBrightnessChanged, mDisplayOffloadSession); + mLastUserSetScreenBrightness, mUserSetBrightnessChanged, mDisplayOffloadSession, + mIsStylusBeingUsed); } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java index 7c0c9312888e..b9de34a80bda 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java @@ -37,6 +37,9 @@ public class FallbackBrightnessStrategy implements DisplayBrightnessStrategy{ StrategyExecutionRequest strategyExecutionRequest) { BrightnessReason brightnessReason = new BrightnessReason(); brightnessReason.setReason(BrightnessReason.REASON_MANUAL); + if (strategyExecutionRequest.isStylusBeingUsed()) { + brightnessReason.setModifier(BrightnessReason.MODIFIER_STYLUS_UNDER_USE); + } return new DisplayBrightnessState.Builder() .setBrightness(strategyExecutionRequest.getCurrentScreenBrightness()) .setBrightnessReason(brightnessReason) 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 70bf56646b32..07343f469ed7 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -232,6 +232,11 @@ public class DisplayManagerFlags { Flags::enableBatteryStatsForAllDisplays ); + private final FlagState mHasArrSupport = new FlagState( + Flags.FLAG_ENABLE_HAS_ARR_SUPPORT, + Flags::enableHasArrSupport + ); + /** * @return {@code true} if 'port' is allowed in display layout configuration file. */ @@ -493,6 +498,12 @@ public class DisplayManagerFlags { } /** + * @return {@code true} if hasArrSupport API is enabled. + */ + public boolean hasArrSupportFlag() { + return mHasArrSupport.isEnabled(); + } + /** * dumps all flagstates * @param pw printWriter */ @@ -541,6 +552,7 @@ public class DisplayManagerFlags { pw.println(" " + mEnableApplyDisplayChangedDuringDisplayAdded); pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage); pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled); + pw.println(" " + mHasArrSupport); } private static class FlagState { diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 602c6997b603..ddb29691f42e 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -414,3 +414,11 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_has_arr_support" + namespace: "core_graphics" + description: "Flag for an API to get whether display supports ARR or not" + bug: "361433651" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index ffa64bfcf29f..88562ab9ba2d 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -156,6 +156,8 @@ public class DisplayModeDirector { // a map from display id to display device config private SparseArray<DisplayDeviceConfig> mDisplayDeviceConfigByDisplay = new SparseArray<>(); + private SparseBooleanArray mHasArrSupport; + private BrightnessObserver mBrightnessObserver; private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener; @@ -194,6 +196,8 @@ public class DisplayModeDirector { private final boolean mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled; + private final boolean mHasArrSupportFlagEnabled; + private final DisplayManagerFlags mDisplayManagerFlags; private final DisplayDeviceConfigProvider mDisplayDeviceConfigProvider; @@ -218,6 +222,7 @@ public class DisplayModeDirector { .isDisplaysRefreshRatesSynchronizationEnabled(); mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled = displayManagerFlags .isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled(); + mHasArrSupportFlagEnabled = displayManagerFlags.hasArrSupportFlag(); mDisplayManagerFlags = displayManagerFlags; mDisplayDeviceConfigProvider = displayDeviceConfigProvider; mContext = context; @@ -228,6 +233,7 @@ public class DisplayModeDirector { mSupportedModesByDisplay = new SparseArray<>(); mAppSupportedModesByDisplay = new SparseArray<>(); mDefaultModeByDisplay = new SparseArray<>(); + mHasArrSupport = new SparseBooleanArray(); mAppRequestObserver = new AppRequestObserver(displayManagerFlags); mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig()); mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); @@ -452,7 +458,13 @@ public class DisplayModeDirector { return mAppRequestObserver; } + // TODO(b/372019752) Rename all the occurrences of the VRR with ARR. private boolean isVrrSupportedLocked(int displayId) { + if (mHasArrSupportFlagEnabled) { + Boolean hasArrSupport = mHasArrSupport.get(displayId); + return hasArrSupport != null && hasArrSupport; + } + // TODO(b/371041638) Remove config.isVrrSupportEnabled once hasArrSupport is rolled out DisplayDeviceConfig config = mDisplayDeviceConfigByDisplay.get(displayId); return config != null && config.isVrrSupportEnabled(); } @@ -1469,6 +1481,7 @@ public class DisplayModeDirector { DisplayInfo displayInfo = getDisplayInfo(displayId); registerExternalDisplay(displayInfo); updateDisplayModes(displayId, displayInfo); + updateHasArrSupport(displayId, displayInfo); updateLayoutLimitedFrameRate(displayId, displayInfo); updateUserSettingDisplayPreferredSize(displayInfo); updateDisplaysPeakRefreshRateAndResolution(displayInfo); @@ -1482,6 +1495,7 @@ public class DisplayModeDirector { mDefaultModeByDisplay.remove(displayId); mDisplayDeviceConfigByDisplay.remove(displayId); mSettingsObserver.removeRefreshRateSetting(displayId); + mHasArrSupport.delete(displayId); } updateLayoutLimitedFrameRate(displayId, null); removeUserSettingDisplayPreferredSize(displayId); @@ -1493,6 +1507,7 @@ public class DisplayModeDirector { public void onDisplayChanged(int displayId) { updateDisplayDeviceConfig(displayId); DisplayInfo displayInfo = getDisplayInfo(displayId); + updateHasArrSupport(displayId, displayInfo); updateDisplayModes(displayId, displayInfo); updateLayoutLimitedFrameRate(displayId, displayInfo); updateUserSettingDisplayPreferredSize(displayInfo); @@ -1691,6 +1706,16 @@ public class DisplayModeDirector { } } } + + private void updateHasArrSupport(int displayId, @Nullable DisplayInfo info) { + if (info == null) { + return; + } + synchronized (mLock) { + mHasArrSupport.put(displayId, info.hasArrSupport); + } + } + } /** diff --git a/services/core/java/com/android/server/display/mode/SyntheticModeManager.java b/services/core/java/com/android/server/display/mode/SyntheticModeManager.java index a83b9395dfc0..71b34679882d 100644 --- a/services/core/java/com/android/server/display/mode/SyntheticModeManager.java +++ b/services/core/java/com/android/server/display/mode/SyntheticModeManager.java @@ -37,17 +37,22 @@ public class SyntheticModeManager { SYNTHETIC_MODE_REFRESH_RATE + FLOAT_TOLERANCE; private final boolean mSynthetic60HzModesEnabled; + private final boolean mHasArrSupportEnabled; public SyntheticModeManager(DisplayManagerFlags flags) { mSynthetic60HzModesEnabled = flags.isSynthetic60HzModesEnabled(); + mHasArrSupportEnabled = flags.hasArrSupportFlag(); } /** * creates display supportedModes array, exposed to applications */ public Display.Mode[] createAppSupportedModes(DisplayDeviceConfig config, - Display.Mode[] modes) { - if (!config.isVrrSupportEnabled() || !mSynthetic60HzModesEnabled) { + Display.Mode[] modes, boolean hasArrSupport) { + // TODO(b/361433651) Remove config.isVrrSupportEnabled once hasArrSupport is rolled out + boolean isArrSupported = + mHasArrSupportEnabled ? hasArrSupport : config.isVrrSupportEnabled(); + if (!isArrSupported || !mSynthetic60HzModesEnabled) { return modes; } List<Display.Mode> appSupportedModes = new ArrayList<>(); diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index dc2c95797b8b..636854b85ee4 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -63,7 +63,6 @@ import com.android.internal.pm.parsing.PackageParser2; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; -import com.android.server.integrity.engine.RuleEvaluationEngine; import com.android.server.integrity.model.IntegrityCheckResult; import com.android.server.integrity.model.RuleMetadata; import com.android.server.pm.PackageManagerServiceUtils; @@ -130,7 +129,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { private final Handler mHandler; private final PackageManagerInternal mPackageManagerInternal; private final Supplier<PackageParser2> mParserSupplier; - private final RuleEvaluationEngine mEvaluationEngine; private final IntegrityFileManager mIntegrityFileManager; /** Create an instance of {@link AppIntegrityManagerServiceImpl}. */ @@ -142,7 +140,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { context, LocalServices.getService(PackageManagerInternal.class), PackageParserUtils::forParsingFileWithDefaults, - RuleEvaluationEngine.getRuleEvaluationEngine(), IntegrityFileManager.getInstance(), handlerThread.getThreadHandler()); } @@ -152,13 +149,11 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { Context context, PackageManagerInternal packageManagerInternal, Supplier<PackageParser2> parserSupplier, - RuleEvaluationEngine evaluationEngine, IntegrityFileManager integrityFileManager, Handler handler) { mContext = context; mPackageManagerInternal = packageManagerInternal; mParserSupplier = parserSupplier; - mEvaluationEngine = evaluationEngine; mIntegrityFileManager = integrityFileManager; mHandler = handler; @@ -330,7 +325,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { + " installers " + allowedInstallers); } - IntegrityCheckResult result = mEvaluationEngine.evaluate(appInstallMetadata); + IntegrityCheckResult result = IntegrityCheckResult.allow(); if (!result.getMatchedRules().isEmpty() || DEBUG_INTEGRITY_COMPONENT) { Slog.i( TAG, diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java deleted file mode 100644 index e8c828bb85b6..000000000000 --- a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.integrity.engine; - -import android.content.integrity.AppInstallMetadata; -import android.content.integrity.Rule; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.integrity.IntegrityFileManager; -import com.android.server.integrity.model.IntegrityCheckResult; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * The engine used to evaluate rules against app installs. - * - * <p>Every app install is evaluated against rules (pushed by the verifier) by the evaluation engine - * to allow/block that install. - */ -public class RuleEvaluationEngine { - private static final String TAG = "RuleEvaluation"; - - // The engine for loading rules, retrieving metadata for app installs, and evaluating app - // installs against rules. - private static RuleEvaluationEngine sRuleEvaluationEngine; - - private final IntegrityFileManager mIntegrityFileManager; - - @VisibleForTesting - RuleEvaluationEngine(IntegrityFileManager integrityFileManager) { - mIntegrityFileManager = integrityFileManager; - } - - /** Provide a singleton instance of the rule evaluation engine. */ - public static synchronized RuleEvaluationEngine getRuleEvaluationEngine() { - if (sRuleEvaluationEngine == null) { - return new RuleEvaluationEngine(IntegrityFileManager.getInstance()); - } - return sRuleEvaluationEngine; - } - - /** - * Load, and match the list of rules against an app install metadata. - * - * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules - * against. - * @return result of the integrity check - */ - public IntegrityCheckResult evaluate( - AppInstallMetadata appInstallMetadata) { - List<Rule> rules = loadRules(appInstallMetadata); - return IntegrityCheckResult.allow(); - } - - private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) { - if (!mIntegrityFileManager.initialized()) { - Slog.w(TAG, "Integrity rule files are not available."); - return Collections.emptyList(); - } - - try { - return mIntegrityFileManager.readRules(appInstallMetadata); - } catch (Exception e) { - Slog.e(TAG, "Error loading rules.", e); - return new ArrayList<>(); - } - } -} diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java index c79f41d32b29..25e1c94aa689 100644 --- a/services/core/java/com/android/server/media/AudioManagerRouteController.java +++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java @@ -420,7 +420,7 @@ import java.util.Objects; // to derive a name ourselves from the type instead. String deviceName = audioDeviceInfo.getPort().name(); - if (!TextUtils.isEmpty(address)) { + if (mBluetoothRouteController.containsBondedDeviceWithAddress(address)) { routeId = mBluetoothRouteController.getRouteIdForBluetoothAddress(address); deviceName = mBluetoothRouteController.getNameForBluetoothAddress(address); } diff --git a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java index 8b65ea305ad8..c79d6e5400cd 100644 --- a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java +++ b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java @@ -141,6 +141,11 @@ import java.util.stream.Collectors; mContext.unregisterReceiver(mDeviceStateChangedReceiver); } + /** Returns true if the given address corresponds to a currently-bonded Bluetooth device. */ + public synchronized boolean containsBondedDeviceWithAddress(@Nullable String address) { + return mAddressToBondedDevice.containsKey(address); + } + @Nullable public synchronized String getRouteIdForBluetoothAddress(@Nullable String address) { BluetoothDevice bluetoothDevice = mAddressToBondedDevice.get(address); diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 3ba93845a290..93f512bc7e17 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -334,7 +334,7 @@ public final class NotificationRecord { return helper.createWaveformVibration(vibrationPattern, insistent); } - if (com.android.server.notification.Flags.notificationVibrationInSoundUri()) { + if (com.android.server.notification.Flags.notificationVibrationInSoundUriForChannel()) { final VibrationEffect vibrationEffectFromSoundUri = helper.createVibrationEffectFromSoundUri(channel.getSound()); if (vibrationEffectFromSoundUri != null) { diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java index 49a6ffde6783..a221152222ee 100644 --- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java +++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java @@ -55,6 +55,8 @@ public class BackgroundUserSoundNotifier { private static final String ACTION_SWITCH_USER = "com.android.server.ACTION_SWITCH_TO_USER"; private static final String ACTION_DISMISS_NOTIFICATION = "com.android.server.ACTION_DISMISS_NOTIFICATION"; + private static final String EXTRA_NOTIFICATION_CLIENT_UID = + "com.android.server.EXTRA_CLIENT_UID"; /** * The clientUid from the AudioFocusInfo of the background user, * for which an active notification is currently displayed. @@ -101,7 +103,7 @@ public class BackgroundUserSoundNotifier { ActivityManager am = mSystemUserContext.getSystemService(ActivityManager.class); registerReceiver(am); - mBgUserListener = new BackgroundUserListener(mSystemUserContext); + mBgUserListener = new BackgroundUserListener(); AudioPolicy.Builder focusControlPolicyBuilder = new AudioPolicy.Builder(mSystemUserContext); focusControlPolicyBuilder.setLooper(Looper.getMainLooper()); @@ -119,26 +121,16 @@ public class BackgroundUserSoundNotifier { final class BackgroundUserListener extends AudioPolicy.AudioPolicyFocusListener { - Context mSystemContext; - - BackgroundUserListener(Context systemContext) { - mSystemContext = systemContext; - } - - @SuppressLint("MissingPermission") public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) { try { - BackgroundUserSoundNotifier.this.notifyForegroundUserAboutSoundIfNecessary(afi, - mSystemContext.createContextAsUser( - UserHandle.of(ActivityManager.getCurrentUser()), 0)); + BackgroundUserSoundNotifier.this.notifyForegroundUserAboutSoundIfNecessary(afi); } catch (RemoteException e) { throw new RuntimeException(e); } } - @SuppressLint("MissingPermission") public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { - BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary(); + BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary(afi.getClientUid()); } } @@ -160,7 +152,7 @@ public class BackgroundUserSoundNotifier { if (mNotificationClientUid == -1) { return; } - dismissNotification(); + dismissNotification(mNotificationClientUid); if (DEBUG) { final int actionIndex = intent.getAction().lastIndexOf(".") + 1; @@ -171,7 +163,7 @@ public class BackgroundUserSoundNotifier { } if (ACTION_MUTE_SOUND.equals(intent.getAction())) { - muteAlarmSounds(mSystemUserContext); + muteAlarmSounds(mNotificationClientUid); } else if (ACTION_SWITCH_USER.equals(intent.getAction())) { activityManager.switchUser(UserHandle.getUserId(mNotificationClientUid)); } @@ -193,17 +185,17 @@ public class BackgroundUserSoundNotifier { */ @SuppressLint("MissingPermission") @VisibleForTesting - void muteAlarmSounds(Context context) { - AudioManager audioManager = context.getSystemService(AudioManager.class); + void muteAlarmSounds(int notificationClientUid) { + AudioManager audioManager = mSystemUserContext.getSystemService(AudioManager.class); if (audioManager != null) { for (AudioPlaybackConfiguration apc : audioManager.getActivePlaybackConfigurations()) { - if (apc.getClientUid() == mNotificationClientUid && apc.getPlayerProxy() != null) { + if (apc.getClientUid() == notificationClientUid && apc.getPlayerProxy() != null) { apc.getPlayerProxy().stop(); } } } - AudioFocusInfo currentAfi = getAudioFocusInfoForNotification(); + AudioFocusInfo currentAfi = getAudioFocusInfoForNotification(notificationClientUid); if (currentAfi != null) { mFocusControlAudioPolicy.sendFocusLossAndUpdate(currentAfi); } @@ -212,9 +204,14 @@ public class BackgroundUserSoundNotifier { /** * Check if sound is coming from background user and show notification is required. */ + @SuppressLint("MissingPermission") @VisibleForTesting - void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context foregroundContext) - throws RemoteException { + void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi) throws RemoteException { + if (afi == null) { + return; + } + Context foregroundContext = mSystemUserContext.createContextAsUser( + UserHandle.of(ActivityManager.getCurrentUser()), 0); final int userId = UserHandle.getUserId(afi.getClientUid()); final int usage = afi.getAttributes().getUsage(); UserInfo userInfo = mUserManager.getUserInfo(userId); @@ -232,8 +229,8 @@ public class BackgroundUserSoundNotifier { mNotificationClientUid = afi.getClientUid(); - mNotificationManager.notifyAsUser(LOG_TAG, mNotificationClientUid, - createNotification(userInfo.name, foregroundContext), + mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(), + createNotification(userInfo.name, foregroundContext, afi.getClientUid()), foregroundContext.getUser()); } } @@ -245,14 +242,15 @@ public class BackgroundUserSoundNotifier { * focus ownership. */ @VisibleForTesting - void dismissNotificationIfNecessary() { - if (getAudioFocusInfoForNotification() == null && mNotificationClientUid >= 0) { + void dismissNotificationIfNecessary(int notificationClientUid) { + if (getAudioFocusInfoForNotification(notificationClientUid) == null + && mNotificationClientUid >= 0) { if (DEBUG) { Log.d(LOG_TAG, "Alarm ringing on background user " - + UserHandle.getUserHandleForUid(mNotificationClientUid).getIdentifier() + + UserHandle.getUserHandleForUid(notificationClientUid).getIdentifier() + " left focus stack, dismissing notification"); } - dismissNotification(); + dismissNotification(notificationClientUid); mNotificationClientUid = -1; } } @@ -262,8 +260,8 @@ public class BackgroundUserSoundNotifier { * shown. */ @SuppressLint("MissingPermission") - private void dismissNotification() { - mNotificationManager.cancelAsUser(LOG_TAG, mNotificationClientUid, UserHandle.ALL); + private void dismissNotification(int notificationClientUid) { + mNotificationManager.cancelAsUser(LOG_TAG, notificationClientUid, UserHandle.ALL); } /** @@ -272,11 +270,11 @@ public class BackgroundUserSoundNotifier { @SuppressLint("MissingPermission") @VisibleForTesting @Nullable - AudioFocusInfo getAudioFocusInfoForNotification() { - if (mNotificationClientUid >= 0) { + AudioFocusInfo getAudioFocusInfoForNotification(int notificationClientUid) { + if (notificationClientUid >= 0) { List<AudioFocusInfo> stack = mFocusControlAudioPolicy.getFocusStack(); for (int i = stack.size() - 1; i >= 0; i--) { - if (stack.get(i).getClientUid() == mNotificationClientUid) { + if (stack.get(i).getClientUid() == notificationClientUid) { return stack.get(i); } } @@ -284,22 +282,24 @@ public class BackgroundUserSoundNotifier { return null; } - private PendingIntent createPendingIntent(String intentAction) { + private PendingIntent createPendingIntent(String intentAction, int notificationClientUid) { final Intent intent = new Intent(intentAction); - PendingIntent resultPI = PendingIntent.getBroadcast(mSystemUserContext, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - return resultPI; + intent.putExtra(EXTRA_NOTIFICATION_CLIENT_UID, notificationClientUid); + return PendingIntent.getBroadcast(mSystemUserContext, notificationClientUid, intent, + PendingIntent.FLAG_IMMUTABLE); } + @SuppressLint("MissingPermission") @VisibleForTesting - Notification createNotification(String userName, Context fgContext) { + Notification createNotification(String userName, Context fgContext, int notificationClientUid) { final String title = fgContext.getString(R.string.bg_user_sound_notification_title_alarm, userName); final int icon = R.drawable.ic_audio_alarm; - PendingIntent mutePI = createPendingIntent(ACTION_MUTE_SOUND); - PendingIntent switchPI = createPendingIntent(ACTION_SWITCH_USER); - PendingIntent dismissNotificationPI = createPendingIntent(ACTION_DISMISS_NOTIFICATION); + PendingIntent mutePI = createPendingIntent(ACTION_MUTE_SOUND, notificationClientUid); + PendingIntent switchPI = createPendingIntent(ACTION_SWITCH_USER, notificationClientUid); + PendingIntent dismissNotificationPI = createPendingIntent(ACTION_DISMISS_NOTIFICATION, + notificationClientUid); final Notification.Action mute = new Notification.Action.Builder(null, fgContext.getString(R.string.bg_user_sound_notification_button_mute), diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index 02afdd662b10..9e8598a04a98 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -89,6 +89,7 @@ import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -893,7 +894,8 @@ public final class DexOptHelper { @Override public void onApexStaged(@NonNull ApexStagedEvent event) { - mArtManager.onApexStaged(event.stagedApexModuleNames); + mArtManager.onApexStaged(Arrays.stream(event.stagedApexInfos) + .map(info -> info.moduleName).toArray(String[]::new)); } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerNative.java b/services/core/java/com/android/server/pm/PackageManagerNative.java index 66ecd6e67e56..7d8573e35522 100644 --- a/services/core/java/com/android/server/pm/PackageManagerNative.java +++ b/services/core/java/com/android/server/pm/PackageManagerNative.java @@ -20,7 +20,6 @@ import static android.content.pm.PackageManager.CERT_INPUT_SHA256; import static com.android.server.pm.PackageManagerService.TAG; -import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManagerNative; import android.content.pm.IStagedApexObserver; @@ -184,14 +183,8 @@ final class PackageManagerNative extends IPackageManagerNative.Stub { } @Override - public String[] getStagedApexModuleNames() { - return mPm.mInstallerService.getStagingManager() - .getStagedApexModuleNames().toArray(new String[0]); - } - - @Override - @Nullable - public StagedApexInfo getStagedApexInfo(String moduleName) { - return mPm.mInstallerService.getStagingManager().getStagedApexInfo(moduleName); + public StagedApexInfo[] getStagedApexInfos() { + return mPm.mInstallerService.getStagingManager().getStagedApexInfos().toArray( + new StagedApexInfo[0]); } } diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 94bdfbd9c6f5..38cf0b9136dd 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -17,7 +17,6 @@ package com.android.server.pm; import android.annotation.NonNull; -import android.annotation.Nullable; import android.apex.ApexInfo; import android.apex.ApexSessionInfo; import android.apex.ApexSessionParams; @@ -38,7 +37,6 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.IntArray; import android.util.Slog; @@ -65,9 +63,9 @@ import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -807,14 +805,13 @@ public class StagingManager { } /** - * Returns ApexInfo about APEX contained inside the session as a {@code Map<String, ApexInfo>}, - * where the key of the map is the module name of the ApexInfo. + * Returns ApexInfo about APEX contained inside the session. * - * Returns an empty map if there is any error. + * Returns an empty list if there is any error. */ @VisibleForTesting @NonNull - Map<String, ApexInfo> getStagedApexInfos(@NonNull StagedSession session) { + List<ApexInfo> getStagedApexInfos(@NonNull StagedSession session) { Preconditions.checkArgument(session != null, "Session is null"); Preconditions.checkArgument(!session.hasParentSessionId(), session.sessionId() + " session has parent session"); @@ -824,7 +821,7 @@ public class StagingManager { // Even if caller calls this method on ready session, the session could be abandoned // right after this method is called. if (!session.isSessionReady() || session.isDestroyed()) { - return Collections.emptyMap(); + return Collections.emptyList(); } ApexSessionParams params = new ApexSessionParams(); @@ -838,38 +835,17 @@ public class StagingManager { } } params.childSessionIds = childSessionIds.toArray(); - - ApexInfo[] infos = mApexManager.getStagedApexInfos(params); - Map<String, ApexInfo> result = new ArrayMap<>(); - for (ApexInfo info : infos) { - result.put(info.moduleName, info); - } - return result; + return Arrays.asList(mApexManager.getStagedApexInfos(params)); } /** - * Returns apex module names of all packages that are staged ready - */ - List<String> getStagedApexModuleNames() { - List<String> result = new ArrayList<>(); - synchronized (mStagedSessions) { - for (int i = 0; i < mStagedSessions.size(); i++) { - final StagedSession session = mStagedSessions.valueAt(i); - if (!session.isSessionReady() || session.isDestroyed() - || session.hasParentSessionId() || !session.containsApexSession()) { - continue; - } - result.addAll(getStagedApexInfos(session).keySet()); - } - } - return result; - } - - /** - * Returns ApexInfo of the {@code moduleInfo} provided if it is staged, otherwise returns null. + * Returns ApexInfo list about APEXes contained inside all staged sessions. + * + * Returns an empty list if there is any error. */ - @Nullable - StagedApexInfo getStagedApexInfo(String moduleName) { + @NonNull + List<StagedApexInfo> getStagedApexInfos() { + List<StagedApexInfo> result = new ArrayList<>(); synchronized (mStagedSessions) { for (int i = 0; i < mStagedSessions.size(); i++) { final StagedSession session = mStagedSessions.valueAt(i); @@ -877,8 +853,7 @@ public class StagingManager { || session.hasParentSessionId() || !session.containsApexSession()) { continue; } - ApexInfo ai = getStagedApexInfos(session).get(moduleName); - if (ai != null) { + getStagedApexInfos(session).stream().map(ai -> { StagedApexInfo info = new StagedApexInfo(); info.moduleName = ai.moduleName; info.diskImagePath = ai.modulePath; @@ -886,17 +861,19 @@ public class StagingManager { info.versionName = ai.versionName; info.hasClassPathJars = ai.hasClassPathJars; return info; - } + }).forEach(result::add); } } - return null; + return result; } private void notifyStagedApexObservers() { synchronized (mStagedApexObservers) { + List<StagedApexInfo> stagedApexInfos = getStagedApexInfos(); + ApexStagedEvent event = new ApexStagedEvent(); + event.stagedApexInfos = + stagedApexInfos.toArray(new StagedApexInfo[stagedApexInfos.size()]); for (IStagedApexObserver observer : mStagedApexObservers) { - ApexStagedEvent event = new ApexStagedEvent(); - event.stagedApexModuleNames = getStagedApexModuleNames().toArray(new String[0]); try { observer.onApexStaged(event); } catch (RemoteException re) { diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index eb62b5631c43..8ba56c5320f2 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -771,6 +771,10 @@ public class Notifier { public void onGroupRemoved(int groupId) { mInteractivityByGroupId.remove(groupId); mWakefulnessSessionObserver.removePowerGroup(groupId); + if (mFlags.isPerDisplayWakeByTouchEnabled()) { + resetDisplayInteractivities(); + mInputManagerInternal.setDisplayInteractivities(mDisplayInteractivities); + } } /** diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java index 5f704a002a33..6f1e15b5033f 100644 --- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java +++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java @@ -29,7 +29,6 @@ import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.IpSecTransformState; import android.net.Network; -import android.net.vcn.Flags; import android.net.vcn.VcnManager; import android.os.Handler; import android.os.HandlerExecutor; @@ -233,7 +232,7 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { @VisibleForTesting(visibility = Visibility.PRIVATE) static int getMaxSeqNumIncreasePerSecond(@Nullable PersistableBundleWrapper carrierConfig) { int maxSeqNumIncrease = MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED; - if (Flags.handleSeqNumLeap() && carrierConfig != null) { + if (carrierConfig != null) { maxSeqNumIncrease = carrierConfig.getInt( VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY, @@ -287,10 +286,8 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { // with the new interval mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig); - if (Flags.handleSeqNumLeap()) { - mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig); - mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig); - } + mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig); + mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig); if (canStart() != isStarted()) { if (canStart()) { @@ -438,13 +435,10 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { onValidationResultReceivedInternal(true /* isFailed */); } - // In both "valid" or "unusual_seq_num_leap" cases, trigger network validation - if (Flags.validateNetworkOnIpsecLoss()) { - // Trigger re-validation of the underlying network; if it fails, the VCN will - // attempt to migrate away. - mConnectivityManager.reportNetworkConnectivity( - getNetwork(), false /* hasConnectivity */); - } + // In both "invalid" and "unusual_seq_num_leap" cases, trigger network validation. If + // validation fails, the VCN will attempt to migrate away. + mConnectivityManager.reportNetworkConnectivity( + getNetwork(), false /* hasConnectivity */); } } @@ -474,8 +468,7 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { boolean isUnusualSeqNumLeap = false; // Handle sequence number leap - if (Flags.handleSeqNumLeap() - && maxSeqNumIncreasePerSecond != MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) { + if (maxSeqNumIncreasePerSecond != MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) { final long timeDiffMillis = newState.getTimestampMillis() - oldState.getTimestampMillis(); final long maxSeqNumIncrease = timeDiffMillis * maxSeqNumIncreasePerSecond / 1000; @@ -506,7 +499,7 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { + " actualPktCntDiff: " + actualPktCntDiff); - if (Flags.handleSeqNumLeap() && expectedPktCntDiff < MIN_VALID_EXPECTED_RX_PACKET_NUM) { + if (expectedPktCntDiff < MIN_VALID_EXPECTED_RX_PACKET_NUM) { // The sample size is too small to ensure a reliable detection result return PacketLossCalculationResult.invalid(); } diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java index 78e06d46c74c..c852fb4e170f 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java @@ -25,7 +25,6 @@ import android.net.IpSecTransform; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.vcn.Flags; import android.net.vcn.VcnManager; import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.os.Handler; @@ -297,10 +296,8 @@ public class UnderlyingNetworkEvaluator { updatePriorityClass( underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); - if (Flags.evaluateIpsecLossOnLpNcChange()) { - for (NetworkMetricMonitor monitor : mMetricMonitors) { - monitor.onLinkPropertiesOrCapabilitiesChanged(); - } + for (NetworkMetricMonitor monitor : mMetricMonitors) { + monitor.onLinkPropertiesOrCapabilitiesChanged(); } } @@ -316,10 +313,8 @@ public class UnderlyingNetworkEvaluator { updatePriorityClass( underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); - if (Flags.evaluateIpsecLossOnLpNcChange()) { - for (NetworkMetricMonitor monitor : mMetricMonitors) { - monitor.onLinkPropertiesOrCapabilitiesChanged(); - } + for (NetworkMetricMonitor monitor : mMetricMonitors) { + monitor.onLinkPropertiesOrCapabilitiesChanged(); } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index a44eb48d7836..460de01a7d1d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -285,9 +285,6 @@ import android.app.servertransaction.StopActivityItem; import android.app.servertransaction.TopResumedActivityChangeItem; import android.app.servertransaction.TransferSplashScreenViewStateItem; import android.app.usage.UsageEvents.Event; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; -import android.compat.annotation.Overridable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -467,11 +464,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // finished destroying itself. private static final int DESTROY_TIMEOUT = 10 * 1000; - @ChangeId - @Overridable - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) - static final long UNIVERSAL_RESIZABLE_BY_DEFAULT = 357141415; - final ActivityTaskManagerService mAtmService; final ActivityCallerState mCallerState; @NonNull @@ -3192,7 +3184,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A && mDisplayContent != null && mDisplayContent.getConfiguration() .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP && mDisplayContent.getIgnoreOrientationRequest() - && info.isChangeEnabled(UNIVERSAL_RESIZABLE_BY_DEFAULT); + && info.isChangeEnabled(ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT); if (!compatEnabled && !mWmService.mConstants.mIgnoreActivityOrientationRequest) { return false; } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 87fa62ac0e3b..5b5bb88cac98 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -86,6 +86,7 @@ import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENS import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg; import android.annotation.IntDef; @@ -2786,10 +2787,22 @@ class ActivityStarter { } } - if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0 - && ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 || mSourceRecord == null)) { - // ignore the flag if there is no the sourceRecord or without new_task flag - mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT; + if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) { + final boolean hasNewTaskFlag = (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0; + if (!hasNewTaskFlag || mSourceRecord == null) { + // ignore the flag if there is no the sourceRecord or without new_task flag + Slog.w(TAG_WM, !hasNewTaskFlag + ? "Launch adjacent ignored due to missing NEW_TASK" + : "Launch adjacent ignored due to missing source activity"); + mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT; + } + // Ensure that the source task or its parents has not disabled launch-adjacent + if (mSourceRecord != null && mSourceRecord.getTask() != null && + mSourceRecord.getTask().isLaunchAdjacentDisabled()) { + Slog.w(TAG_WM, "Launch adjacent blocked by source task or ancestor"); + mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT; + } + } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 3d6b64b2e536..3560565ce9cd 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -589,7 +589,7 @@ public abstract class ActivityTaskManagerInternal { * sensitive environment. */ public abstract TaskSnapshot getTaskSnapshotBlocking(int taskId, - boolean isLowResolution); + boolean isLowResolution, @TaskSnapshot.ReferenceFlags int usage); /** Returns true if uid is considered foreground for activity start purposes. */ public abstract boolean isUidForeground(int uid); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 3dfc8f4e5bf9..4db478a13c92 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3946,6 +3946,28 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + private TaskSnapshot getTaskSnapshotInner(int taskId, boolean isLowResolution, + @TaskSnapshot.ReferenceFlags int usage) { + final Task task; + synchronized (mGlobalLock) { + task = mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS); + if (task == null) { + Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found"); + return null; + } + // Try to load snapshot from cache first, and add reference if the snapshot is in cache. + final TaskSnapshot snapshot = mWindowManager.mTaskSnapshotController.getSnapshot(taskId, + task.mUserId, false /* restoreFromDisk */, isLowResolution); + if (snapshot != null) { + snapshot.addReference(usage); + return snapshot; + } + } + // Don't call this while holding the lock as this operation might hit the disk. + return mWindowManager.mTaskSnapshotController.getSnapshot(taskId, + task.mUserId, true /* restoreFromDisk */, isLowResolution); + } + @Override public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) { mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()"); @@ -7274,8 +7296,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public TaskSnapshot getTaskSnapshotBlocking( - int taskId, boolean isLowResolution) { - return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution); + int taskId, boolean isLowResolution, @TaskSnapshot.ReferenceFlags int usage) { + return ActivityTaskManagerService.this.getTaskSnapshotInner( + taskId, isLowResolution, usage); } @Override diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 515f148ac2ff..a380ba1a6f11 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -558,7 +558,7 @@ public class BackgroundActivityStartController { .append(mBalAllowedByPiCreatorWithHardening); sb.append("; resultIfPiCreatorAllowsBal: ").append(mResultForCaller); sb.append("; callerStartMode: ").append(balStartModeToString( - mCheckedOptions.getPendingIntentBackgroundActivityStartMode())); + mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode())); sb.append("; hasRealCaller: ").append(hasRealCaller()); sb.append("; isCallForResult: ").append(mIsCallForResult); sb.append("; isPendingIntent: ").append(isPendingIntent()); @@ -585,7 +585,7 @@ public class BackgroundActivityStartController { sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender); sb.append("; resultIfPiSenderAllowsBal: ").append(mResultForRealCaller); sb.append("; realCallerStartMode: ").append(balStartModeToString( - mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode())); + mCheckedOptions.getPendingIntentBackgroundActivityStartMode())); } // features sb.append("; balImproveRealCallerVisibilityCheck: ") @@ -792,7 +792,8 @@ public class BackgroundActivityStartController { // Allowed before V by creator if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) { Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked" - + " if the PI creator upgrades target_sdk to 35+! " + + " if the PI creator upgrades target_sdk to 35+!" + + " goo.gle/android-bal" + " (missing opt in by PI creator)!" + state); return allowBasedOnCaller(state); } @@ -802,6 +803,7 @@ public class BackgroundActivityStartController { if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) { Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked" + " if the PI sender upgrades target_sdk to 34+! " + + " goo.gle/android-bal" + " (missing opt in by PI sender)!" + state); return allowBasedOnRealCaller(state); } @@ -829,7 +831,8 @@ public class BackgroundActivityStartController { } private BalVerdict abortLaunch(BalState state) { - Slog.wtf(TAG, "Background activity launch blocked! " + state); + Slog.wtf(TAG, "Background activity launch blocked! goo.gle/android-bal " + + state); if (balShowToastsBlocked() && (state.mResultForCaller.allows() || state.mResultForRealCaller.allows())) { // only show a toast if either caller or real caller could launch if they opted in @@ -1044,6 +1047,24 @@ public class BackgroundActivityStartController { "realCallingUid has BAL permission."); } + // don't abort if the realCallingUid has SYSTEM_ALERT_WINDOW permission + Slog.i(TAG, "hasSystemAlertWindowPermission(" + state.mRealCallingUid + ", " + + state.mRealCallingPid + ", " + state.mRealCallingPackage + ") " + + balStartModeToString( + state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode())); + if (state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode() + == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS + && mService.hasSystemAlertWindowPermission(state.mRealCallingUid, + state.mRealCallingPid, state.mRealCallingPackage)) { + Slog.w( + TAG, + "Background activity start for " + + state.mRealCallingPackage + + " allowed because SYSTEM_ALERT_WINDOW permission is granted."); + return new BalVerdict(BAL_ALLOW_SAW_PERMISSION, + /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted"); + } + // if the realCallingUid is a persistent system process, abort if the IntentSender // wasn't allowed to start an activity if (state.mForcedBalByPiSender.allowsBackgroundActivityStarts() diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java index 3dc035e62e58..cbe3d79f10fc 100644 --- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java +++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java @@ -401,6 +401,7 @@ class DeferredDisplayUpdater { || !Objects.equals(first.deviceProductInfo, second.deviceProductInfo) || first.modeId != second.modeId || first.renderFrameRate != second.renderFrameRate + || first.hasArrSupport != second.hasArrSupport || first.defaultModeId != second.defaultModeId || first.userPreferredModeId != second.userPreferredModeId || !Arrays.equals(first.supportedModes, second.supportedModes) diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 98919d9fd617..a4e4deb9ed7d 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -503,6 +503,11 @@ class Task extends TaskFragment { boolean mIsTrimmableFromRecents; /** + * Sets whether the launch-adjacent flag is respected or not for this task or its child tasks. + */ + private boolean mLaunchAdjacentDisabled; + + /** * Bounds offset should be applied when calculating compatible configuration for apps targeting * SDK level 34 or before. */ @@ -3802,6 +3807,9 @@ class Task extends TaskFragment { pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime); pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)"); pw.print(prefix); pw.println(" isTrimmable=" + mIsTrimmableFromRecents); + if (mLaunchAdjacentDisabled) { + pw.println(prefix + "mLaunchAdjacentDisabled=true"); + } } @Override @@ -5734,6 +5742,12 @@ class Task extends TaskFragment { } private boolean canMoveTaskToBack(Task task) { + // Checks whether a task is a child of this task because it can be reparetned when + // transition is deferred. + if (task != this && task.getParent() != this) { + return false; + } + // In LockTask mode, moving a locked task to the back of the root task may expose unlocked // ones. Therefore we need to check if this operation is allowed. if (!mAtmService.getLockTaskController().canMoveTaskToBack(task)) { @@ -5803,7 +5817,7 @@ class Task extends TaskFragment { (deferred) -> { // Need to check again if deferred since the system might // be in a different state. - if (!isAttached() || (deferred && !canMoveTaskToBack(tr))) { + if (!tr.isAttached() || (deferred && !canMoveTaskToBack(tr))) { Slog.e(TAG, "Failed to move task to back after saying we could: " + tr.mTaskId); transition.abort(); @@ -6268,6 +6282,28 @@ class Task extends TaskFragment { } /** + * Sets this task and its children to disable respecting launch-adjacent. + */ + void setLaunchAdjacentDisabled(boolean disabled) { + mLaunchAdjacentDisabled = disabled; + } + + /** + * Returns whether this task or any of its ancestors have disabled respecting the + * launch-adjacent flag. + */ + boolean isLaunchAdjacentDisabled() { + Task t = this; + while (t != null) { + if (t.mLaunchAdjacentDisabled) { + return true; + } + t = t.getParent().asTask(); + } + return false; + } + + /** * Return true if the activityInfo has the same requiredDisplayCategory as this task. */ boolean isSameRequiredDisplayCategory(@NonNull ActivityInfo info) { diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 5dd3bbce4e96..2c71c1a1f4f3 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1074,6 +1074,8 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { final Task launchRootTask = Task.fromWindowContainerToken(options.getLaunchRootTask()); // We only allow this for created by organizer tasks. if (launchRootTask != null && launchRootTask.mCreatedByOrganizer) { + Slog.i(TAG_WM, "Using launch root task from activity options: taskId=" + + launchRootTask.mTaskId); return launchRootTask; } } @@ -1081,19 +1083,25 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Use launch-adjacent-flag-root if launching with launch-adjacent flag. if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0 && mLaunchAdjacentFlagRootTask != null) { + final Task launchAdjacentRootAdjacentTask = + mLaunchAdjacentFlagRootTask.getAdjacentTask(); if (sourceTask != null && (sourceTask == candidateTask || sourceTask.topRunningActivity() == null)) { // Do nothing when task that is getting opened is same as the source or when // the source is no-longer valid. Slog.w(TAG_WM, "Ignoring LAUNCH_ADJACENT because adjacent source is gone."); } else if (sourceTask != null - && mLaunchAdjacentFlagRootTask.getAdjacentTask() != null + && launchAdjacentRootAdjacentTask != null && (sourceTask == mLaunchAdjacentFlagRootTask || sourceTask.isDescendantOf(mLaunchAdjacentFlagRootTask))) { - // If the adjacent launch is coming from the same root, launch to - // adjacent root instead. - return mLaunchAdjacentFlagRootTask.getAdjacentTask(); + // If the adjacent launch is coming from the same root that was specified as the + // launch-adjacent task, so instead we launch to its adjacent root instead. + Slog.i(TAG_WM, "Using adjacent-to specified launch-adjacent task: taskId=" + + launchAdjacentRootAdjacentTask.mTaskId); + return launchAdjacentRootAdjacentTask; } else { + Slog.i(TAG_WM, "Using specified launch-adjacent task: taskId=" + + mLaunchAdjacentFlagRootTask.mTaskId); return mLaunchAdjacentFlagRootTask; } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 82c7a9350eca..166d74b132bd 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -69,6 +69,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT; @@ -1450,6 +1451,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub task.setTrimmableFromRecents(hop.isTrimmableFromRecents()); break; } + case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT: { + final WindowContainer container = WindowContainer.fromBinder(hop.getContainer()); + final Task task = container != null ? container.asTask() : null; + if (task == null || !task.isAttached()) { + Slog.e(TAG, "Attempt to operate on unknown or detached container: " + + container); + break; + } + task.setLaunchAdjacentDisabled(hop.isLaunchAdjacentDisabled()); + break; + } case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: { if (mService.mBackNavigationController.restoreBackNavigation()) { effects |= TRANSACT_EFFECTS_LIFECYCLE; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7e450dd965d6..aca6f7235714 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -16815,6 +16815,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } EnforcingAdmin enforcingAdmin; + + // TODO(b/370472975): enable when we stop policy enforecer callback from blocking the main + // thread if (Flags.setPermissionGrantStateCoexistence()) { enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( admin, @@ -16840,6 +16843,31 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { callback.sendResult(null); return; } + + // TODO(b/266924257): decide how to handle the internal state if the package doesn't + // exist, or the permission isn't requested by the app, because we could end up with + // inconsistent state between the policy engine and package manager. Also a package + // might get removed or has it's permission updated after we've set the policy. + if (grantState == PERMISSION_GRANT_STATE_DEFAULT) { + mDevicePolicyEngine.removeLocalPolicy( + PolicyDefinition.PERMISSION_GRANT(packageName, permission), + enforcingAdmin, + caller.getUserId()); + } else { + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.PERMISSION_GRANT(packageName, permission), + enforcingAdmin, + new IntegerPolicyValue(grantState), + caller.getUserId()); + } + int newState = mInjector.binderWithCleanCallingIdentity(() -> + getPermissionGrantStateForUser( + packageName, permission, caller, caller.getUserId())); + if (newState == grantState) { + callback.sendResult(Bundle.EMPTY); + } else { + callback.sendResult(null); + } } else { Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller) @@ -16862,9 +16890,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } synchronized (getLockObject()) { long ident = mInjector.binderClearCallingIdentity(); + boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId()) + >= android.os.Build.VERSION_CODES.Q; + try { - boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId()) - >= android.os.Build.VERSION_CODES.Q; if (!isPostQAdmin) { // Legacy admins assume that they cannot control pre-M apps if (getTargetSdk(packageName, caller.getUserId()) @@ -16877,47 +16906,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { callback.sendResult(null); return; } - } catch (SecurityException e) { - Slogf.e(LOG_TAG, "Could not set permission grant state", e); - callback.sendResult(null); - } finally { - mInjector.binderRestoreCallingIdentity(ident); - } - } - } - // TODO(b/278710449): enable when we stop policy enforecer callback from blocking the main - // thread - if (false) { - // TODO(b/266924257): decide how to handle the internal state if the package doesn't - // exist, or the permission isn't requested by the app, because we could end up with - // inconsistent state between the policy engine and package manager. Also a package - // might get removed or has it's permission updated after we've set the policy. - if (grantState == PERMISSION_GRANT_STATE_DEFAULT) { - mDevicePolicyEngine.removeLocalPolicy( - PolicyDefinition.PERMISSION_GRANT(packageName, permission), - enforcingAdmin, - caller.getUserId()); - } else { - mDevicePolicyEngine.setLocalPolicy( - PolicyDefinition.PERMISSION_GRANT(packageName, permission), - enforcingAdmin, - new IntegerPolicyValue(grantState), - caller.getUserId()); - } - int newState = mInjector.binderWithCleanCallingIdentity(() -> - getPermissionGrantStateForUser( - packageName, permission, caller, caller.getUserId())); - if (newState == grantState) { - callback.sendResult(Bundle.EMPTY); - } else { - callback.sendResult(null); - } - } else { - synchronized (getLockObject()) { - long ident = mInjector.binderClearCallingIdentity(); - try { - boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId()) - >= android.os.Build.VERSION_CODES.Q; if (grantState == PERMISSION_GRANT_STATE_GRANTED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { @@ -16939,7 +16927,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } catch (SecurityException e) { Slogf.e(LOG_TAG, "Could not set permission grant state", e); - callback.sendResult(null); } finally { mInjector.binderRestoreCallingIdentity(ident); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 01b2d3e34bdc..fdf6b809fa85 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -2343,6 +2343,41 @@ public final class DisplayPowerControllerTest { } } + @Test + public void stylusUsageStarted_disablesAutomaticBrightnessStrategy() { + when(mDisplayManagerFlagsMock.isBlockAutobrightnessChangesOnStylusUsage()) + .thenReturn(true); + when(mDisplayManagerFlagsMock.isRefactorDisplayPowerControllerEnabled()) + .thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_BRIGHT; + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); + advanceTime(2); + clearInvocations(mHolder.automaticBrightnessController); + mHolder.dpc.stylusGestureStarted(2000000); + + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + verify(mHolder.automaticBrightnessController, times(0)) + .getAutomaticScreenBrightness(any()); + + // Stylus usage timed out, hence autobrightness is now enabled back again + advanceTime(6); + verify(mHolder.automaticBrightnessController).getAutomaticScreenBrightness(null); + + // Ideally we should be able to assert against new BrightnessEvent(Display.DEFAULT_DISPLAY), + // but because brightnessEvent has the mTime field which refers to the current time, + // asserting against that is non-trivial + verify(mHolder.automaticBrightnessController).getAutomaticScreenBrightness( + any(BrightnessEvent.class)); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ @@ -2406,6 +2441,7 @@ public final class DisplayPowerControllerTest { .thenReturn(new int[0]); when(displayDeviceConfigMock.getDefaultDozeBrightness()) .thenReturn(DEFAULT_DOZE_BRIGHTNESS); + when(displayDeviceConfigMock.getIdleStylusTimeoutMillis()).thenReturn(5); when(displayDeviceConfigMock.getBrightnessRampFastDecrease()) .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index d831cf8a3643..b6da3ae6a5cd 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -51,6 +51,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -209,8 +210,8 @@ public class LogicalDisplayMapperTest { when(mResourcesMock.getIntArray( com.android.internal.R.array.config_deviceStatesOnWhichToSleep)) .thenReturn(new int[]{0}); - when(mSyntheticModeManagerMock.createAppSupportedModes(any(), any())).thenAnswer( - AdditionalAnswers.returnsSecondArg()); + when(mSyntheticModeManagerMock.createAppSupportedModes(any(), any(), anyBoolean())) + .thenAnswer(AdditionalAnswers.returnsSecondArg()); when(mFlagsMock.isConnectedDisplayManagementEnabled()).thenReturn(false); mLooper = new TestLooper(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java index 8936f061963c..b002a1f73006 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -87,7 +88,7 @@ public class LogicalDisplayTest { mDisplayDeviceInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 60)}; when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo); - when(mSyntheticModeManager.createAppSupportedModes(any(), any())).thenAnswer( + when(mSyntheticModeManager.createAppSupportedModes(any(), any(), anyBoolean())).thenAnswer( AdditionalAnswers.returnsSecondArg()); // Disable binder caches in this process. @@ -582,7 +583,8 @@ public class LogicalDisplayTest { Display.Mode[] appSupportedModes = new Display.Mode[] {new Display.Mode(OTHER_MODE_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 45)}; when(mSyntheticModeManager.createAppSupportedModes( - any(), eq(mDisplayDeviceInfo.supportedModes))).thenReturn(appSupportedModes); + any(), eq(mDisplayDeviceInfo.supportedModes), anyBoolean())) + .thenReturn(appSupportedModes); mLogicalDisplay.updateLocked(mDeviceRepo, mSyntheticModeManager); DisplayInfo info = mLogicalDisplay.getDisplayInfoLocked(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java index 04b79b4f1761..d93ee84f4870 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java @@ -74,7 +74,7 @@ public final class BrightnessReasonTest { @Test public void setModifierDoesntSetIfModifierIsBeyondExtremes() { - int extremeModifier = 0x40; // equal to BrightnessReason.MODIFIER_MASK * 2 + int extremeModifier = 0x80; // reset modifier mBrightnessReason.setModifier(0); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index c0698756a3d7..b3baa5deb4a7 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -125,7 +125,7 @@ public final class DisplayBrightnessControllerTest { DisplayManagerInternal.DisplayOffloadSession.class)); verify(displayBrightnessStrategy).updateBrightness( eq(new StrategyExecutionRequest(displayPowerRequest, DEFAULT_BRIGHTNESS, - /* userSetBrightnessChanged= */ false))); + /* userSetBrightnessChanged= */ false, /* isStylusBeingUsed */ false))); assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(), displayBrightnessStrategy); } @@ -559,4 +559,11 @@ public final class DisplayBrightnessControllerTest { displayDeviceConfig, handler, brightnessMappingStrategy, isDisplayEnabled, leadDisplayId); } + + @Test + public void setStylusBeingUsed_setsStylusInUseState() { + assertFalse(mDisplayBrightnessController.isStylusBeingUsed()); + mDisplayBrightnessController.setStylusBeingUsed(true); + assertTrue(mDisplayBrightnessController.isStylusBeingUsed()); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index a44c517ed9cf..fe1505162e24 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java @@ -68,6 +68,8 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public final class DisplayBrightnessStrategySelectorTest { private static final boolean DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING = false; + private static final boolean STYLUS_IS_NOT_BEING_USED = false; + private static final boolean STYLUS_IS_BEING_USED = true; private static final int DISPLAY_ID = 1; @Mock @@ -196,7 +198,8 @@ public final class DisplayBrightnessStrategySelectorTest { DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mDozeBrightnessModeStrategy); } @@ -212,7 +215,8 @@ public final class DisplayBrightnessStrategySelectorTest { DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mDozeBrightnessModeStrategy); } @@ -226,7 +230,8 @@ public final class DisplayBrightnessStrategySelectorTest { DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mDozeBrightnessModeStrategy); } @@ -249,7 +254,8 @@ public final class DisplayBrightnessStrategySelectorTest { assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mDozeBrightnessModeStrategy); } @@ -259,7 +265,8 @@ public final class DisplayBrightnessStrategySelectorTest { DisplayManagerInternal.DisplayPowerRequest.class); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_OFF, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mScreenOffBrightnessModeStrategy); } @@ -271,7 +278,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mOverrideBrightnessStrategy); } @@ -284,7 +292,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(0.3f); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mTemporaryBrightnessStrategy); } @@ -298,7 +307,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mBoostBrightnessStrategy); } @@ -312,7 +322,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(Float.NaN); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mInvalidBrightnessStrategy); } @@ -323,7 +334,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mFollowerBrightnessStrategy); } @@ -341,7 +353,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mOffloadBrightnessStrategy); } @@ -365,7 +378,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(true); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mAutomaticBrightnessStrategy); verify(mAutomaticBrightnessStrategy).setAutoBrightnessState(Display.STATE_ON, true, BrightnessReason.REASON_UNKNOWN, @@ -373,6 +387,32 @@ public final class DisplayBrightnessStrategySelectorTest { /* useNormalBrightnessForDoze= */ false, 0.1f, false); } + + @Test + public void selectStrategy_doesNotSelectAutomaticStrategyWhenStylusInUse() { + when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); + when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); + when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); + when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); + when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( + true); + mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, + mInjector, DISPLAY_ID, mDisplayManagerFlags); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT; + displayPowerRequest.screenBrightnessOverride = Float.NaN; + when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); + when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); + when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true); + when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(true); + assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( + new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_BEING_USED)), + mAutomaticBrightnessStrategy); + } + @Test public void selectStrategy_selectsAutomaticFallbackStrategyWhenValid() { when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); @@ -389,7 +429,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mAutoBrightnessFallbackStrategy.isValid()).thenReturn(true); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mAutoBrightnessFallbackStrategy); } @@ -407,7 +448,8 @@ public final class DisplayBrightnessStrategySelectorTest { assertNotEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession))); + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED))); } @Test @@ -425,7 +467,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(false); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mFallbackBrightnessStrategy); } @@ -440,7 +483,8 @@ public final class DisplayBrightnessStrategySelectorTest { mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)); + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)); StrategySelectionNotifyRequest strategySelectionNotifyRequest = new StrategySelectionNotifyRequest(displayPowerRequest, Display.STATE_ON, diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java index 99dfa739fb80..2a71af06e0c2 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java @@ -129,7 +129,8 @@ public class AutoBrightnessFallbackStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mAutoBrightnessFallbackStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index efa8b3ef775f..8a1f86093ecf 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java @@ -637,7 +637,7 @@ public class AutomaticBrightnessStrategyTest { .build(); DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState); } @@ -686,7 +686,7 @@ public class AutomaticBrightnessStrategyTest { .build(); DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState); } @@ -725,7 +725,7 @@ public class AutomaticBrightnessStrategyTest { .build(); DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState); } @@ -764,7 +764,7 @@ public class AutomaticBrightnessStrategyTest { .build(); DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java index 275bb3efee8e..c03309e8b4a4 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java @@ -60,7 +60,8 @@ public class BoostBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mBoostBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java index 23e447c25245..e7f80b04e669 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java @@ -57,7 +57,8 @@ public class DozeBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mDozeBrightnessModeStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java index c4a579092d38..dcfa174a53f5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java @@ -61,7 +61,8 @@ public class FallbackBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mFallbackBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, currentBrightness, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java index c01f96e800de..239cdb6002e9 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java @@ -61,7 +61,8 @@ public class FollowerBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mFollowerBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, updatedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java index 9fb2afa26ed2..77302f8747c1 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java @@ -72,7 +72,8 @@ public class OffloadBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mOffloadBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT, mOffloadBrightnessStrategy .getOffloadScreenBrightness(), 0.0f); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java index e8b4c06b9c89..cc21af19722b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java @@ -60,7 +60,8 @@ public class OverrideBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mOverrideBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java index 38709ece7007..652663e52a0a 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java @@ -58,7 +58,8 @@ public final class ScreenOffBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mScreenOffBrightnessModeStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java index f523b6af426b..0022cab371e8 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java @@ -60,7 +60,8 @@ public class TemporaryBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mTemporaryBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt index b2d83d744ce6..9a93fba040cc 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt @@ -43,35 +43,42 @@ class SyntheticModeManagerTest { @Test fun testAppSupportedModes(@TestParameter testCase: AppSupportedModesTestCase) { whenever(mockFlags.isSynthetic60HzModesEnabled).thenReturn(testCase.syntheticModesEnabled) + whenever(mockFlags.hasArrSupportFlag()).thenReturn(testCase.hasArrSupport) whenever(mockConfig.isVrrSupportEnabled).thenReturn(testCase.vrrSupported) val syntheticModeManager = SyntheticModeManager(mockFlags) val result = syntheticModeManager.createAppSupportedModes( - mockConfig, testCase.supportedModes) + mockConfig, testCase.supportedModes, testCase.hasArrSupport) assertThat(result).isEqualTo(testCase.expectedAppModes) } + // TODO(b/361433651) Remove vrrSupported once hasArrSupport is rolled out enum class AppSupportedModesTestCase( val syntheticModesEnabled: Boolean, val vrrSupported: Boolean, + val hasArrSupport: Boolean, val supportedModes: Array<Mode>, val expectedAppModes: Array<Mode> ) { - SYNTHETIC_MODES_NOT_SUPPORTED(false, true, DISPLAY_MODES, DISPLAY_MODES), - VRR_NOT_SUPPORTED(true, false, DISPLAY_MODES, DISPLAY_MODES), - VRR_SYNTHETIC_NOT_SUPPORTED(false, false, DISPLAY_MODES, DISPLAY_MODES), - SINGLE_RESOLUTION_MODES(true, true, DISPLAY_MODES, arrayOf( + SYNTHETIC_MODES_NOT_SUPPORTED(false, true, true, DISPLAY_MODES, DISPLAY_MODES), + VRR_NOT_SUPPORTED(true, false, false, DISPLAY_MODES, DISPLAY_MODES), + VRR_SYNTHETIC_NOT_SUPPORTED(false, false, false, DISPLAY_MODES, DISPLAY_MODES), + SINGLE_RESOLUTION_MODES(true, true, true, DISPLAY_MODES, arrayOf( Mode(2, 100, 100, 120f), Mode(3, 100, 100, 60f, 60f, true, floatArrayOf(), intArrayOf()) )), - NO_60HZ_MODES(true, true, arrayOf(Mode(2, 100, 100, 120f)), + SINGLE_RESOLUTION_MODES_HASARR(true, false, true, DISPLAY_MODES, arrayOf( + Mode(2, 100, 100, 120f), + Mode(3, 100, 100, 60f, 60f, true, floatArrayOf(), intArrayOf()) + )), + NO_60HZ_MODES(true, true, true, arrayOf(Mode(2, 100, 100, 120f)), arrayOf( Mode(2, 100, 100, 120f), Mode(3, 100, 100, 60f, 60f, true, floatArrayOf(), intArrayOf()) ) ), - MULTI_RESOLUTION_MODES(true, true, + MULTI_RESOLUTION_MODES(true, true, true, arrayOf( Mode(1, 100, 100, 120f), Mode(2, 200, 200, 60f), @@ -86,7 +93,7 @@ class SyntheticModeManagerTest { Mode(7, 300, 300, 60f, 60f, true, floatArrayOf(), intArrayOf()) ) ), - WITH_HDR_TYPES(true, true, + WITH_HDR_TYPES(true, true, true, arrayOf( Mode(1, 100, 100, 120f, 120f, false, floatArrayOf(), intArrayOf(1, 2)), Mode(2, 200, 200, 60f, 120f, false, floatArrayOf(), intArrayOf(3, 4)), @@ -99,7 +106,7 @@ class SyntheticModeManagerTest { Mode(5, 200, 200, 60f, 60f, true, floatArrayOf(), intArrayOf(5, 6)), ) ), - UNACHIEVABLE_60HZ(true, true, + UNACHIEVABLE_60HZ(true, true, true, arrayOf( Mode(1, 100, 100, 90f), ), @@ -107,7 +114,7 @@ class SyntheticModeManagerTest { Mode(1, 100, 100, 90f), ) ), - MULTI_RESOLUTION_MODES_WITH_UNACHIEVABLE_60HZ(true, true, + MULTI_RESOLUTION_MODES_WITH_UNACHIEVABLE_60HZ(true, true, true, arrayOf( Mode(1, 100, 100, 120f), Mode(2, 200, 200, 90f), @@ -118,7 +125,7 @@ class SyntheticModeManagerTest { Mode(3, 100, 100, 60f, 60f, true, floatArrayOf(), intArrayOf()), ) ), - LOWER_THAN_60HZ_MODES(true, true, + LOWER_THAN_60HZ_MODES(true, true, true, arrayOf( Mode(1, 100, 100, 30f), Mode(2, 100, 100, 45f), diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java index 121145672d68..439243e85e75 100644 --- a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java +++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java @@ -82,6 +82,11 @@ public class AudioManagerRouteControllerTest { private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET = createAudioDeviceInfo( AudioSystem.DEVICE_OUT_WIRED_HEADSET, "name_wired_hs", /* address= */ null); + private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS = + createAudioDeviceInfo( + AudioSystem.DEVICE_OUT_WIRED_HEADSET, + "name_wired_hs_with_address", + /* address= */ "card=1;device=0"); private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP = createAudioDeviceInfo( AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "name_a2dp", /* address= */ "12:34:45"); @@ -304,6 +309,55 @@ public class AudioManagerRouteControllerTest { assertThat(selectedRoute.getName().toString()).isEqualTo(FAKE_ROUTE_NAME); } + @Test + public void getAvailableRoutes_whenAddressIsPopulatedForNonBluetoothDevice_usesCorrectName() { + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS, + FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP); + + List<MediaRoute2Info> availableRoutes = mControllerUnderTest.getAvailableRoutes(); + assertThat(availableRoutes.size()).isEqualTo(3); + + assertThat( + getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET) + .getName() + .toString()) + .isEqualTo( + FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS + .getProductName() + .toString()); + + assertThat( + getAvailableRouteWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP) + .getName() + .toString()) + .isEqualTo(FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP.getProductName().toString()); + } + + @Test + public void + getAvailableRoutes_whenAddressIsNotPopulatedForNonBluetoothDevice_usesCorrectName() { + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET); + + List<MediaRoute2Info> availableRoutes = mControllerUnderTest.getAvailableRoutes(); + assertThat(availableRoutes.size()).isEqualTo(2); + + assertThat( + getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) + .getName() + .toString()) + .isEqualTo(FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER.getProductName().toString()); + + assertThat( + getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET) + .getName() + .toString()) + .isEqualTo(FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET.getProductName().toString()); + } + // Internal methods. @NonNull diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java index 9ba272446689..625dbe6e16f9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.media.AudioAttributes.USAGE_ALARM; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; @@ -91,6 +92,7 @@ public class BackgroundUserSoundNotifierTest { } @Test public void testAlarmOnBackgroundUser_foregroundUserNotified() throws RemoteException { + assumeTrue(UserManager.supportsMultipleUsers()); AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build(); UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0); final int fgUserId = mSpiedContext.getUserId(); @@ -100,7 +102,7 @@ public class BackgroundUserSoundNotifierTest { /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT); clearInvocations(mNotificationManager); - mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi, mSpiedContext); + mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi); verify(mNotificationManager) .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()), eq(afi.getClientUid()), any(Notification.class), @@ -116,7 +118,7 @@ public class BackgroundUserSoundNotifierTest { /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT); clearInvocations(mNotificationManager); - mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi, mSpiedContext); + mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi); verifyZeroInteractions(mNotificationManager); } @@ -131,7 +133,7 @@ public class BackgroundUserSoundNotifierTest { AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT); clearInvocations(mNotificationManager); - mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi, mSpiedContext); + mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi); verifyZeroInteractions(mNotificationManager); } @@ -169,7 +171,7 @@ public class BackgroundUserSoundNotifierTest { doReturn(focusStack).when(mockAudioPolicy).getFocusStack(); mBackgroundUserSoundNotifier.mFocusControlAudioPolicy = mockAudioPolicy; - mBackgroundUserSoundNotifier.muteAlarmSounds(mSpiedContext); + mBackgroundUserSoundNotifier.muteAlarmSounds(bgUserUid); verify(apc1.getPlayerProxy()).stop(); verify(mockAudioPolicy).sendFocusLossAndUpdate(afi); @@ -178,6 +180,7 @@ public class BackgroundUserSoundNotifierTest { @Test public void testOnAudioFocusGrant_alarmOnBackgroundUser_notifiesForegroundUser() { + assumeTrue(UserManager.supportsMultipleUsers()); final int fgUserId = mSpiedContext.getUserId(); UserInfo bgUser = createUser("Background User", UserManager.USER_TYPE_FULL_SECONDARY, 0); int bgUserUid = bgUser.id * 100000; @@ -205,7 +208,7 @@ public class BackgroundUserSoundNotifierTest { .when(mUserManager).getUserSwitchability(any()); Notification notification = mBackgroundUserSoundNotifier.createNotification(userName, - mSpiedContext); + mSpiedContext, 101000); assertEquals("Alarm for BgUser", notification.extras.getString( Notification.EXTRA_TITLE)); @@ -232,7 +235,7 @@ public class BackgroundUserSoundNotifierTest { .when(mUserManager).getUserSwitchability(any()); Notification notification = mBackgroundUserSoundNotifier.createNotification(userName, - mSpiedContext); + mSpiedContext, 101000); assertEquals(1, notification.actions.length); assertEquals(mSpiedContext.getString( diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java index 39acd8dd816a..43a8aa957fa5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java @@ -72,7 +72,6 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; @@ -486,7 +485,7 @@ public class StagingManagerTest { FakeStagedSession session = new FakeStagedSession(239); session.setIsApex(true); // Call and verify - Map<String, ApexInfo> result = mStagingManager.getStagedApexInfos(session); + var result = mStagingManager.getStagedApexInfos(session); assertThat(result).isEmpty(); } // Invalid session: destroyed @@ -496,7 +495,7 @@ public class StagingManagerTest { session.setIsApex(true); session.setDestroyed(true); // Call and verify - Map<String, ApexInfo> result = mStagingManager.getStagedApexInfos(session); + var result = mStagingManager.getStagedApexInfos(session); assertThat(result).isEmpty(); } } @@ -520,8 +519,8 @@ public class StagingManagerTest { when(mApexManager.getStagedApexInfos(any())).thenReturn(fakeApexInfos); // Call and verify - Map<String, ApexInfo> result = mStagingManager.getStagedApexInfos(validSession); - assertThat(result).containsExactly(fakeApexInfos[0].moduleName, fakeApexInfos[0]); + List<ApexInfo> result = mStagingManager.getStagedApexInfos(validSession); + assertThat(result).containsExactly(fakeApexInfos[0]); ArgumentCaptor<ApexSessionParams> argumentCaptor = ArgumentCaptor.forClass(ApexSessionParams.class); @@ -544,9 +543,8 @@ public class StagingManagerTest { when(mApexManager.getStagedApexInfos(any())).thenReturn(fakeApexInfos); // Call and verify - Map<String, ApexInfo> result = mStagingManager.getStagedApexInfos(parentSession); - assertThat(result).containsExactly(fakeApexInfos[0].moduleName, fakeApexInfos[0], - fakeApexInfos[1].moduleName, fakeApexInfos[1]); + List<ApexInfo> result = mStagingManager.getStagedApexInfos(parentSession); + assertThat(result).containsExactly(fakeApexInfos[0], fakeApexInfos[1]); ArgumentCaptor<ApexSessionParams> argumentCaptor = ArgumentCaptor.forClass(ApexSessionParams.class); @@ -557,7 +555,7 @@ public class StagingManagerTest { } @Test - public void getStagedApexModuleNames_returnsStagedApexModules() throws Exception { + public void getStagedApexInfos_returnsStagedApexModules() throws Exception { FakeStagedSession validSession1 = new FakeStagedSession(239); validSession1.setIsApex(true); validSession1.setSessionReady(); @@ -575,8 +573,8 @@ public class StagingManagerTest { mockApexManagerGetStagedApexInfoWithSessionId(); - List<String> result = mStagingManager.getStagedApexModuleNames(); - assertThat(result).containsExactly("239", "123", "124"); + List<StagedApexInfo> result = mStagingManager.getStagedApexInfos(); + assertThat(result).containsExactly((Object[]) fakeStagedApexInfos("239", "123", "124")); verify(mApexManager, times(2)).getStagedApexInfos(any()); } @@ -605,26 +603,12 @@ public class StagingManagerTest { }); } - @Test - public void getStagedApexInfo() throws Exception { - FakeStagedSession validSession1 = new FakeStagedSession(239); - validSession1.setIsApex(true); - validSession1.setSessionReady(); - mStagingManager.createSession(validSession1); - ApexInfo[] fakeApexInfos = getFakeApexInfo(Arrays.asList("module1")); - when(mApexManager.getStagedApexInfos(any())).thenReturn(fakeApexInfos); - - // Verify null is returned if module name is not found - StagedApexInfo result = mStagingManager.getStagedApexInfo("not found"); - assertThat(result).isNull(); - verify(mApexManager, times(1)).getStagedApexInfos(any()); - // Otherwise, the correct object is returned - result = mStagingManager.getStagedApexInfo("module1"); - assertThat(result.moduleName).isEqualTo(fakeApexInfos[0].moduleName); - assertThat(result.diskImagePath).isEqualTo(fakeApexInfos[0].modulePath); - assertThat(result.versionCode).isEqualTo(fakeApexInfos[0].versionCode); - assertThat(result.versionName).isEqualTo(fakeApexInfos[0].versionName); - verify(mApexManager, times(2)).getStagedApexInfos(any()); + private StagedApexInfo[] fakeStagedApexInfos(String... moduleNames) { + return Arrays.stream(moduleNames).map(moduleName -> { + StagedApexInfo info = new StagedApexInfo(); + info.moduleName = moduleName; + return info; + }).toArray(StagedApexInfo[]::new); } @Test @@ -646,8 +630,8 @@ public class StagingManagerTest { ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass( ApexStagedEvent.class); verify(observer, times(1)).onApexStaged(argumentCaptor.capture()); - assertThat(argumentCaptor.getValue().stagedApexModuleNames).isEqualTo( - new String[]{"239"}); + assertThat(argumentCaptor.getValue().stagedApexInfos).isEqualTo( + fakeStagedApexInfos("239")); } // Create another staged session and verify observers are notified of union @@ -662,8 +646,8 @@ public class StagingManagerTest { ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass( ApexStagedEvent.class); verify(observer, times(1)).onApexStaged(argumentCaptor.capture()); - assertThat(argumentCaptor.getValue().stagedApexModuleNames).isEqualTo( - new String[]{"239", "240"}); + assertThat(argumentCaptor.getValue().stagedApexInfos).isEqualTo( + fakeStagedApexInfos("239", "240")); } // Finally, verify that once unregistered, observer is not notified @@ -699,7 +683,7 @@ public class StagingManagerTest { ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass( ApexStagedEvent.class); verify(observer, times(1)).onApexStaged(argumentCaptor.capture()); - assertThat(argumentCaptor.getValue().stagedApexModuleNames).hasLength(0); + assertThat(argumentCaptor.getValue().stagedApexInfos).hasLength(0); } @Test diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index a1db18232c09..1c7fc63efd41 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java @@ -54,6 +54,7 @@ import android.os.test.TestLooper; import android.provider.Settings; import android.testing.TestableContext; import android.util.IntArray; +import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.Display; import android.view.DisplayAddress; @@ -384,6 +385,32 @@ public class NotifierTest { } @Test + public void testOnGroupRemoved_perDisplayWakeByTouchEnabled() { + createNotifier(); + // GIVEN per-display wake by touch is enabled and one display group has been defined + when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(true); + final int groupId = 313; + final int displayId1 = 3113; + final int displayId2 = 4114; + final int[] displays = new int[]{displayId1, displayId2}; + when(mDisplayManagerInternal.getDisplayIds()).thenReturn(IntArray.wrap(displays)); + when(mDisplayManagerInternal.getDisplayIdsForGroup(groupId)).thenReturn(displays); + mNotifier.onGroupWakefulnessChangeStarted( + groupId, WAKEFULNESS_AWAKE, PowerManager.WAKE_REASON_TAP, /* eventTime= */ 1000); + final SparseBooleanArray expectedDisplayInteractivities = new SparseBooleanArray(); + expectedDisplayInteractivities.put(displayId1, true); + expectedDisplayInteractivities.put(displayId2, true); + verify(mInputManagerInternal).setDisplayInteractivities(expectedDisplayInteractivities); + + // WHEN display group is removed + when(mDisplayManagerInternal.getDisplayIdsByGroupsIds()).thenReturn(new SparseArray<>()); + mNotifier.onGroupRemoved(groupId); + + // THEN native input manager is informed that displays in that group no longer exist + verify(mInputManagerInternal).setDisplayInteractivities(new SparseBooleanArray()); + } + + @Test public void testOnWakeLockListener_RemoteException_NoRethrow() throws RemoteException { when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true); createNotifier(); diff --git a/services/tests/security/forensic/OWNERS b/services/tests/security/forensic/OWNERS new file mode 100644 index 000000000000..80c9afb96033 --- /dev/null +++ b/services/tests/security/forensic/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 36824 + +file:platform/frameworks/base:main:/core/java/android/security/forensic/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java index 3e2949d60183..de5564cb7704 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java @@ -20,6 +20,8 @@ import static com.android.media.audio.Flags.FLAG_ABS_VOLUME_INDEX_FIX; import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME; import static com.android.media.audio.Flags.absVolumeIndexFix; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -109,12 +111,13 @@ public class AudioDeviceVolumeManagerTest { mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName); mTestLooper.dispatchAll(); verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - AudioManager.STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE); + eq(AudioManager.STREAM_MUSIC), eq(minIndex), anyBoolean(), + eq(AudioSystem.DEVICE_OUT_USB_DEVICE)); mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName); mTestLooper.dispatchAll(); verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE); + AudioManager.STREAM_MUSIC, midIndex, false, AudioSystem.DEVICE_OUT_USB_DEVICE); } @Test @@ -151,7 +154,7 @@ public class AudioDeviceVolumeManagerTest { // Stream volume changes verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - AudioManager.STREAM_MUSIC, targetIndex, + AudioManager.STREAM_MUSIC, targetIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET); } @@ -162,7 +165,7 @@ public class AudioDeviceVolumeManagerTest { mTestLooper.dispatchAll(); verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - AudioManager.STREAM_MUSIC, maxIndex, + AudioManager.STREAM_MUSIC, maxIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET); } @@ -193,8 +196,8 @@ public class AudioDeviceVolumeManagerTest { } // Stream volume changes verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - AudioManager.STREAM_MUSIC, passedIndex, - AudioSystem.DEVICE_OUT_BLE_HEADSET); + AudioManager.STREAM_MUSIC, passedIndex, false, + AudioSystem.DEVICE_OUT_BLE_HEADSET); } // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4) @@ -207,7 +210,7 @@ public class AudioDeviceVolumeManagerTest { passedIndex = 4; } verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - AudioManager.STREAM_MUSIC, passedIndex, - AudioSystem.DEVICE_OUT_BLE_HEADSET); + AudioManager.STREAM_MUSIC, passedIndex, false, + AudioSystem.DEVICE_OUT_BLE_HEADSET); } } diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java index 96ac5d251ffd..ce59a86c6ca3 100644 --- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java +++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java @@ -132,12 +132,13 @@ public class NoOpAudioSystemAdapter extends AudioSystemAdapter { } @Override - public int setStreamVolumeIndexAS(int stream, int index, int device) { + public int setStreamVolumeIndexAS(int stream, int index, boolean muted, int device) { return AudioSystem.AUDIO_STATUS_OK; } @Override - public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, int device) { + public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, boolean muted, + int device) { return AudioSystem.AUDIO_STATUS_OK; } 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 dc8c1b9c8a10..6b41c434b80f 100644 --- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java @@ -18,6 +18,7 @@ package com.android.server.audio; import static android.media.AudioManager.ADJUST_LOWER; import static android.media.AudioManager.ADJUST_MUTE; import static android.media.AudioManager.ADJUST_RAISE; +import static android.media.AudioManager.ADJUST_UNMUTE; import static android.media.AudioManager.DEVICE_OUT_BLE_SPEAKER; import static android.media.AudioManager.DEVICE_OUT_BLUETOOTH_SCO; import static android.media.AudioManager.DEVICE_OUT_SPEAKER; @@ -41,13 +42,13 @@ import static android.view.KeyEvent.KEYCODE_VOLUME_UP; import static com.android.media.audio.Flags.FLAG_ABS_VOLUME_INDEX_FIX; import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME; +import static com.android.media.audio.Flags.FLAG_RING_MY_CAR; import static com.android.media.audio.Flags.absVolumeIndexFix; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; @@ -180,6 +181,10 @@ public class VolumeHelperTest { } return mStreamDevice.get(stream); } + + public void setMuteAffectedStreams(int muteAffectedStreams) { + mMuteAffectedStreams = muteAffectedStreams; + } } private static class TestDeviceVolumeBehaviorDispatcherStub @@ -223,6 +228,7 @@ public class VolumeHelperTest { mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider); + mAudioService.setMuteAffectedStreams(AudioSystem.DEFAULT_MUTE_STREAMS_AFFECTED); mTestLooper.dispatchAll(); prepareAudioServiceState(); @@ -258,6 +264,8 @@ public class VolumeHelperTest { for (int streamType : usedStreamTypes) { mAudioService.setStreamVolume(streamType, DEFAULT_STREAM_VOLUME, /*flags=*/0, mContext.getOpPackageName()); + mAudioService.adjustStreamVolume(streamType, ADJUST_UNMUTE, /*flags=*/0, + mContext.getOpPackageName()); } if (!mIsAutomotive) { @@ -301,7 +309,20 @@ public class VolumeHelperTest { mTestLooper.dispatchAll(); verify(mSpyAudioSystem).setStreamVolumeIndexAS( - eq(STREAM_MUSIC), eq(newIndex), eq(DEVICE_OUT_USB_DEVICE)); + STREAM_MUSIC, newIndex, /*muted=*/false, DEVICE_OUT_USB_DEVICE); + } + + @Test + @RequiresFlagsEnabled(FLAG_RING_MY_CAR) + public void adjustStreamVolume_adjustMute_callsASSetStreamVolumeIndex() throws Exception { + int currentIndex = mAudioService.getStreamVolume(STREAM_MUSIC); + + mAudioService.adjustStreamVolume(STREAM_MUSIC, ADJUST_MUTE, /*flags=*/0, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem).setStreamVolumeIndexAS( + eq(STREAM_MUSIC), eq(currentIndex), /*muted=*/eq(true), anyInt()); } @Test @@ -325,7 +346,7 @@ public class VolumeHelperTest { mTestLooper.dispatchAll(); verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - eq(STREAM_MUSIC), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + eq(STREAM_MUSIC), anyInt(), anyBoolean(), eq(DEVICE_OUT_USB_DEVICE)); } @Test @@ -341,7 +362,7 @@ public class VolumeHelperTest { mTestLooper.dispatchAll(); verify(mSpyAudioSystem).setStreamVolumeIndexAS( - eq(STREAM_MUSIC), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + eq(STREAM_MUSIC), anyInt(), eq(false), eq(DEVICE_OUT_USB_DEVICE)); } // --------------- Volume Group APIs --------------- @@ -356,15 +377,15 @@ public class VolumeHelperTest { mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE); mAudioService.setVolumeGroupVolumeIndex(mAudioMusicVolumeGroup.getId(), circularNoMinMaxIncrementVolume(STREAM_MUSIC), /*flags=*/0, - mContext.getOpPackageName(), /*attributionTag*/null); + mContext.getOpPackageName(), /*attributionTag*/null); mTestLooper.dispatchAll(); - verify(mSpyAudioSystem).setVolumeIndexForAttributes(any(), anyInt(), + verify(mSpyAudioSystem).setVolumeIndexForAttributes(any(), anyInt(), eq(false), eq(DEVICE_OUT_USB_DEVICE)); } @Test - public void adjustVolumeGroupVolume_callsASSetVolumeIndexForAttributes() throws Exception { + public void adjustVolumeGroupVolume_callsASSetStreamVolumeIndexAS() throws Exception { assumeNotNull(mAudioMusicVolumeGroup); mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE); @@ -372,8 +393,24 @@ public class VolumeHelperTest { ADJUST_LOWER, /*flags=*/0, mContext.getOpPackageName()); mTestLooper.dispatchAll(); - verify(mSpyAudioSystem).setVolumeIndexForAttributes( - any(), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + // adjust calls setStreamVolumeIndexAS instead of setVolumeIndexForAttributes + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + anyInt(), anyInt(), anyBoolean(), eq(DEVICE_OUT_USB_DEVICE)); + } + + @Test + @RequiresFlagsEnabled(FLAG_RING_MY_CAR) + public void adjustVolumeGroupVolume_adjustMute_callsASSetStreamVolumeIndexAS() + throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + mAudioService.adjustVolumeGroupVolume(mAudioMusicVolumeGroup.getId(), + ADJUST_MUTE, /*flags=*/0, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + // adjust calls setStreamVolumeIndexAS instead of setVolumeIndexForAttributes + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + anyInt(), anyInt(), eq(true), anyInt()); } @Test @@ -437,7 +474,7 @@ public class VolumeHelperTest { @Test public void check_isStreamAffectedByMute() { - assertFalse(mAudioService.isStreamAffectedByMute(STREAM_VOICE_CALL)); + assertTrue(mAudioService.isStreamAffectedByMute(STREAM_VOICE_CALL)); } // --------------------- Volume Flag Check -------------------- @@ -452,14 +489,14 @@ public class VolumeHelperTest { mContext.getOpPackageName()); mTestLooper.dispatchAll(); verify(mSpyAudioSystem).setStreamVolumeIndexAS( - eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_BLE_SPEAKER)); + eq(STREAM_NOTIFICATION), anyInt(), eq(false), eq(DEVICE_OUT_BLE_SPEAKER)); reset(mSpyAudioSystem); mAudioService.adjustStreamVolume(STREAM_NOTIFICATION, ADJUST_LOWER, FLAG_BLUETOOTH_ABS_VOLUME, mContext.getOpPackageName()); mTestLooper.dispatchAll(); verify(mSpyAudioSystem).setStreamVolumeIndexAS( - eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_BLE_SPEAKER)); + eq(STREAM_NOTIFICATION), anyInt(), eq(false), eq(DEVICE_OUT_BLE_SPEAKER)); } @Test @@ -471,13 +508,13 @@ public class VolumeHelperTest { mContext.getOpPackageName()); mTestLooper.dispatchAll(); verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS( - eq(STREAM_NOTIFICATION), eq(newIndex), eq(DEVICE_OUT_BLE_SPEAKER)); + eq(STREAM_NOTIFICATION), eq(newIndex), eq(false), eq(DEVICE_OUT_BLE_SPEAKER)); mAudioService.adjustStreamVolume(STREAM_NOTIFICATION, ADJUST_LOWER, FLAG_BLUETOOTH_ABS_VOLUME, mContext.getOpPackageName()); mTestLooper.dispatchAll(); verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS( - eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_BLE_SPEAKER)); + eq(STREAM_NOTIFICATION), anyInt(), eq(false), eq(DEVICE_OUT_BLE_SPEAKER)); } @Test @@ -523,7 +560,7 @@ public class VolumeHelperTest { mTestLooper.dispatchAll(); verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS( - eq(STREAM_MUSIC), anyInt(), anyInt()); + eq(STREAM_MUSIC), anyInt(), eq(false), anyInt()); } @Test @@ -537,7 +574,7 @@ public class VolumeHelperTest { mTestLooper.dispatchAll(); verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS( - eq(STREAM_VOICE_CALL), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + eq(STREAM_VOICE_CALL), anyInt(), eq(false), eq(DEVICE_OUT_USB_DEVICE)); mAudioService.setDeviceForStream(STREAM_BLUETOOTH_SCO, DEVICE_OUT_BLUETOOTH_SCO); mAudioService.adjustStreamVolume(STREAM_BLUETOOTH_SCO, ADJUST_MUTE, /*flags=*/0, @@ -545,7 +582,7 @@ public class VolumeHelperTest { mTestLooper.dispatchAll(); verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS( - eq(STREAM_BLUETOOTH_SCO), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + eq(STREAM_BLUETOOTH_SCO), anyInt(), eq(false), eq(DEVICE_OUT_USB_DEVICE)); } // ----------------- AudioDeviceVolumeManager ----------------- @@ -568,18 +605,18 @@ public class VolumeHelperTest { mTestLooper.dispatchAll(); // there is a min/max index mismatch in automotive - assertEquals(volMin, mAudioService.getDeviceVolume(volMin, usbDevice, - mContext.getOpPackageName())); + assertEquals(volMin.getVolumeIndex(), mAudioService.getDeviceVolume(volMin, usbDevice, + mContext.getOpPackageName()).getVolumeIndex()); verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - eq(STREAM_MUSIC), anyInt(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE)); + eq(STREAM_MUSIC), anyInt(), anyBoolean(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE)); mAudioService.setDeviceVolume(volMid, usbDevice, mContext.getOpPackageName()); mTestLooper.dispatchAll(); // there is a min/max index mismatch in automotive - assertEquals(volMid, mAudioService.getDeviceVolume(volMid, usbDevice, - mContext.getOpPackageName())); + assertEquals(volMid.getVolumeIndex(), mAudioService.getDeviceVolume(volMid, usbDevice, + mContext.getOpPackageName()).getVolumeIndex()); verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - eq(STREAM_MUSIC), anyInt(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE)); + eq(STREAM_MUSIC), anyInt(), anyBoolean(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE)); } @Test @@ -617,8 +654,7 @@ public class VolumeHelperTest { mAudioService.getDeviceVolume(volCur, bleDevice, mContext.getOpPackageName())); // Stream volume changes verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - STREAM_MUSIC, targetIndex, - AudioSystem.DEVICE_OUT_BLE_HEADSET); + STREAM_MUSIC, targetIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET); } // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4) @@ -630,8 +666,7 @@ public class VolumeHelperTest { assertEquals(volIndex4, mAudioService.getDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName())); verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - STREAM_MUSIC, maxIndex, - AudioSystem.DEVICE_OUT_BLE_HEADSET); + STREAM_MUSIC, maxIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET); } @Test @@ -660,8 +695,7 @@ public class VolumeHelperTest { } // Stream volume changes verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - STREAM_MUSIC, passedIndex, - AudioSystem.DEVICE_OUT_BLE_HEADSET); + STREAM_MUSIC, passedIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET); } // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4) @@ -674,8 +708,7 @@ public class VolumeHelperTest { passedIndex = 4; } verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - STREAM_MUSIC, passedIndex, - AudioSystem.DEVICE_OUT_BLE_HEADSET); + STREAM_MUSIC, passedIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET); } // ---------------- DeviceVolumeBehaviorTest ---------------- diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java index eb9cce007b77..d1f6c2f9f1f0 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -69,7 +69,6 @@ import androidx.test.InstrumentationRegistry; import com.android.internal.R; import com.android.internal.pm.parsing.PackageParser2; import com.android.server.compat.PlatformCompat; -import com.android.server.integrity.engine.RuleEvaluationEngine; import com.android.server.integrity.model.IntegrityCheckResult; import com.android.server.pm.parsing.TestPackageParser2; import com.android.server.testutils.TestUtils; @@ -138,7 +137,6 @@ public class AppIntegrityManagerServiceImplTest { @Mock PlatformCompat mPlatformCompat; @Mock Context mMockContext; @Mock Resources mMockResources; - @Mock RuleEvaluationEngine mRuleEvaluationEngine; @Mock IntegrityFileManager mIntegrityFileManager; @Mock Handler mHandler; @@ -176,7 +174,6 @@ public class AppIntegrityManagerServiceImplTest { mMockContext, mPackageManagerInternal, mParserSupplier, - mRuleEvaluationEngine, mIntegrityFileManager, mHandler); @@ -307,91 +304,6 @@ public class AppIntegrityManagerServiceImplTest { } @Test - public void handleBroadcast_correctArgs() throws Exception { - allowlistUsAsRuleProvider(); - makeUsSystemApp(); - ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mMockContext) - .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); - Intent intent = makeVerificationIntent(); - when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow()); - - broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent); - runJobInHandler(); - - ArgumentCaptor<AppInstallMetadata> metadataCaptor = - ArgumentCaptor.forClass(AppInstallMetadata.class); - verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture()); - AppInstallMetadata appInstallMetadata = metadataCaptor.getValue(); - assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName()); - assertThat(appInstallMetadata.getAppCertificates()).containsExactly(APP_CERT); - assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName()); - // we cannot check installer cert because it seems to be device specific. - assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode()); - assertFalse(appInstallMetadata.isPreInstalled()); - // Asserting source stamp not present. - assertFalse(appInstallMetadata.isStampPresent()); - assertFalse(appInstallMetadata.isStampVerified()); - assertFalse(appInstallMetadata.isStampTrusted()); - assertNull(appInstallMetadata.getStampCertificateHash()); - // These are hardcoded in the test apk android manifest - Map<String, String> allowedInstallers = - appInstallMetadata.getAllowedInstallersAndCertificates(); - assertEquals(2, allowedInstallers.size()); - assertEquals(PLAY_STORE_CERT, allowedInstallers.get(PLAY_STORE_PKG)); - assertEquals(INSTALLER_CERTIFICATE_NOT_EVALUATED, allowedInstallers.get(ADB_INSTALLER)); - } - - @Test - public void handleBroadcast_correctArgs_multipleCerts() throws Exception { - allowlistUsAsRuleProvider(); - makeUsSystemApp(); - ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mMockContext) - .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); - Intent intent = makeVerificationIntent(); - intent.setDataAndType(Uri.fromFile(mTestApkTwoCerts), PACKAGE_MIME_TYPE); - when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow()); - - broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent); - runJobInHandler(); - - ArgumentCaptor<AppInstallMetadata> metadataCaptor = - ArgumentCaptor.forClass(AppInstallMetadata.class); - verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture()); - AppInstallMetadata appInstallMetadata = metadataCaptor.getValue(); - assertThat(appInstallMetadata.getAppCertificates()) - .containsExactly(DUMMY_APP_TWO_CERTS_CERT_1, DUMMY_APP_TWO_CERTS_CERT_2); - } - - @Test - public void handleBroadcast_correctArgs_sourceStamp() throws Exception { - allowlistUsAsRuleProvider(); - makeUsSystemApp(); - ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mMockContext) - .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); - Intent intent = makeVerificationIntent(); - intent.setDataAndType(Uri.fromFile(mTestApkSourceStamp), PACKAGE_MIME_TYPE); - when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow()); - - broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent); - runJobInHandler(); - - ArgumentCaptor<AppInstallMetadata> metadataCaptor = - ArgumentCaptor.forClass(AppInstallMetadata.class); - verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture()); - AppInstallMetadata appInstallMetadata = metadataCaptor.getValue(); - assertTrue(appInstallMetadata.isStampPresent()); - assertTrue(appInstallMetadata.isStampVerified()); - assertTrue(appInstallMetadata.isStampTrusted()); - assertEquals(SOURCE_STAMP_CERTIFICATE_HASH, appInstallMetadata.getStampCertificateHash()); - } - - @Test public void handleBroadcast_allow() throws Exception { allowlistUsAsRuleProvider(); makeUsSystemApp(); @@ -400,7 +312,6 @@ public class AppIntegrityManagerServiceImplTest { verify(mMockContext) .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); Intent intent = makeVerificationIntent(); - when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow()); broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent); runJobInHandler(); @@ -411,32 +322,6 @@ public class AppIntegrityManagerServiceImplTest { } @Test - public void handleBroadcast_reject() throws Exception { - allowlistUsAsRuleProvider(); - makeUsSystemApp(); - ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mMockContext) - .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); - when(mRuleEvaluationEngine.evaluate(any())) - .thenReturn( - IntegrityCheckResult.deny( - Arrays.asList( - new Rule( - new AtomicFormula.BooleanAtomicFormula( - AtomicFormula.PRE_INSTALLED, false), - Rule.DENY)))); - Intent intent = makeVerificationIntent(); - - broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent); - runJobInHandler(); - - verify(mPackageManagerInternal) - .setIntegrityVerificationResult( - 1, PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT); - } - - @Test public void handleBroadcast_notInitialized() throws Exception { allowlistUsAsRuleProvider(); makeUsSystemApp(); @@ -446,7 +331,6 @@ public class AppIntegrityManagerServiceImplTest { verify(mMockContext) .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); Intent intent = makeVerificationIntent(); - when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow()); broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent); runJobInHandler(); @@ -467,8 +351,6 @@ public class AppIntegrityManagerServiceImplTest { verify(mMockContext, atLeastOnce()) .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); Intent intent = makeVerificationIntent(TEST_FRAMEWORK_PACKAGE); - when(mRuleEvaluationEngine.evaluate(any())) - .thenReturn(IntegrityCheckResult.deny(/* rule= */ null)); broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent); runJobInHandler(); diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java deleted file mode 100644 index e1ee9c3a1c8f..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.integrity.engine; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -import android.content.integrity.AppInstallMetadata; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.Rule; - -import com.android.server.integrity.IntegrityFileManager; -import com.android.server.integrity.model.IntegrityCheckResult; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -@RunWith(JUnit4.class) -public class RuleEvaluationEngineTest { - - private static final String INSTALLER_1 = "installer1"; - private static final String INSTALLER_1_CERT = "installer1_cert"; - private static final String INSTALLER_2 = "installer2"; - private static final String INSTALLER_2_CERT = "installer2_cert"; - - private static final String RANDOM_INSTALLER = "random"; - private static final String RANDOM_INSTALLER_CERT = "random_cert"; - - @Mock - private IntegrityFileManager mIntegrityFileManager; - - private RuleEvaluationEngine mEngine; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mEngine = new RuleEvaluationEngine(mIntegrityFileManager); - - when(mIntegrityFileManager.readRules(any())).thenReturn(Collections.singletonList(new Rule( - IntegrityFormula.Installer.notAllowedByManifest(), Rule.DENY))); - - when(mIntegrityFileManager.initialized()).thenReturn(true); - } - - @Test - public void testAllowedInstallers_empty() { - AppInstallMetadata appInstallMetadata1 = - getAppInstallMetadataBuilder() - .setInstallerName(INSTALLER_1) - .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) - .build(); - AppInstallMetadata appInstallMetadata2 = - getAppInstallMetadataBuilder() - .setInstallerName(INSTALLER_2) - .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT)) - .build(); - AppInstallMetadata appInstallMetadata3 = - getAppInstallMetadataBuilder() - .setInstallerName(RANDOM_INSTALLER) - .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT)) - .build(); - - assertThat(mEngine.evaluate(appInstallMetadata1).getEffect()) - .isEqualTo(IntegrityCheckResult.Effect.ALLOW); - assertThat(mEngine.evaluate(appInstallMetadata2).getEffect()) - .isEqualTo(IntegrityCheckResult.Effect.ALLOW); - assertThat(mEngine.evaluate(appInstallMetadata3).getEffect()) - .isEqualTo(IntegrityCheckResult.Effect.ALLOW); - } - - @Test - public void testAllowedInstallers_oneElement() { - Map<String, String> allowedInstallers = - Collections.singletonMap(INSTALLER_1, INSTALLER_1_CERT); - - AppInstallMetadata appInstallMetadata1 = - getAppInstallMetadataBuilder() - .setInstallerName(INSTALLER_1) - .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) - .setAllowedInstallersAndCert(allowedInstallers) - .build(); - assertThat(mEngine.evaluate(appInstallMetadata1).getEffect()) - .isEqualTo(IntegrityCheckResult.Effect.ALLOW); - - AppInstallMetadata appInstallMetadata2 = - getAppInstallMetadataBuilder() - .setInstallerName(RANDOM_INSTALLER) - .setAllowedInstallersAndCert(allowedInstallers) - .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) - .build(); - assertThat(mEngine.evaluate(appInstallMetadata2).getEffect()) - .isEqualTo(IntegrityCheckResult.Effect.ALLOW); - - AppInstallMetadata appInstallMetadata3 = - getAppInstallMetadataBuilder() - .setInstallerName(INSTALLER_1) - .setAllowedInstallersAndCert(allowedInstallers) - .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT)) - .build(); - assertThat(mEngine.evaluate(appInstallMetadata3).getEffect()) - .isEqualTo(IntegrityCheckResult.Effect.ALLOW); - - AppInstallMetadata appInstallMetadata4 = - getAppInstallMetadataBuilder() - .setInstallerName(INSTALLER_1) - .setAllowedInstallersAndCert(allowedInstallers) - .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT)) - .build(); - assertThat(mEngine.evaluate(appInstallMetadata4).getEffect()) - .isEqualTo(IntegrityCheckResult.Effect.ALLOW); - } - - @Test - public void testAllowedInstallers_multipleElement() { - Map<String, String> allowedInstallers = new HashMap<>(2); - allowedInstallers.put(INSTALLER_1, INSTALLER_1_CERT); - allowedInstallers.put(INSTALLER_2, INSTALLER_2_CERT); - - AppInstallMetadata appInstallMetadata1 = - getAppInstallMetadataBuilder() - .setInstallerName(INSTALLER_1) - .setAllowedInstallersAndCert(allowedInstallers) - .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) - .build(); - assertThat(mEngine.evaluate(appInstallMetadata1).getEffect()) - .isEqualTo(IntegrityCheckResult.Effect.ALLOW); - - AppInstallMetadata appInstallMetadata2 = - getAppInstallMetadataBuilder() - .setInstallerName(INSTALLER_2) - .setAllowedInstallersAndCert(allowedInstallers) - .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT)) - .build(); - assertThat(mEngine.evaluate(appInstallMetadata2).getEffect()) - .isEqualTo(IntegrityCheckResult.Effect.ALLOW); - - AppInstallMetadata appInstallMetadata3 = - getAppInstallMetadataBuilder() - .setInstallerName(INSTALLER_1) - .setAllowedInstallersAndCert(allowedInstallers) - .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT)) - .build(); - assertThat(mEngine.evaluate(appInstallMetadata3).getEffect()) - .isEqualTo(IntegrityCheckResult.Effect.ALLOW); - - AppInstallMetadata appInstallMetadata4 = - getAppInstallMetadataBuilder() - .setInstallerName(INSTALLER_2) - .setAllowedInstallersAndCert(allowedInstallers) - .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) - .build(); - assertThat(mEngine.evaluate(appInstallMetadata4).getEffect()) - .isEqualTo(IntegrityCheckResult.Effect.ALLOW); - } - - /** Returns a builder with all fields filled with some placeholder data. */ - private AppInstallMetadata.Builder getAppInstallMetadataBuilder() { - return new AppInstallMetadata.Builder() - .setPackageName("abc") - .setAppCertificates(Collections.singletonList("abc")) - .setAppCertificateLineage(Collections.singletonList("abc")) - .setInstallerCertificates(Collections.singletonList("abc")) - .setInstallerName("abc") - .setVersionCode(-1) - .setIsPreInstalled(true); - } -} diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index c247c08c8010..3b0cb4ad8779 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -68,6 +68,7 @@ import static org.testng.Assert.assertThrows; import android.Manifest; import android.app.Activity; +import android.app.ActivityManager; import android.app.AlarmManager; import android.app.Flags; import android.app.IOnProjectionStateChangedListener; @@ -247,6 +248,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { mInjector = spy(new TestInjector()); mUiManagerService = new UiModeManagerService(mContext, /* setupWizardComplete= */ true, mTwilightManager, mInjector); + // Initialize the current user. + mUiManagerService.setCurrentUser(ActivityManager.getCurrentUser()); try { mUiManagerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); } catch (SecurityException e) {/* ignore for permission denial */} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 50a5f658f059..4391152220c0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -546,7 +546,8 @@ public class NotificationRecordTest extends UiServiceTestCase { } @Test - @EnableFlags(com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI) + @EnableFlags(com.android.server.notification.Flags + .FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI_FOR_CHANNEL) public void testVibration_customVibrationForSound_withVibrationUri() throws IOException { defaultChannel.enableVibration(true); VibrationInfo vibration = getTestingVibration(mVibrator); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index d0080d29f82b..d5f86b6feac8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -1261,6 +1261,33 @@ public class ActivityStarterTests extends WindowTestsBase { } @Test + public void testLaunchAdjacentDisabled() { + final ActivityStarter starter = + prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetRootTask */); + final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final ActivityOptions options = ActivityOptions.makeBasic(); + final ActivityRecord[] outActivity = new ActivityRecord[1]; + + // Activity must not land on split-screen task if currently not in split-screen mode. + starter.setActivityOptions(options.toBundle()) + .setReason("testLaunchAdjacentDisabled") + .setOutActivity(outActivity).execute(); + assertThat(outActivity[0].inMultiWindowMode()).isFalse(); + + // Move activity to split-screen-primary task and make sure it has the focus. + TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayContent()); + top.getRootTask().reparent(splitOrg.mPrimary, POSITION_BOTTOM); + top.getRootTask().moveToFront("testLaunchAdjacentDisabled"); + top.getRootTask().setLaunchAdjacentDisabled(true); + + // Ensure activity does not launch into split-screen-secondary when launch adjacent is + // disabled + startActivityInner(starter, outActivity[0], top, options, null /* inTask */, + null /* taskFragment*/); + assertThat(outActivity[0].isDescendantOf(splitOrg.mSecondary)).isFalse(); + } + + @Test public void testTransientLaunchWithKeyguard() { final ActivityStarter starter = prepareStarter(0 /* flags */); final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java index 3910904337b2..750968100e2b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java @@ -683,6 +683,41 @@ public class BackgroundActivityStartControllerExemptionTests { } @Test + @RequiresFlagsEnabled(Flags.FLAG_BAL_ADDITIONAL_START_MODES) + public void testRealCaller_sawPermission() { + int callingUid = REGULAR_UID_1; + int callingPid = REGULAR_PID_1; + final String callingPackage = REGULAR_PACKAGE_1; + int realCallingUid = REGULAR_UID_2; + int realCallingPid = REGULAR_PID_2; + + // setup state + when(mService.hasSystemAlertWindowPermission(eq(realCallingUid), eq(realCallingPid), + any())).thenReturn(true); + + // prepare call + PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; + BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE; + Intent intent = TEST_INTENT; + ActivityOptions checkedOptions = + mCheckedOptions.setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); + BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid, + callingPid, callingPackage, realCallingUid, realCallingPid, null, + originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent, + checkedOptions); + + // call + BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller( + balState); + balState.setResultForCaller(callerVerdict); + + // assertions + assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo( + BAL_ALLOW_SAW_PERMISSION); + } + + @Test public void testCaller_isRecents() { int callingUid = REGULAR_UID_1; int callingPid = REGULAR_PID_1; diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 72f4fa9158fb..c1edae91aabb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4883,7 +4883,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test - @EnableCompatChanges({ActivityRecord.UNIVERSAL_RESIZABLE_BY_DEFAULT}) + @EnableCompatChanges({ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT}) public void testUniversalResizeableByDefault() { mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT); mDisplayContent.setIgnoreOrientationRequest(false); diff --git a/services/usb/java/com/android/server/usb/UsbManagerInternal.java b/services/usb/java/com/android/server/usb/UsbManagerInternal.java index c97df6b4f63a..31c5986c45b8 100644 --- a/services/usb/java/com/android/server/usb/UsbManagerInternal.java +++ b/services/usb/java/com/android/server/usb/UsbManagerInternal.java @@ -34,9 +34,11 @@ import java.lang.annotation.RetentionPolicy; public abstract class UsbManagerInternal { public static final int OS_USB_DISABLE_REASON_AAPM = 0; + public static final int OS_USB_DISABLE_REASON_LOCKDOWN_MODE = 1; @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {OS_USB_DISABLE_REASON_AAPM}) + @IntDef(value = {OS_USB_DISABLE_REASON_AAPM, + OS_USB_DISABLE_REASON_LOCKDOWN_MODE}) public @interface OsUsbDisableReason { } diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index ba9dff656f0a..ec4f7e1ea4ba 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -1527,8 +1527,11 @@ public class UsbService extends IUsbManager.Stub { } mLockdownModeStatus = lockDownTriggeredByUser; for (UsbPort port: mPortManager.getPorts()) { - enableUsbData(port.getId(), !lockDownTriggeredByUser, STRONG_AUTH_OPERATION_ID, - new IUsbOperationInternal.Default()); + enableUsbDataInternal(port.getId(), !lockDownTriggeredByUser, + STRONG_AUTH_OPERATION_ID, + new IUsbOperationInternal.Default(), + UsbManagerInternal.OS_USB_DISABLE_REASON_LOCKDOWN_MODE, + true); } } } diff --git a/telecomm/java/android/telecom/Logging/Session.java b/telecomm/java/android/telecom/Logging/Session.java index e2fb6019f30a..ad0d4f4de3ae 100644 --- a/telecomm/java/android/telecom/Logging/Session.java +++ b/telecomm/java/android/telecom/Logging/Session.java @@ -16,7 +16,6 @@ package android.telecom.Logging; -import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -24,8 +23,13 @@ import android.telecom.Log; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.telecom.flags.Flags; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** * Stores information about a thread's point of entry into that should persist until that thread @@ -55,7 +59,7 @@ public class Session { * Initial value of mExecutionEndTimeMs and the final value of {@link #getLocalExecutionTime()} * if the Session is canceled. */ - public static final int UNDEFINED = -1; + public static final long UNDEFINED = -1; public static class Info implements Parcelable { public final String sessionId; @@ -129,46 +133,39 @@ public class Session { } } - private String mSessionId; - private String mShortMethodName; + private final String mSessionId; + private volatile String mShortMethodName; private long mExecutionStartTimeMs; private long mExecutionEndTimeMs = UNDEFINED; - private Session mParentSession; - private ArrayList<Session> mChildSessions; + private volatile Session mParentSession; + private final ArrayList<Session> mChildSessions = new ArrayList<>(5); private boolean mIsCompleted = false; - private boolean mIsExternal = false; - private int mChildCounter = 0; + private final boolean mIsExternal; + private final AtomicInteger mChildCounter = new AtomicInteger(0); // True if this is a subsession that has been started from the same thread as the parent // session. This can happen if Log.startSession(...) is called multiple times on the same // thread in the case of one Telecom entry point method calling another entry point method. // In this case, we can just make this subsession "invisible," but still keep track of it so // that the Log.endSession() calls match up. - private boolean mIsStartedFromActiveSession = false; + private final boolean mIsStartedFromActiveSession; // Optionally provided info about the method/class/component that started the session in order // to make Logging easier. This info will be provided in parentheses along with the session. - private String mOwnerInfo; + private final String mOwnerInfo; // Cache Full Method path so that recursive population of the full method path only needs to // be calculated once. - private String mFullMethodPathCache; + private volatile String mFullMethodPathCache; public Session(String sessionId, String shortMethodName, long startTimeMs, - boolean isStartedFromActiveSession, String ownerInfo) { - setSessionId(sessionId); + boolean isStartedFromActiveSession, boolean isExternal, String ownerInfo) { + mSessionId = (sessionId != null) ? sessionId : "???"; setShortMethodName(shortMethodName); mExecutionStartTimeMs = startTimeMs; mParentSession = null; - mChildSessions = new ArrayList<>(5); mIsStartedFromActiveSession = isStartedFromActiveSession; + mIsExternal = isExternal; mOwnerInfo = ownerInfo; } - public void setSessionId(@NonNull String sessionId) { - if (sessionId == null) { - mSessionId = "?"; - } - mSessionId = sessionId; - } - public String getShortMethodName() { return mShortMethodName; } @@ -180,10 +177,6 @@ public class Session { mShortMethodName = shortMethodName; } - public void setIsExternal(boolean isExternal) { - mIsExternal = isExternal; - } - public boolean isExternal() { return mIsExternal; } @@ -193,13 +186,15 @@ public class Session { } public void addChild(Session childSession) { - if (childSession != null) { + if (childSession == null) return; + synchronized (mChildSessions) { mChildSessions.add(childSession); } } public void removeChild(Session child) { - if (child != null) { + if (child == null) return; + synchronized (mChildSessions) { mChildSessions.remove(child); } } @@ -217,7 +212,9 @@ public class Session { } public ArrayList<Session> getChildSessions() { - return mChildSessions; + synchronized (mChildSessions) { + return new ArrayList<>(mChildSessions); + } } public boolean isSessionCompleted() { @@ -259,17 +256,41 @@ public class Session { return mExecutionEndTimeMs - mExecutionStartTimeMs; } - public synchronized String getNextChildId() { - return String.valueOf(mChildCounter++); + public String getNextChildId() { + return String.valueOf(mChildCounter.getAndIncrement()); } - // Builds full session id recursively + // Builds full session ID, which incliudes the optional external indicators (E), + // base session ID, and the optional sub-session IDs (_X): @[E-]...[ID][_X][_Y]... private String getFullSessionId() { - return getFullSessionId(0); + if (!Flags.endSessionImprovements()) return getFullSessionIdRecursive(0); + int currParentCount = 0; + StringBuilder id = new StringBuilder(); + Session currSession = this; + while (currSession != null) { + Session parentSession = currSession.getParentSession(); + if (parentSession != null) { + if (currParentCount >= SESSION_RECURSION_LIMIT) { + id.insert(0, getSessionId()); + id.insert(0, TRUNCATE_STRING); + android.util.Slog.w(LOG_TAG, "getFullSessionId: Hit iteration limit!"); + return id.toString(); + } + if (Log.VERBOSE) { + id.insert(0, currSession.getSessionId()); + id.insert(0, SESSION_SEPARATION_CHAR_CHILD); + } + } else { + id.insert(0, currSession.getSessionId()); + } + currSession = parentSession; + currParentCount++; + } + return id.toString(); } // keep track of calls and bail if we hit the recursion limit - private String getFullSessionId(int parentCount) { + private String getFullSessionIdRecursive(int parentCount) { if (parentCount >= SESSION_RECURSION_LIMIT) { // Don't use Telecom's Log.w here or it will cause infinite recursion because it will // try to add session information to this logging statement, which will cause it to hit @@ -286,12 +307,12 @@ public class Session { return mSessionId; } else { if (Log.VERBOSE) { - return parentSession.getFullSessionId(parentCount + 1) + return parentSession.getFullSessionIdRecursive(parentCount + 1) // Append "_X" to subsession to show subsession designation. + SESSION_SEPARATION_CHAR_CHILD + mSessionId; } else { // Only worry about the base ID at the top of the tree. - return parentSession.getFullSessionId(parentCount + 1); + return parentSession.getFullSessionIdRecursive(parentCount + 1); } } @@ -300,16 +321,18 @@ public class Session { private Session getRootSession(String callingMethod) { int currParentCount = 0; Session topNode = this; - while (topNode.getParentSession() != null) { + Session parentNode = topNode.getParentSession(); + while (parentNode != null) { if (currParentCount >= SESSION_RECURSION_LIMIT) { // Don't use Telecom's Log.w here or it will cause infinite recursion because it // will try to add session information to this logging statement, which will cause // it to hit this condition again and so on... - android.util.Slog.w(LOG_TAG, "getRootSession: Hit recursion limit from " + android.util.Slog.w(LOG_TAG, "getRootSession: Hit iteration limit from " + callingMethod); break; } - topNode = topNode.getParentSession(); + topNode = parentNode; + parentNode = topNode.getParentSession(); currParentCount++; } return topNode; @@ -320,14 +343,40 @@ public class Session { return getRootSession("printFullSessionTree").printSessionTree(); } - // Recursively move down session tree using DFS, but print out each node when it is reached. private String printSessionTree() { StringBuilder sb = new StringBuilder(); - printSessionTree(0, sb, 0); + if (!Flags.endSessionImprovements()) { + printSessionTreeRecursive(0, sb, 0); + return sb.toString(); + } + int depth = 0; + ArrayDeque<Session> deque = new ArrayDeque<>(); + deque.add(this); + while (!deque.isEmpty()) { + Session node = deque.pollFirst(); + sb.append("\t".repeat(depth)); + sb.append(node.toString()); + sb.append("\n"); + if (depth >= SESSION_RECURSION_LIMIT) { + sb.append(TRUNCATE_STRING); + depth -= 1; + continue; + } + List<Session> childSessions = node.getChildSessions().reversed(); + if (!childSessions.isEmpty()) { + depth += 1; + for (Session child : childSessions) { + deque.addFirst(child); + } + } else { + depth -= 1; + } + } return sb.toString(); } - private void printSessionTree(int tabI, StringBuilder sb, int currChildCount) { + // Recursively move down session tree using DFS, but print out each node when it is reached. + private void printSessionTreeRecursive(int tabI, StringBuilder sb, int currChildCount) { // Prevent infinite recursion. if (currChildCount >= SESSION_RECURSION_LIMIT) { // Don't use Telecom's Log.w here or it will cause infinite recursion because it will @@ -343,26 +392,85 @@ public class Session { for (int i = 0; i <= tabI; i++) { sb.append("\t"); } - child.printSessionTree(tabI + 1, sb, currChildCount + 1); + child.printSessionTreeRecursive(tabI + 1, sb, currChildCount + 1); } } - // Recursively concatenate mShortMethodName with the parent Sessions to create full method - // path. if truncatePath is set to true, all other external sessions (except for the most - // recent) will be truncated to "..." + // + + /** + * Concatenate the short method name with the parent Sessions to create full method path. + * @param truncatePath if truncatePath is set to true, all other external sessions (except for + * the most recent) will be truncated to "..." + * @return The full method path associated with this Session. + */ + @VisibleForTesting public String getFullMethodPath(boolean truncatePath) { StringBuilder sb = new StringBuilder(); - getFullMethodPath(sb, truncatePath, 0); + if (!Flags.endSessionImprovements()) { + getFullMethodPathRecursive(sb, truncatePath, 0); + return sb.toString(); + } + // Check to see if the session has been renamed yet. If it has not, then the session + // has not been continued. + Session parentSession = getParentSession(); + boolean isSessionStarted = parentSession == null + || !getShortMethodName().equals(parentSession.getShortMethodName()); + int depth = 0; + Session currSession = this; + while (currSession != null) { + String cache = currSession.mFullMethodPathCache; + // Return cached value for method path. When returning the truncated path, recalculate + // the full path without using the cached value. + if (!TextUtils.isEmpty(cache) && !truncatePath) { + sb.insert(0, cache); + return sb.toString(); + } + + parentSession = currSession.getParentSession(); + // Encapsulate the external session's method name so it is obvious what part of the + // session is external or truncate it if we do not want the entire history. + if (currSession.isExternal()) { + if (truncatePath) { + sb.insert(0, TRUNCATE_STRING); + } else { + sb.insert(0, ")"); + sb.insert(0, currSession.getShortMethodName()); + sb.insert(0, "("); + } + } else { + sb.insert(0, currSession.getShortMethodName()); + } + if (parentSession != null) { + sb.insert(0, SUBSESSION_SEPARATION_CHAR); + } + + if (depth >= SESSION_RECURSION_LIMIT) { + // Don't use Telecom's Log.w here or it will cause infinite recursion because it + // will try to add session information to this logging statement, which will cause + // it to hit this condition again and so on... + android.util.Slog.w(LOG_TAG, "getFullMethodPath: Hit iteration limit!"); + sb.insert(0, TRUNCATE_STRING); + return sb.toString(); + } + currSession = parentSession; + depth++; + } + if (isSessionStarted && !truncatePath) { + // Cache the full method path for this node so that we do not need to calculate it + // again in the future. + mFullMethodPathCache = sb.toString(); + } return sb.toString(); } - private synchronized void getFullMethodPath(StringBuilder sb, boolean truncatePath, + private synchronized void getFullMethodPathRecursive(StringBuilder sb, boolean truncatePath, int parentCount) { if (parentCount >= SESSION_RECURSION_LIMIT) { // Don't use Telecom's Log.w here or it will cause infinite recursion because it will // try to add session information to this logging statement, which will cause it to hit // this condition again and so on... - android.util.Slog.w(LOG_TAG, "getFullMethodPath: Hit recursion limit!"); + android.util.Slog.w(LOG_TAG, "getFullMethodPathRecursive: Hit recursion limit!"); sb.append(TRUNCATE_STRING); return; } @@ -378,7 +486,7 @@ public class Session { // Check to see if the session has been renamed yet. If it has not, then the session // has not been continued. isSessionStarted = !mShortMethodName.equals(parentSession.mShortMethodName); - parentSession.getFullMethodPath(sb, truncatePath, parentCount + 1); + parentSession.getFullMethodPathRecursive(sb, truncatePath, parentCount + 1); sb.append(SUBSESSION_SEPARATION_CHAR); } // Encapsulate the external session's method name so it is obvious what part of the session @@ -409,14 +517,14 @@ public class Session { @Override public int hashCode() { - int result = mSessionId != null ? mSessionId.hashCode() : 0; - result = 31 * result + (mShortMethodName != null ? mShortMethodName.hashCode() : 0); - result = 31 * result + (int) (mExecutionStartTimeMs ^ (mExecutionStartTimeMs >>> 32)); - result = 31 * result + (int) (mExecutionEndTimeMs ^ (mExecutionEndTimeMs >>> 32)); + int result = mSessionId.hashCode(); + result = 31 * result + mShortMethodName.hashCode(); + result = 31 * result + Long.hashCode(mExecutionStartTimeMs); + result = 31 * result + Long.hashCode(mExecutionEndTimeMs); result = 31 * result + (mParentSession != null ? mParentSession.hashCode() : 0); - result = 31 * result + (mChildSessions != null ? mChildSessions.hashCode() : 0); + result = 31 * result + mChildSessions.hashCode(); result = 31 * result + (mIsCompleted ? 1 : 0); - result = 31 * result + mChildCounter; + result = 31 * result + mChildCounter.hashCode(); result = 31 * result + (mIsStartedFromActiveSession ? 1 : 0); result = 31 * result + (mOwnerInfo != null ? mOwnerInfo.hashCode() : 0); return result; @@ -432,23 +540,13 @@ public class Session { if (mExecutionStartTimeMs != session.mExecutionStartTimeMs) return false; if (mExecutionEndTimeMs != session.mExecutionEndTimeMs) return false; if (mIsCompleted != session.mIsCompleted) return false; - if (mChildCounter != session.mChildCounter) return false; + if (!(mChildCounter.get() == session.mChildCounter.get())) return false; if (mIsStartedFromActiveSession != session.mIsStartedFromActiveSession) return false; - if (mSessionId != null ? - !mSessionId.equals(session.mSessionId) : session.mSessionId != null) - return false; - if (mShortMethodName != null ? !mShortMethodName.equals(session.mShortMethodName) - : session.mShortMethodName != null) - return false; - if (mParentSession != null ? !mParentSession.equals(session.mParentSession) - : session.mParentSession != null) - return false; - if (mChildSessions != null ? !mChildSessions.equals(session.mChildSessions) - : session.mChildSessions != null) - return false; - return mOwnerInfo != null ? mOwnerInfo.equals(session.mOwnerInfo) - : session.mOwnerInfo == null; - + if (!Objects.equals(mSessionId, session.mSessionId)) return false; + if (!Objects.equals(mShortMethodName, session.mShortMethodName)) return false; + if (!Objects.equals(mParentSession, session.mParentSession)) return false; + if (!Objects.equals(mChildSessions, session.mChildSessions)) return false; + return Objects.equals(mOwnerInfo, session.mOwnerInfo); } @Override diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java index 9d17219c1ae4..00e344c67cc5 100644 --- a/telecomm/java/android/telecom/Logging/SessionManager.java +++ b/telecomm/java/android/telecom/Logging/SessionManager.java @@ -27,6 +27,7 @@ import android.telecom.Log; import android.util.Base64; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.telecom.flags.Flags; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -36,10 +37,16 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; /** - * TODO: Create better Sessions Documentation + * SessionManager manages the active sessions in a HashMap, which maps the active thread(s) to the + * associated {@link Session}s. + * <p> + * Note: Sessions assume that session structure modification is synchronized on this object - only + * one thread can modify the structure of any Session at one time. Printing the current session to + * the log is not synchronized because we should not clean up a session chain while printing from + * another Thread. Either the Session chain is still active and can not be cleaned up yet, or the + * Session chain has ended and we are cleaning up. * @hide */ - public class SessionManager { // Currently using 3 letters, So don't exceed 64^3 @@ -54,11 +61,11 @@ public class SessionManager { private Context mContext; @VisibleForTesting - public ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(100); + public final ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(64); @VisibleForTesting public java.lang.Runnable mCleanStaleSessions = () -> cleanupStaleSessions(getSessionCleanupTimeoutMs()); - private Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper()); + private final Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper()); // Overridden in LogTest to skip query to ContentProvider private interface ISessionCleanupTimeoutMs { @@ -83,7 +90,7 @@ public class SessionManager { }; // Usage is synchronized on this class. - private List<ISessionListener> mSessionListeners = new ArrayList<>(); + private final List<ISessionListener> mSessionListeners = new ArrayList<>(); public interface ISessionListener { /** @@ -110,10 +117,19 @@ public class SessionManager { } private synchronized void resetStaleSessionTimer() { - mSessionCleanupHandler.removeCallbacksAndMessages(null); - // Will be null in Log Testing - if (mCleanStaleSessions != null) { - mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs()); + if (!Flags.endSessionImprovements()) { + mSessionCleanupHandler.removeCallbacksAndMessages(null); + // Will be null in Log Testing + if (mCleanStaleSessions != null) { + mSessionCleanupHandler.postDelayed(mCleanStaleSessions, + getSessionCleanupTimeoutMs()); + } + } else { + if (mCleanStaleSessions != null + && !mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) { + mSessionCleanupHandler.postDelayed(mCleanStaleSessions, + getSessionCleanupTimeoutMs()); + } } } @@ -147,13 +163,11 @@ public class SessionManager { Session childSession = createSubsession(true); continueSession(childSession, shortMethodName); return; - } else { - // Only Log that we are starting the parent session. - Log.d(LOGGING_TAG, Session.START_SESSION); } Session newSession = new Session(getNextSessionID(), shortMethodName, - System.currentTimeMillis(), false, callerIdentification); + System.currentTimeMillis(), false, false, callerIdentification); mSessionMapper.put(threadId, newSession); + Log.d(LOGGING_TAG, Session.START_SESSION); } /** @@ -179,17 +193,16 @@ public class SessionManager { } // Create Session from Info and add to the sessionMapper under this ID. - Log.d(LOGGING_TAG, Session.START_EXTERNAL_SESSION); Session externalSession = new Session(Session.EXTERNAL_INDICATOR + sessionInfo.sessionId, - sessionInfo.methodPath, System.currentTimeMillis(), - false /*isStartedFromActiveSession*/, sessionInfo.ownerInfo); - externalSession.setIsExternal(true); + sessionInfo.methodPath, System.currentTimeMillis(), false, true, + sessionInfo.ownerInfo); // Mark the external session as already completed, since we have no way of knowing when // the external session actually has completed. externalSession.markSessionCompleted(Session.UNDEFINED); // Track the external session with the SessionMapper so that we can create and continue // an active subsession based on it. mSessionMapper.put(threadId, externalSession); + Log.d(LOGGING_TAG, Session.START_EXTERNAL_SESSION); // Create a subsession from this external Session parent node Session childSession = createSubsession(); continueSession(childSession, shortMethodName); @@ -226,13 +239,12 @@ public class SessionManager { // Start execution time of the session will be overwritten in continueSession(...). Session newSubsession = new Session(threadSession.getNextChildId(), threadSession.getShortMethodName(), System.currentTimeMillis(), - isStartedFromActiveSession, threadSession.getOwnerInfo()); + isStartedFromActiveSession, false, threadSession.getOwnerInfo()); threadSession.addChild(newSubsession); newSubsession.setParentSession(threadSession); if (!isStartedFromActiveSession) { - Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " " + - newSubsession.toString()); + Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION); } else { Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " (Invisible subsession)"); @@ -273,7 +285,7 @@ public class SessionManager { } subsession.markSessionCompleted(Session.UNDEFINED); - endParentSessions(subsession); + cleanupSessionTreeAndNotify(subsession); } /** @@ -328,7 +340,7 @@ public class SessionManager { // Remove after completed so that reference still exists for logging the end events Session parentSession = completedSession.getParentSession(); mSessionMapper.remove(threadId); - endParentSessions(completedSession); + cleanupSessionTreeAndNotify(completedSession); // If this subsession was started from a parent session using Log.startSession, return the // ThreadID back to the parent after completion. if (parentSession != null && !parentSession.isSessionCompleted() && @@ -337,8 +349,49 @@ public class SessionManager { } } + /** + * Move up the session tree and remove completed sessions until we either hit a session that was + * not completed yet or we reach the root node. Once we reach the root node, we will report the + * session times to session complete listeners. + * @param session The Session to clean up. + */ + private void cleanupSessionTreeAndNotify(Session session) { + if (session == null) return; + if (!Flags.endSessionImprovements()) { + endParentSessionsRecursive(session); + return; + } + Session currSession = session; + // Traverse upwards and unlink until we either hit the root node or a node that isn't + // complete yet. + while (currSession != null) { + if (!currSession.isSessionCompleted() || !currSession.getChildSessions().isEmpty()) { + // We will return once the active session is completed. + return; + } + Session parentSession = currSession.getParentSession(); + currSession.setParentSession(null); + // The session is either complete when we have reached the top node or we have reached + // the node where the parent is external. We only want to report the time it took to + // complete the local session, so for external nodes, report finished when the sub-node + // completes. + boolean reportSessionComplete = + (parentSession == null && !currSession.isExternal()) + || (parentSession != null && parentSession.isExternal()); + if (parentSession != null) parentSession.removeChild(currSession); + if (reportSessionComplete) { + long fullSessionTimeMs = System.currentTimeMillis() + - currSession.getExecutionStartTimeMilliseconds(); + Log.d(LOGGING_TAG, Session.END_SESSION + " (dur: " + fullSessionTimeMs + + " ms): " + currSession); + notifySessionCompleteListeners(currSession.getShortMethodName(), fullSessionTimeMs); + } + currSession = parentSession; + } + } + // Recursively deletes all complete parent sessions of the current subsession if it is a leaf. - private void endParentSessions(Session subsession) { + private void endParentSessionsRecursive(Session subsession) { // Session is not completed or not currently a leaf, so we can not remove because a child is // still running if (!subsession.isSessionCompleted() || subsession.getChildSessions().size() != 0) { @@ -355,7 +408,7 @@ public class SessionManager { System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds(); notifySessionCompleteListeners(subsession.getShortMethodName(), fullSessionTimeMs); } - endParentSessions(parentSession); + endParentSessionsRecursive(parentSession); } else { // All of the subsessions have been completed and it is time to report on the full // running time of the session. @@ -370,8 +423,10 @@ public class SessionManager { } private void notifySessionCompleteListeners(String methodName, long sessionTimeMs) { - for (ISessionListener l : mSessionListeners) { - l.sessionComplete(methodName, sessionTimeMs); + synchronized (mSessionListeners) { + for (ISessionListener l : mSessionListeners) { + l.sessionComplete(methodName, sessionTimeMs); + } } } @@ -380,8 +435,8 @@ public class SessionManager { return currentSession != null ? currentSession.toString() : ""; } - public synchronized void registerSessionListener(ISessionListener l) { - if (l != null) { + public void registerSessionListener(ISessionListener l) { + synchronized (mSessionListeners) { mSessionListeners.add(l); } } @@ -425,25 +480,30 @@ public class SessionManager { @VisibleForTesting public synchronized void cleanupStaleSessions(long timeoutMs) { - String logMessage = "Stale Sessions Cleaned:\n"; + StringBuilder logMessage = new StringBuilder("Stale Sessions Cleaned:"); boolean isSessionsStale = false; long currentTimeMs = System.currentTimeMillis(); // Remove references that are in the Session Mapper (causing GC to occur) on - // sessions that are lasting longer than LOGGING_SESSION_TIMEOUT_MS. + // sessions that are lasting longer than DEFAULT_SESSION_TIMEOUT_MS. // If this occurs, then there is most likely a Session active that never had // Log.endSession called on it. for (Iterator<ConcurrentHashMap.Entry<Integer, Session>> it = mSessionMapper.entrySet().iterator(); it.hasNext(); ) { ConcurrentHashMap.Entry<Integer, Session> entry = it.next(); Session session = entry.getValue(); - if (currentTimeMs - session.getExecutionStartTimeMilliseconds() > timeoutMs) { + long runTime = currentTimeMs - session.getExecutionStartTimeMilliseconds(); + if (runTime > timeoutMs) { it.remove(); - logMessage += session.printFullSessionTree() + "\n"; + logMessage.append("\n"); + logMessage.append("["); + logMessage.append(runTime); + logMessage.append("ms] "); + logMessage.append(session.printFullSessionTree()); isSessionsStale = true; } } if (isSessionsStale) { - Log.w(LOGGING_TAG, logMessage); + Log.w(LOGGING_TAG, logMessage.toString()); } else { Log.v(LOGGING_TAG, "No stale logging sessions needed to be cleaned..."); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 47f6764dba98..02999c81250a 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -253,7 +253,6 @@ public class CarrierConfigManager { * * The default value is true. */ - @FlaggedApi(Flags.FLAG_SHOW_CALL_ID_AND_CALL_WAITING_IN_ADDITIONAL_SETTINGS_MENU) public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL = "additional_settings_caller_id_visibility_bool"; @@ -263,7 +262,6 @@ public class CarrierConfigManager { * * The default value is true. */ - @FlaggedApi(Flags.FLAG_SHOW_CALL_ID_AND_CALL_WAITING_IN_ADDITIONAL_SETTINGS_MENU) public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL = "additional_settings_call_waiting_visibility_bool"; @@ -10558,7 +10556,6 @@ public class CarrierConfigManager { * @see SubscriptionInfo#getServiceCapabilities() * @see SubscriptionManager.OnSubscriptionsChangedListener */ - @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY = "cellular_service_capabilities_int_array"; /** @@ -11110,7 +11107,7 @@ public class CarrierConfigManager { sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, ""); sDefaults.putInt(KEY_NR_ADVANCED_BANDS_SECONDARY_TIMER_SECONDS_INT, 0); sDefaults.putBoolean(KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL, false); - sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, true); + sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, false); sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_PLMN_CHANGE_BOOL, false); /* Default value is 1 hour. */ sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000); diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 1089602934fd..d164c8851f5b 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -951,7 +951,6 @@ public class SubscriptionInfo implements Parcelable { * @see SubscriptionManager#SERVICE_CAPABILITY_DATA */ @NonNull - @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) public @SubscriptionManager.ServiceCapability Set<Integer> getServiceCapabilities() { return SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities); } @@ -1829,7 +1828,6 @@ public class SubscriptionInfo implements Parcelable { * @throws IllegalArgumentException when any capability is not supported. */ @NonNull - @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) public Builder setServiceCapabilities( @NonNull @SubscriptionManager.ServiceCapability Set<Integer> capabilities) { int combinedCapabilities = 0; diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 6faef7ecfa1b..377e5f2e7db2 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1422,7 +1422,6 @@ public class SubscriptionManager { * * @see TelephonyManager#isDeviceVoiceCapable() */ - @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) public static final int SERVICE_CAPABILITY_VOICE = 1; /** @@ -1440,13 +1439,11 @@ public class SubscriptionManager { * * @see TelephonyManager#isDeviceSmsCapable() */ - @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) public static final int SERVICE_CAPABILITY_SMS = 2; /** * Represents a value indicating the data calling capabilities of a subscription. */ - @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) public static final int SERVICE_CAPABILITY_DATA = 3; /** @@ -3474,14 +3471,62 @@ public class SubscriptionManager { @SystemApi public boolean canManageSubscription(@NonNull SubscriptionInfo info, @NonNull String packageName) { + if (Flags.hsumPackageManager()) { + return canManageSubscriptionAsUser(info, packageName, mContext.getUser()); + } else { + if (info == null || info.getAccessRules() == null || packageName == null) { + return false; + } + PackageManager packageManager = mContext.getPackageManager(); + PackageInfo packageInfo; + try { + packageInfo = packageManager.getPackageInfo(packageName, + PackageManager.GET_SIGNING_CERTIFICATES); + } catch (PackageManager.NameNotFoundException e) { + logd("Unknown package: " + packageName); + return false; + } + for (UiccAccessRule rule : info.getAccessRules()) { + if (rule.getCarrierPrivilegeStatus(packageInfo) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return true; + } + } + return false; + } + } + + /** + * Checks whether the given app is authorized to manage the given subscription for given user. + * + * <p>An app can only be authorized if it is available to the given user and included in the + * {@link android.telephony.UiccAccessRule} of the {@link android.telephony.SubscriptionInfo} + * with the access status. + * + * <p>Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns + * true). To check for permissions for non-embedded subscription as well, + * see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}. + * + * @param info The subscription to check. + * @param packageName Package name of the app to check. + * @param user UserHandle to check + * @return whether the app is authorized to manage this subscription per its access rules. + * + * @see android.telephony.TelephonyManager#hasCarrierPrivileges + * @hide + */ + public boolean canManageSubscriptionAsUser(@NonNull SubscriptionInfo info, + @NonNull String packageName, @NonNull UserHandle user) { if (info == null || info.getAccessRules() == null || packageName == null) { return false; } - PackageManager packageManager = mContext.getPackageManager(); + PackageManager pm = mContext.getUser().equals(user) + ? mContext.getPackageManager() + : mContext.createContextAsUser(user, 0).getPackageManager(); PackageInfo packageInfo; try { - packageInfo = packageManager.getPackageInfo(packageName, - PackageManager.GET_SIGNING_CERTIFICATES); + packageInfo = pm.getPackageInfo(packageName, + PackageManager.GET_SIGNING_CERTIFICATES); } catch (PackageManager.NameNotFoundException e) { logd("Unknown package: " + packageName); return false; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index fee45873502b..a7fe0cb0940c 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -2127,7 +2127,6 @@ public class TelephonyManager { * <p>On some devices, this settings activity may not exist. Callers should ensure that this * case is appropriately handled. */ - @FlaggedApi(Flags.FLAG_RESET_MOBILE_NETWORK_SETTINGS) @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS = "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS"; @@ -6934,7 +6933,6 @@ public class TelephonyManager { * * @see SubscriptionInfo#getServiceCapabilities() */ - @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) public boolean isDeviceVoiceCapable() { return isVoiceCapable(); } @@ -6974,7 +6972,6 @@ public class TelephonyManager { * * @see SubscriptionInfo#getServiceCapabilities() */ - @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) public boolean isDeviceSmsCapable() { return isSmsCapable(); } @@ -8781,13 +8778,14 @@ public class TelephonyManager { * Authentication error, no memory space available in EFMUK * * @throws UnsupportedOperationException If the device does not have - * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}. + * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION} or doesn't support given + * authType. */ // TODO(b/73660190): This should probably require MODIFY_PHONE_STATE, not // READ_PRIVILEGED_PHONE_STATE. It certainly shouldn't reference the permission in Javadoc since // it's not public API. @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) - public String getIccAuthentication(int appType,@AuthType int authType, String data) { + public String getIccAuthentication(int appType, @AuthType int authType, String data) { return getIccAuthentication(getSubId(), appType, authType, data); } @@ -8812,10 +8810,14 @@ public class TelephonyManager { * Key freshness failure * Authentication error, no memory space available * Authentication error, no memory space available in EFMUK + * @throws UnsupportedOperationException If the device does not have + * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION} or doesn't support given + * authType. * @hide */ @UnsupportedAppUsage - public String getIccAuthentication(int subId, int appType,@AuthType int authType, String data) { + public String getIccAuthentication(int subId, int appType, @AuthType int authType, + String data) { try { IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index f2e34257ef01..9ce8e807f612 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -352,7 +352,8 @@ android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity" android:theme="@style/CutoutShortEdges" android:label="SplitScreenPrimaryActivity" - android:exported="true"> + android:exported="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -363,7 +364,8 @@ android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity" android:theme="@style/CutoutShortEdges" android:label="SplitScreenSecondaryActivity" - android:exported="true"> + android:exported="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java index 0375f66069c3..d9295dd17dd0 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -515,33 +515,27 @@ public class StagedInstallInternalTest { Install.single(APEX_V2)); } - @Test - public void testGetStagedModuleNames() throws Exception { - // Before staging a session - String[] result = getPackageManagerNative().getStagedApexModuleNames(); - assertThat(result).hasLength(0); - // Stage an apex - int sessionId = Install.single(APEX_V2).setStaged().commit(); - result = getPackageManagerNative().getStagedApexModuleNames(); - assertThat(result).hasLength(1); - assertThat(result).isEqualTo(new String[]{SHIM_APEX_PACKAGE_NAME}); - // Abandon the session - InstallUtils.openPackageInstallerSession(sessionId).abandon(); - result = getPackageManagerNative().getStagedApexModuleNames(); - assertThat(result).hasLength(0); + private StagedApexInfo findStagedApexInfo(StagedApexInfo[] infos, String moduleName) { + for (StagedApexInfo info: infos) { + if (info.moduleName.equals(moduleName)) { + return info; + } + } + return null; } @Test - public void testGetStagedApexInfo() throws Exception { - // Ask for non-existing module - StagedApexInfo result = getPackageManagerNative().getStagedApexInfo("not found"); - assertThat(result).isNull(); + public void testGetStagedApexInfos() throws Exception { + // Not found before staging + StagedApexInfo[] result = getPackageManagerNative().getStagedApexInfos(); + assertThat(findStagedApexInfo(result, TEST_APEX_PACKAGE_NAME)).isNull(); // Stage an apex int sessionId = Install.single(TEST_APEX_CLASSPATH).setStaged().commit(); // Query proper module name - result = getPackageManagerNative().getStagedApexInfo(TEST_APEX_PACKAGE_NAME); - assertThat(result.moduleName).isEqualTo(TEST_APEX_PACKAGE_NAME); - assertThat(result.hasClassPathJars).isTrue(); + result = getPackageManagerNative().getStagedApexInfos(); + StagedApexInfo found = findStagedApexInfo(result, TEST_APEX_PACKAGE_NAME); + assertThat(found).isNotNull(); + assertThat(found.hasClassPathJars).isTrue(); InstallUtils.openPackageInstallerSession(sessionId).abandon(); } @@ -573,14 +567,15 @@ public class StagedInstallInternalTest { int sessionId = Install.single(APEX_V2).setStaged().commit(); ArgumentCaptor<ApexStagedEvent> captor = ArgumentCaptor.forClass(ApexStagedEvent.class); verify(observer, timeout(5000)).onApexStaged(captor.capture()); - assertThat(captor.getValue().stagedApexModuleNames).isEqualTo( - new String[] {SHIM_APEX_PACKAGE_NAME}); + StagedApexInfo found = + findStagedApexInfo(captor.getValue().stagedApexInfos, SHIM_APEX_PACKAGE_NAME); + assertThat(found).isNotNull(); // Abandon and verify observer is called Mockito.clearInvocations(observer); InstallUtils.openPackageInstallerSession(sessionId).abandon(); verify(observer, timeout(5000)).onApexStaged(captor.capture()); - assertThat(captor.getValue().stagedApexModuleNames).hasLength(0); + assertThat(captor.getValue().stagedApexInfos).hasLength(0); } @Test diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java index f1fc503c8556..97abcd77e6b9 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -592,23 +592,15 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } @Test - public void testGetStagedModuleNames() throws Exception { - assumeTrue("Device does not support updating APEX", - mHostUtils.isApexUpdateSupported()); - - runPhase("testGetStagedModuleNames"); - } - - @Test @LargeTest - public void testGetStagedApexInfo() throws Exception { + public void testGetStagedApexInfos() throws Exception { assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); pushTestApex(APEXD_TEST_APEX); getDevice().reboot(); - runPhase("testGetStagedApexInfo"); + runPhase("testGetStagedApexInfos"); } @Test diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 4cb7c91b2451..7e0bbc4b3e50 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -70,7 +70,6 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.Uri; -import android.net.vcn.Flags; import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; @@ -85,7 +84,6 @@ import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.os.test.TestLooper; -import android.platform.test.flag.junit.SetFlagsRule; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -104,7 +102,6 @@ import com.android.server.vcn.util.PersistableBundleUtils; import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -122,8 +119,6 @@ import java.util.UUID; @RunWith(AndroidJUnit4.class) @SmallTest public class VcnManagementServiceTest { - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - private static final String CONTEXT_ATTRIBUTION_TAG = "VCN"; private static final String TEST_PACKAGE_NAME = VcnManagementServiceTest.class.getPackage().getName(); @@ -285,8 +280,6 @@ public class VcnManagementServiceTest { @Before public void setUp() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_MAIN_USER); - doNothing() .when(mMockContext) .enforceCallingOrSelfPermission( diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java index edad67896e8e..421e1ad20b78 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java @@ -34,14 +34,12 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.TelephonyNetworkSpecifier; import android.net.vcn.FeatureFlags; -import android.net.vcn.Flags; import android.os.Handler; import android.os.IPowerManager; import android.os.IThermalService; import android.os.ParcelUuid; import android.os.PowerManager; import android.os.test.TestLooper; -import android.platform.test.flag.junit.SetFlagsRule; import android.telephony.TelephonyManager; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; @@ -49,7 +47,6 @@ import com.android.server.vcn.VcnContext; import com.android.server.vcn.VcnNetworkProvider; import org.junit.Before; -import org.junit.Rule; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -57,8 +54,6 @@ import java.util.Set; import java.util.UUID; public abstract class NetworkEvaluationTestBase { - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - protected static final String SSID = "TestWifi"; protected static final String SSID_OTHER = "TestWifiOther"; protected static final String PLMN_ID = "123456"; @@ -120,10 +115,6 @@ public abstract class NetworkEvaluationTestBase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS); - mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE); - mSetFlagsRule.enableFlags(Flags.FLAG_HANDLE_SEQ_NUM_LEAP); - when(mNetwork.getNetId()).thenReturn(-1); mTestLooper = new TestLooper(); diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index 4920f7b41e3f..a5ff4964b0a4 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -8,7 +8,7 @@ package { // OWNER: g/ravenwood // Bug component: 25698 - default_team: "trendy_team_framework_backstage_power", + default_team: "trendy_team_ravenwood", } // Visibility only for ravenwood prototype uses. |