diff options
380 files changed, 7443 insertions, 3486 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 56831d70af3a..8a69e86ce5f8 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -652,6 +652,8 @@ java_aconfig_library { min_sdk_version: "30", apex_available: [ "//apex_available:platform", + "com.android.art", + "com.android.art.debug", "com.android.permission", ], } @@ -1205,6 +1207,7 @@ aconfig_declarations { name: "device_policy_aconfig_flags", package: "android.app.admin.flags", container: "system", + exportable: true, srcs: [ "core/java/android/app/admin/flags/flags.aconfig", ], @@ -1217,6 +1220,18 @@ java_aconfig_library { } java_aconfig_library { + name: "device_policy_aconfig_flags_java_export", + aconfig_declarations: "device_policy_aconfig_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + mode: "exported", + min_sdk_version: "30", + apex_available: [ + "//apex_available:platform", + "com.android.permission", + ], +} + +java_aconfig_library { name: "device_policy_aconfig_flags_lib_host", aconfig_declarations: "device_policy_aconfig_flags", host_supported: true, diff --git a/Android.bp b/Android.bp index 424a4a71ce40..42028e010e84 100644 --- a/Android.bp +++ b/Android.bp @@ -83,7 +83,6 @@ filegroup { ":framework-telecomm-sources", ":framework-telephony-common-sources", ":framework-telephony-sources", - ":framework-vcn-util-sources", ":framework-wifi-annotations", ":framework-wifi-non-updatable-sources", ":PacProcessor-aidl-sources", @@ -313,7 +312,6 @@ java_defaults { ":framework-telecomm-sources", ":framework-telephony-common-sources", ":framework-telephony-sources", - ":framework-vcn-util-sources", ":framework-wifi-annotations", ":framework-wifi-non-updatable-sources", ":PacProcessor-aidl-sources", @@ -371,6 +369,7 @@ java_defaults { "view-inspector-annotation-processor", "staledataclass-annotation-processor", "error_prone_android_framework", + "systemfeatures-metadata-processor", ], // Exports needed for staledataclass-annotation-processor, see b/139342589. javacflags: [ @@ -597,7 +596,7 @@ filegroup { srcs: [ "core/java/com/android/internal/util/HexDump.java", "core/java/com/android/internal/util/WakeupMessage.java", - "services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java", + "core/java/android/net/vcn/util/PersistableBundleUtils.java", "telephony/java/android/telephony/Annotation.java", ], } diff --git a/core/api/current.txt b/core/api/current.txt index 6aef9dd83085..4a1628e9ce39 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -20768,6 +20768,7 @@ package android.hardware.display { public final class VirtualDisplayConfig implements android.os.Parcelable { method public int describeContents(); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDefaultBrightness(); method public int getDensityDpi(); method @NonNull public java.util.Set<java.lang.String> getDisplayCategories(); method public int getFlags(); @@ -20780,10 +20781,16 @@ package android.hardware.display { field @NonNull public static final android.os.Parcelable.Creator<android.hardware.display.VirtualDisplayConfig> CREATOR; } + @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public static interface VirtualDisplayConfig.BrightnessListener { + method public void onBrightnessChanged(@FloatRange(from=0.0f, to=1.0f) float); + } + public static final class VirtualDisplayConfig.Builder { ctor public VirtualDisplayConfig.Builder(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int); method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder addDisplayCategory(@NonNull String); method @NonNull public android.hardware.display.VirtualDisplayConfig build(); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setBrightnessListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.display.VirtualDisplayConfig.BrightnessListener); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDefaultBrightness(@FloatRange(from=0.0f, to=1.0f) float); method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCategories(@NonNull java.util.Set<java.lang.String>); method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setFlags(int); method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setRequestedRefreshRate(@FloatRange(from=0.0f) float); @@ -24854,6 +24861,7 @@ package android.media { method @NonNull public String getId(); method @NonNull public CharSequence getName(); method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus(); + method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public int getSupportedRoutingTypes(); method public int getType(); method public int getVolume(); method public int getVolumeHandling(); @@ -24869,6 +24877,8 @@ package android.media { field public static final String FEATURE_REMOTE_AUDIO_PLAYBACK = "android.media.route.feature.REMOTE_AUDIO_PLAYBACK"; field public static final String FEATURE_REMOTE_PLAYBACK = "android.media.route.feature.REMOTE_PLAYBACK"; field public static final String FEATURE_REMOTE_VIDEO_PLAYBACK = "android.media.route.feature.REMOTE_VIDEO_PLAYBACK"; + field @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final int FLAG_ROUTING_TYPE_REMOTE = 4; // 0x4 + field @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final int FLAG_ROUTING_TYPE_SYSTEM_AUDIO = 1; // 0x1 field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0 field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1 field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2; // 0x2 @@ -24919,6 +24929,7 @@ package android.media { method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle); method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri); method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int); + method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") @NonNull public android.media.MediaRoute2Info.Builder setSupportedRoutingTypes(int); method @NonNull public android.media.MediaRoute2Info.Builder setType(int); method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic(); method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>); @@ -33613,14 +33624,12 @@ package android.os { @FlaggedApi("android.os.cpu_gpu_headrooms") public final class CpuHeadroomParams { ctor public CpuHeadroomParams(); method public int getCalculationType(); - method @IntRange(from=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) public long getCalculationWindowMillis(); + method @IntRange(from=0x32, to=0x2710) public long getCalculationWindowMillis(); method public void setCalculationType(int); - method public void setCalculationWindowMillis(@IntRange(from=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int); + method public void setCalculationWindowMillis(@IntRange(from=0x32, to=0x2710) int); method public void setTids(@NonNull int...); field public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1 field public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0 - field public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; // 0x2710 - field public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; // 0x32 } public final class CpuUsageInfo implements android.os.Parcelable { @@ -33873,13 +33882,11 @@ package android.os { @FlaggedApi("android.os.cpu_gpu_headrooms") public final class GpuHeadroomParams { ctor public GpuHeadroomParams(); method public int getCalculationType(); - method @IntRange(from=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) public int getCalculationWindowMillis(); + method @IntRange(from=0x32, to=0x2710) public int getCalculationWindowMillis(); method public void setCalculationType(int); - method public void setCalculationWindowMillis(@IntRange(from=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int); + method public void setCalculationWindowMillis(@IntRange(from=0x32, to=0x2710) int); field public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1 field public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0 - field public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; // 0x2710 - field public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; // 0x32 } public class Handler { @@ -37616,6 +37623,10 @@ package android.provider { field public static final String TERTIARY_PHONE_TYPE = "tertiary_phone_type"; } + @FlaggedApi("android.provider.new_default_account_api_enabled") public static class ContactsContract.LocalSimContactsWriteException extends java.lang.IllegalArgumentException { + ctor public ContactsContract.LocalSimContactsWriteException(@NonNull String); + } + public static final class ContactsContract.PhoneLookup implements android.provider.BaseColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.PhoneLookupColumns { field public static final android.net.Uri CONTENT_FILTER_URI; field public static final android.net.Uri ENTERPRISE_CONTENT_FILTER_URI; diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 1a949d84c052..d42201d40ece 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -129,6 +129,7 @@ package android.content.pm { public abstract class PackageManager { method @NonNull public String getSdkSandboxPackageName(); + method @FlaggedApi("android.content.pm.cloud_compilation_pm") @NonNull public static android.content.pm.SigningInfo getVerifiedSigningInfo(@NonNull String, int) throws android.content.pm.SigningInfoException; method @RequiresPermission(android.Manifest.permission.MAKE_UID_VISIBLE) public void makeUidVisible(int, int); field public static final String EXTRA_VERIFICATION_ROOT_HASH = "android.content.pm.extra.VERIFICATION_ROOT_HASH"; field public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 67108864; // 0x4000000 @@ -139,6 +140,18 @@ package android.content.pm { method @NonNull public String getPackageName(); } + public final class SigningInfo implements android.os.Parcelable { + method @FlaggedApi("android.content.pm.cloud_compilation_pm") public boolean signersMatchExactly(@NonNull android.content.pm.SigningInfo); + field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_JAR = 1; // 0x1 + field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_SIGNING_BLOCK_V2 = 2; // 0x2 + field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_SIGNING_BLOCK_V3 = 3; // 0x3 + field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_SIGNING_BLOCK_V4 = 4; // 0x4 + } + + @FlaggedApi("android.content.pm.cloud_compilation_pm") public class SigningInfoException extends java.lang.Exception { + method @FlaggedApi("android.content.pm.cloud_compilation_pm") public int getCode(); + } + } package android.hardware.usb { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 83699ac30939..7bfa878a7c0b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3543,6 +3543,7 @@ package android.companion.virtual { public static interface VirtualDeviceManager.ActivityListener { method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onActivityLaunchBlocked(int, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle, @Nullable android.content.IntentSender); method public void onDisplayEmpty(int); + method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onSecureWindowHidden(int); method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onSecureWindowShown(int, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle); method @Deprecated public void onTopActivityChanged(int, @NonNull android.content.ComponentName); method public default void onTopActivityChanged(int, @NonNull android.content.ComponentName, int); @@ -5455,19 +5456,13 @@ package android.hardware.display { field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400 } - public abstract static class VirtualDisplay.Callback { - method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public void onRequestedBrightnessChanged(@FloatRange(from=0.0f, to=1.0f) float); - } - public final class VirtualDisplayConfig implements android.os.Parcelable { - method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDefaultBrightness(); method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @Nullable public android.view.DisplayCutout getDisplayCutout(); method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") public boolean isHomeSupported(); method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") public boolean isIgnoreActivitySizeRestrictions(); } public static final class VirtualDisplayConfig.Builder { - method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDefaultBrightness(@FloatRange(from=0.0f, to=1.0f) float); method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCutout(@Nullable android.view.DisplayCutout); method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setHomeSupported(boolean); method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setIgnoreActivitySizeRestrictions(boolean); @@ -7779,7 +7774,7 @@ package android.media { } public final class MediaCas implements java.lang.AutoCloseable { - method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean); + method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceOwnershipRetention(boolean); method @FlaggedApi("android.media.tv.flags.mediacas_update_client_profile_priority") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean updateResourcePriority(int, int); } @@ -8692,8 +8687,8 @@ package android.media.tv.tuner { method public int setLnaEnabled(boolean); method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int); method public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener); - method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean); method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener); + method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceOwnershipRetention(boolean); method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner); method public int transferOwner(@NonNull android.media.tv.tuner.Tuner); method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings); diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index d1eb8e8fa2ff..4bf87f91cb2f 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -141,6 +141,14 @@ public abstract class Animator implements Cloneable { } /** + * @see #sPostNotifyEndListenerEnabled + * @hide + */ + public static boolean isPostNotifyEndListenerEnabled() { + return sPostNotifyEndListenerEnabled; + } + + /** * Starts this animation. If the animation has a nonzero startDelay, the animation will start * running after that delay elapses. A non-delayed animation will have its initial * value(s) set immediately, followed by calls to diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 0668958b2d5c..50486611a5a9 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -1028,4 +1028,14 @@ interface IActivityManager { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)") void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType, boolean enabled, int reason, in String subReason, int source, long threshold); + + /** + * Creates and returns a new IntentCreatorToken that keeps the creatorUid and refreshes key + * fields of the intent passed in. + * + * @param intent The intent with key fields out of sync of the IntentCreatorToken it contains. + * @hide + */ + @EnforcePermission("INTERACT_ACROSS_USERS_FULL") + IBinder refreshIntentCreatorToken(in Intent intent); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index e2d20cb4b7dc..0268b5ba2ae3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -821,14 +821,14 @@ public class Notification implements Parcelable R.layout.notification_2025_template_collapsed_call, R.layout.notification_2025_template_expanded_call, R.layout.notification_2025_template_collapsed_messaging, + R.layout.notification_2025_template_expanded_messaging, R.layout.notification_2025_template_collapsed_media, - R.layout.notification_template_material_big_picture, - R.layout.notification_template_material_big_text, - R.layout.notification_template_material_inbox, - R.layout.notification_template_material_big_messaging, - R.layout.notification_template_material_big_media, - R.layout.notification_template_header -> true; - case R.layout.notification_template_material_progress -> Flags.apiRichOngoing(); + R.layout.notification_2025_template_expanded_media, + R.layout.notification_2025_template_expanded_big_picture, + R.layout.notification_2025_template_expanded_big_text, + R.layout.notification_2025_template_expanded_inbox -> true; + case R.layout.notification_2025_template_expanded_progress + -> Flags.apiRichOngoing(); default -> false; }; } @@ -5964,7 +5964,7 @@ public class Notification implements Parcelable private static void setHeaderlessVerticalMargins(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine) { - if (!p.mHeaderless) { + if (Flags.notificationsRedesignTemplates() || !p.mHeaderless) { return; } int marginDimen = hasSecondLine @@ -6445,10 +6445,13 @@ public class Notification implements Parcelable // Clear view padding to allow buttons to start on the left edge. // This must be done before 'setEmphasizedMode' which sets top/bottom margins. big.setViewPadding(R.id.actions, 0, 0, 0, 0); - // Add an optional indent that will make buttons start at the correct column when - // there is enough space to do so (and fall back to the left edge if not). - big.setInt(R.id.actions, "setCollapsibleIndentDimen", - R.dimen.call_notification_collapsible_indent); + if (!Flags.notificationsRedesignTemplates()) { + // Add an optional indent that will make buttons start at the correct column + // when there is enough space to do so (and fall back to the left edge if not). + // This is handled directly in NotificationActionListLayout in the new design. + big.setInt(R.id.actions, "setCollapsibleIndentDimen", + R.dimen.call_notification_collapsible_indent); + } if (evenlyDividedCallStyleActionLayout()) { if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) { Log.d(TAG, "setting evenly divided mode on action list"); @@ -7561,15 +7564,27 @@ public class Notification implements Parcelable } private int getBigPictureLayoutResource() { - return R.layout.notification_template_material_big_picture; + if (Flags.notificationsRedesignTemplates()) { + return R.layout.notification_2025_template_expanded_big_picture; + } else { + return R.layout.notification_template_material_big_picture; + } } private int getBigTextLayoutResource() { - return R.layout.notification_template_material_big_text; + if (Flags.notificationsRedesignTemplates()) { + return R.layout.notification_2025_template_expanded_big_text; + } else { + return R.layout.notification_template_material_big_text; + } } private int getInboxLayoutResource() { - return R.layout.notification_template_material_inbox; + if (Flags.notificationsRedesignTemplates()) { + return R.layout.notification_2025_template_expanded_inbox; + } else { + return R.layout.notification_template_material_inbox; + } } private int getCollapsedMessagingLayoutResource() { @@ -7581,7 +7596,11 @@ public class Notification implements Parcelable } private int getExpandedMessagingLayoutResource() { - return R.layout.notification_template_material_big_messaging; + if (Flags.notificationsRedesignTemplates()) { + return R.layout.notification_2025_template_expanded_messaging; + } else { + return R.layout.notification_template_material_big_messaging; + } } private int getCollapsedMediaLayoutResource() { @@ -7592,6 +7611,14 @@ public class Notification implements Parcelable } } + private int getExpandedMediaLayoutResource() { + if (Flags.notificationsRedesignTemplates()) { + return R.layout.notification_2025_template_expanded_media; + } else { + return R.layout.notification_template_material_big_media; + } + } + private int getConversationLayoutResource() { if (Flags.notificationsRedesignTemplates()) { return R.layout.notification_2025_template_conversation; @@ -7617,7 +7644,11 @@ public class Notification implements Parcelable } private int getProgressLayoutResource() { - return R.layout.notification_template_material_progress; + if (Flags.notificationsRedesignTemplates()) { + return R.layout.notification_2025_template_expanded_progress; + } else { + return R.layout.notification_template_material_progress; + } } private int getActionLayoutResource() { @@ -10541,7 +10572,7 @@ public class Notification implements Parcelable .fillTextsFrom(mBuilder); TemplateBindResult result = new TemplateBindResult(); RemoteViews template = mBuilder.applyStandardTemplate( - R.layout.notification_template_material_big_media, p , result); + mBuilder.getExpandedMediaLayoutResource(), p , result); for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) { if (i < actionCount) { diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 581efa5d2efa..686c830646f2 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -387,6 +387,7 @@ flag { flag { name: "split_create_managed_profile_enabled" + is_exported: true namespace: "enterprise" description: "Split up existing create and provision managed profile API." bug: "375382324" diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java index cbd1d932ab00..c8d80d3afe43 100644 --- a/core/java/android/app/appfunctions/AppFunctionException.java +++ b/core/java/android/app/appfunctions/AppFunctionException.java @@ -29,7 +29,14 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; -/** Represents an app function related errors. */ +/** + * Represents an app function related error. + * + * <p>This exception may include an {@link AppFunctionException#getExtras() Bundle} + * containing additional error-specific metadata. + * + * <p>The AppFunction SDK can expose structured APIs by packing and unpacking this Bundle. + */ @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) public final class AppFunctionException extends Exception implements Parcelable { /** diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java index 1557815a8468..a88198a4ec7c 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java @@ -27,7 +27,16 @@ import android.os.Parcelable; import java.util.Objects; -/** A request to execute an app function. */ +/** + * A request to execute an app function. + * + * <p>The {@link ExecuteAppFunctionRequest#getParameters()} contains the parameters for the function + * to be executed in a GenericDocument. Structured classes defined in the AppFunction SDK can be + * converted into GenericDocuments. + * + * <p>The {@link ExecuteAppFunctionRequest#getExtras()} provides any extra metadata for the request. + * Structured APIs can be exposed in the SDK by packing and unpacking this Bundle. + */ @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) public final class ExecuteAppFunctionRequest implements Parcelable { @NonNull diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java index acad43b782e5..a4952f486059 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java @@ -27,7 +27,16 @@ import android.os.Parcelable; import java.util.Objects; -/** The response to an app function execution. */ +/** + * The response to an app function execution. + * + * <p>The {@link ExecuteAppFunctionResponse#getResultDocument()} contains the function's return + * value as a GenericDocument. This can be converted back into a structured class using the + * AppFunction SDK. + * + * <p>The {@link ExecuteAppFunctionResponse#getExtras()} provides any extra metadata returned by the + * function. The AppFunction SDK can expose structured APIs by packing and unpacking this Bundle. + */ @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) public final class ExecuteAppFunctionResponse implements Parcelable { @NonNull diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 367f1afc912b..f8ac27de1754 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -90,6 +90,12 @@ interface IVirtualDevice { */ boolean hasCustomAudioInputSupport(); + /** + * Returns whether this device is allowed to create mirror displays. + */ + boolean canCreateMirrorDisplays(); + + /* /* * Turns off all trusted non-mirror displays of the virtual device. */ diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl index 767f52a92566..448793d12bcb 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl @@ -63,4 +63,11 @@ oneway interface IVirtualDeviceActivityListener { * @param user The user associated with the activity. */ void onSecureWindowShown(int displayId, in ComponentName componentName, in UserHandle user); + + /** + * Called when a secure surface is no longer shown on the device. + * + * @param displayId The display ID on which the secure surface was shown. + */ + void onSecureWindowHidden(int displayId); } diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java index d63a4434d7d8..42c74414ecd9 100644 --- a/core/java/android/companion/virtual/VirtualDeviceInternal.java +++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java @@ -166,6 +166,20 @@ public class VirtualDeviceInternal { Binder.restoreCallingIdentity(token); } } + + @Override + public void onSecureWindowHidden(int displayId) { + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mActivityListenersLock) { + for (int i = 0; i < mActivityListeners.size(); i++) { + mActivityListeners.valueAt(i).onSecureWindowHidden(displayId); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } }; private final IVirtualDeviceSoundEffectListener mSoundEffectListener = @@ -617,6 +631,10 @@ public class VirtualDeviceInternal { mExecutor.execute(() -> mActivityListener.onSecureWindowShown(displayId, componentName, user)); } + + public void onSecureWindowHidden(int displayId) { + mExecutor.execute(() -> mActivityListener.onSecureWindowHidden(displayId)); + } } /** diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 6ea7834243a4..b3f09a98d623 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -1288,6 +1288,17 @@ public final class VirtualDeviceManager { @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API) default void onSecureWindowShown(int displayId, @NonNull ComponentName componentName, @NonNull UserHandle user) {} + + /** + * Called when a window with a secure surface is no longer shown on the device. + * + * @param displayId The display ID on which the window was shown before. + * + * @see Display#FLAG_SECURE + * @see WindowManager.LayoutParams#FLAG_SECURE + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API) + default void onSecureWindowHidden(int displayId) {} } /** diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index cc57dc05d6b1..e271cf4f60ec 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -946,6 +946,34 @@ public class ClipData implements Parcelable { } /** + * Make a clone of ClipData that only contains URIs. This reduces the size of data transfer over + * IPC and only retains important information for the purpose of verifying creator token of an + * Intent. + * @return a copy of ClipData with only URIs remained. + * @hide + */ + public ClipData cloneOnlyUriItems() { + ArrayList<Item> items = null; + final int N = mItems.size(); + for (int i = 0; i < N; i++) { + Item item = mItems.get(i); + if (item.getUri() != null) { + if (items == null) { + items = new ArrayList<>(N); + } + items.add(new Item(item.getUri())); + } else if (item.getIntent() != null) { + if (items == null) { + items = new ArrayList<>(N); + } + items.add(new Item(item.getIntent().cloneForCreatorToken())); + } + } + if (items == null || items.isEmpty()) return null; + return new ClipData(new ClipDescription("", new String[0]), items); + } + + /** * Create a new ClipData holding data of the type * {@link ClipDescription#MIMETYPE_TEXT_PLAIN}. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 02eed1a7553f..d7660172a2f1 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -7970,6 +7970,24 @@ public class Intent implements Parcelable, Cloneable { } /** + * Make a copy of all members important to identify an intent with its creator token. + * @hide + */ + public @NonNull Intent cloneForCreatorToken() { + Intent clone = new Intent() + .setAction(this.mAction) + .setDataAndType(this.mData, this.mType) + .setPackage(this.mPackage) + .setComponent(this.mComponent) + .setFlags(this.mFlags & IMMUTABLE_FLAGS); + if (this.mClipData != null) { + clone.setClipData(this.mClipData.cloneOnlyUriItems()); + } + clone.mCreatorTokenInfo = this.mCreatorTokenInfo; + return clone; + } + + /** * Create an intent with a given action. All other fields (data, type, * class) are null. Note that the action <em>must</em> be in a * namespace because Intents are used globally in the system -- for @@ -11684,7 +11702,7 @@ public class Intent implements Parcelable, Cloneable { Log.w(TAG, "Failure filling in extras", e); } } - mCreatorTokenInfo = other.mCreatorTokenInfo; + fillInCreatorTokenInfo(other.mCreatorTokenInfo, changes); if (mayHaveCopiedUris && mContentUserHint == UserHandle.USER_CURRENT && other.mContentUserHint != UserHandle.USER_CURRENT) { mContentUserHint = other.mContentUserHint; @@ -11692,6 +11710,45 @@ public class Intent implements Parcelable, Cloneable { return changes; } + // keep original creator token and merge nested intent keys. + private void fillInCreatorTokenInfo(CreatorTokenInfo otherCreatorTokenInfo, int changes) { + if (otherCreatorTokenInfo != null && otherCreatorTokenInfo.mNestedIntentKeys != null) { + if (mCreatorTokenInfo == null) { + mCreatorTokenInfo = new CreatorTokenInfo(); + } + ArraySet<NestedIntentKey> otherNestedIntentKeys = + otherCreatorTokenInfo.mNestedIntentKeys; + if (mCreatorTokenInfo.mNestedIntentKeys == null) { + mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(otherNestedIntentKeys); + } else { + ArraySet<NestedIntentKey> otherKeys; + if ((changes & FILL_IN_CLIP_DATA) == 0) { + // If clip data is Not filled in from other, do not merge clip data keys. + otherKeys = new ArraySet<>(); + int N = otherNestedIntentKeys.size(); + for (int i = 0; i < N; i++) { + NestedIntentKey key = otherNestedIntentKeys.valueAt(i); + if (key.mType != NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA) { + otherKeys.add(key); + } + } + } else { + // If clip data is filled in from other, remove clip data keys from this + // creatorTokenInfo and then merge every key from the others. + int N = mCreatorTokenInfo.mNestedIntentKeys.size(); + for (int i = N - 1; i >= 0; i--) { + NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i); + if (key.mType == NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA) { + mCreatorTokenInfo.mNestedIntentKeys.removeAt(i); + } + } + otherKeys = otherNestedIntentKeys; + } + mCreatorTokenInfo.mNestedIntentKeys.addAll(otherKeys); + } + } + } + /** * Merge the extras data in this intent with that of other supplied intent using the * strategy specified using {@code extrasMerger}. @@ -12228,6 +12285,7 @@ public class Intent implements Parcelable, Cloneable { private IBinder mCreatorToken; // Stores all extra keys whose values are intents for a top level intent. private ArraySet<NestedIntentKey> mNestedIntentKeys; + } /** diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 23d3693628e7..a06eb1c5b4ad 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -16,6 +16,7 @@ package android.content.pm; +import static android.content.pm.SigningInfo.AppSigningSchemeVersion; import static android.media.audio.Flags.FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY; import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES; @@ -59,6 +60,8 @@ import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.dex.ArtManager; +import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; import android.content.pm.verify.domain.DomainVerificationManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -94,6 +97,7 @@ import android.telephony.ims.RcsUceAdapter; import android.telephony.ims.SipDelegateManager; import android.util.AndroidException; import android.util.Log; +import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.pm.parsing.PackageInfoCommonUtils; @@ -3147,6 +3151,16 @@ public abstract class PackageManager { public static final long MAXIMUM_VERIFICATION_TIMEOUT = 60*60*1000; /** + * As the generated feature count is useful for classes that may not be compiled in the same + * annotation processing unit as PackageManager, we redeclare it here for visibility. + * + * @hide + */ + @VisibleForTesting + public static final int SDK_FEATURE_COUNT = + com.android.internal.pm.SystemFeaturesMetadata.SDK_FEATURE_COUNT; + + /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's * audio pipeline is low-latency, more suitable for audio applications sensitive to delays or * lag in sound input or output. @@ -11987,4 +12001,31 @@ public abstract class PackageManager { throw new UnsupportedOperationException( "parseServiceMetadata not implemented in subclass"); } + + /** + * Verifies and returns the + * <a href="https://source.android.com/docs/security/features/apksigning">app signing</a> + * information of the file at the given path. This operation takes a few milliseconds. + * + * Unlike {@link #getPackageArchiveInfo(String, PackageInfoFlags)} with {@link + * #GET_SIGNING_CERTIFICATES}, this method does not require the file to be a package archive + * file. + * + * @throws SigningInfoException if the verification fails + * + * @hide + */ + @FlaggedApi(android.content.pm.Flags.FLAG_CLOUD_COMPILATION_PM) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static @NonNull SigningInfo getVerifiedSigningInfo(@NonNull String path, + @AppSigningSchemeVersion int minAppSigningSchemeVersion) throws SigningInfoException { + ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); + ParseResult<SigningDetails> result = + ApkSignatureVerifier.verify(input, path, minAppSigningSchemeVersion); + if (result.isError()) { + throw new SigningInfoException( + result.getErrorCode(), result.getErrorMessage(), result.getException()); + } + return new SigningInfo(result.getResult()); + } } diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java index 23daaf2d4138..e4fbd1f28dbb 100644 --- a/core/java/android/content/pm/SigningInfo.java +++ b/core/java/android/content/pm/SigningInfo.java @@ -16,14 +16,20 @@ package android.content.pm; +import static android.content.pm.SigningDetails.SignatureSchemeVersion; + import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.content.pm.SigningDetails.SignatureSchemeVersion; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.security.PublicKey; import java.util.Collection; @@ -31,6 +37,55 @@ import java.util.Collection; * Information pertaining to the signing certificates used to sign a package. */ public final class SigningInfo implements Parcelable { + /** + * JAR signing (v1 scheme). + * See https://source.android.com/docs/security/features/apksigning#v1. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int VERSION_JAR = SignatureSchemeVersion.JAR; + + /** + * APK signature scheme v2. + * See https://source.android.com/docs/security/features/apksigning/v2. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int VERSION_SIGNING_BLOCK_V2 = SignatureSchemeVersion.SIGNING_BLOCK_V2; + + /** + * APK signature scheme v3. + * See https://source.android.com/docs/security/features/apksigning/v3. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int VERSION_SIGNING_BLOCK_V3 = SignatureSchemeVersion.SIGNING_BLOCK_V3; + + /** + * APK signature scheme v4. + * See https://source.android.com/docs/security/features/apksigning/v4. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int VERSION_SIGNING_BLOCK_V4 = SignatureSchemeVersion.SIGNING_BLOCK_V4; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"VERSION_"}, value = { + VERSION_JAR, + VERSION_SIGNING_BLOCK_V2, + VERSION_SIGNING_BLOCK_V3, + VERSION_SIGNING_BLOCK_V4, + }) + public @interface AppSigningSchemeVersion {} @NonNull private final SigningDetails mSigningDetails; @@ -198,6 +253,17 @@ public final class SigningInfo implements Parcelable { return mSigningDetails; } + /** + * Returns true if the signing certificates in this and other match exactly. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public boolean signersMatchExactly(@NonNull SigningInfo other) { + return mSigningDetails.signaturesMatchExactly(other.mSigningDetails); + } + public static final @android.annotation.NonNull Parcelable.Creator<SigningInfo> CREATOR = new Parcelable.Creator<SigningInfo>() { @Override diff --git a/core/java/android/content/pm/SigningInfoException.java b/core/java/android/content/pm/SigningInfoException.java new file mode 100644 index 000000000000..a81e07e73685 --- /dev/null +++ b/core/java/android/content/pm/SigningInfoException.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; + +/** + * Indicates an error when verifying the + * <a href="https://source.android.com/docs/security/features/apksigning">app signing</a> + * information. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM) +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public class SigningInfoException extends Exception { + private final int mCode; + + /** @hide */ + public SigningInfoException(int code, @NonNull String message, @Nullable Throwable cause) { + super(message, cause); + mCode = code; + } + + /** + * Returns a code representing the cause, in one of the installation parse return codes in + * {@link PackageManager}. + */ + @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM) + public int getCode() { + return mCode; + } +} diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java index ba089f7fd33e..35e5c443e4b2 100644 --- a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java +++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java @@ -53,7 +53,14 @@ public final class DependencyInstallerCallback implements Parcelable { * Callback to indicate that all the requested dependencies have been resolved and their * sessions created. See {@link DependencyInstallerService#onDependenciesRequired}. * + * The system will wait for the sessions to be installed before resuming the original session + * which requested dependency installation. + * + * If any of the session fails to install, the system may fail the original session. The caller + * is expected to handle clean up of any other pending sessions remanining. + * * @param sessionIds the install session IDs for all requested dependencies + * @throws IllegalArgumentException if session id doesn't exist or has already failed. */ public void onAllDependenciesResolved(@NonNull int[] sessionIds) { try { diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl index 92d1d9e118e6..e4cf55d3ce5f 100644 --- a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl +++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl @@ -24,7 +24,7 @@ import java.util.List; * * {@hide} */ -oneway interface IDependencyInstallerCallback { +interface IDependencyInstallerCallback { /** * Callback to indicate that all the requested dependencies have been resolved and have been * committed for installation. See {@link DependencyInstallerService#onDependenciesRequired}. @@ -38,4 +38,4 @@ oneway interface IDependencyInstallerCallback { * and any associated sessions have been abandoned. */ void onFailureToResolveAllDependencies(); -}
\ No newline at end of file +} diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 833260a15c45..d351ebc22026 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -363,6 +363,17 @@ flag { is_fixed_read_only: true } +flag { + name: "cache_user_restrictions_read_only" + namespace: "multiuser" + description: "Cache hasUserRestriction to avoid unnecessary binder calls" + bug: "350419621" + metadata { + purpose: PURPOSE_BUGFIX + } + is_fixed_read_only: true +} + # This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile. flag { name: "enable_private_space_features" diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 266efb7b759c..aba2345f28d8 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -1699,17 +1699,18 @@ public final class CameraManager { } if (context != null) { - final ActivityManager activityManager = - context.getSystemService(ActivityManager.class); - for (ActivityManager.AppTask appTask : activityManager.getAppTasks()) { - final TaskInfo taskInfo = appTask.getTaskInfo(); - final int freeformCameraCompatMode = - taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode; - if (freeformCameraCompatMode != 0 - && taskInfo.topActivity != null - && taskInfo.topActivity.getPackageName().equals(packageName)) { - // WindowManager has requested rotation override. - return getRotationOverrideForCompatFreeform(freeformCameraCompatMode); + final ActivityManager activityManager = context.getSystemService(ActivityManager.class); + if (activityManager != null) { + for (ActivityManager.AppTask appTask : activityManager.getAppTasks()) { + final TaskInfo taskInfo = appTask.getTaskInfo(); + final int freeformCameraCompatMode = taskInfo.appCompatTaskInfo + .cameraCompatTaskInfo.freeformCameraCompatMode; + if (freeformCameraCompatMode != 0 + && taskInfo.topActivity != null + && taskInfo.topActivity.getPackageName().equals(packageName)) { + // WindowManager has requested rotation override. + return getRotationOverrideForCompatFreeform(freeformCameraCompatMode); + } } } } diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 1e66beea42a6..a31b87f5def2 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -1507,13 +1507,6 @@ public final class DisplayManagerGlobal { mExecutor.execute(mCallback::onStopped); } } - - @Override // Binder call - public void onRequestedBrightnessChanged(float brightness) { - if (mCallback != null) { - mExecutor.execute(() -> mCallback.onRequestedBrightnessChanged(brightness)); - } - } } /** diff --git a/core/java/android/hardware/display/IBrightnessListener.aidl b/core/java/android/hardware/display/IBrightnessListener.aidl new file mode 100644 index 000000000000..f5d37435b287 --- /dev/null +++ b/core/java/android/hardware/display/IBrightnessListener.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +/** + * Interface for notifying the display owner about brightness changes. + * + * @hide + */ +oneway interface IBrightnessListener { + /** + * Called when the display's brightness has changed. + * + * @param brightness the new brightness of the display. Value of {@code 0.0} indicates the + * minimum supported brightness and value of {@code 1.0} indicates the maximum supported + * brightness. + */ + void onBrightnessChanged(float brightness); +} diff --git a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl index 9cc0364f2729..c3490d177be2 100644 --- a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl +++ b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl @@ -38,9 +38,4 @@ oneway interface IVirtualDisplayCallback { * of the application to release() the virtual display. */ void onStopped(); - - /** - * Called when the virtual display's requested brightness has changed. - */ - void onRequestedBrightnessChanged(float brightness); } diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java index 3b573ea98c27..32b640583734 100644 --- a/core/java/android/hardware/display/VirtualDisplay.java +++ b/core/java/android/hardware/display/VirtualDisplay.java @@ -16,8 +16,6 @@ package android.hardware.display; import android.annotation.FlaggedApi; -import android.annotation.FloatRange; -import android.annotation.SystemApi; import android.view.Display; import android.view.Surface; @@ -166,25 +164,5 @@ public final class VirtualDisplay { * of the application to release() the virtual display. */ public void onStopped() { } - - /** - * Called when the requested brightness of the display has changed. - * - * <p>The system may adjust the display's brightness based on user or app activity. This - * callback will only be invoked if the display has an explicitly specified default - * brightness value.</p> - * - * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of - * {@code 1.0} indicates the maximum supported brightness.</p> - * - * @see android.view.View#setKeepScreenOn(boolean) - * @see android.view.WindowManager.LayoutParams#screenBrightness - * @see VirtualDisplayConfig.Builder#setDefaultBrightness(float) - * @hide - */ - @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) - @SystemApi - public void onRequestedBrightnessChanged( - @FloatRange(from = 0.0f, to = 1.0f) float brightness) {} } } diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java index 57d9d28a9d47..eceaa8ff4c95 100644 --- a/core/java/android/hardware/display/VirtualDisplayConfig.java +++ b/core/java/android/hardware/display/VirtualDisplayConfig.java @@ -18,11 +18,13 @@ package android.hardware.display; import static android.view.Display.DEFAULT_DISPLAY; +import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.hardware.display.DisplayManager.VirtualDisplayFlag; import android.media.projection.MediaProjection; @@ -38,6 +40,7 @@ import android.view.Surface; import java.util.Collections; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Executor; /** * Holds configuration used to create {@link VirtualDisplay} instances. @@ -63,6 +66,7 @@ public final class VirtualDisplayConfig implements Parcelable { private final DisplayCutout mDisplayCutout; private final boolean mIgnoreActivitySizeRestrictions; private final float mDefaultBrightness; + private final IBrightnessListener mBrightnessListener; private VirtualDisplayConfig( @NonNull String name, @@ -79,7 +83,8 @@ public final class VirtualDisplayConfig implements Parcelable { boolean isHomeSupported, @Nullable DisplayCutout displayCutout, boolean ignoreActivitySizeRestrictions, - @FloatRange(from = 0.0f, to = 1.0f) float defaultBrightness) { + @FloatRange(from = 0.0f, to = 1.0f) float defaultBrightness, + IBrightnessListener brightnessListener) { mName = name; mWidth = width; mHeight = height; @@ -95,6 +100,7 @@ public final class VirtualDisplayConfig implements Parcelable { mDisplayCutout = displayCutout; mIgnoreActivitySizeRestrictions = ignoreActivitySizeRestrictions; mDefaultBrightness = defaultBrightness; + mBrightnessListener = brightnessListener; } /** @@ -167,14 +173,20 @@ public final class VirtualDisplayConfig implements Parcelable { * indicates the maximum supported brightness.</p> * * @see Builder#setDefaultBrightness(float) - * @hide */ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) - @SystemApi public @FloatRange(from = 0.0f, to = 1.0f) float getDefaultBrightness() { return mDefaultBrightness; } + /** + * Returns the listener to get notified about changes in the display brightness. + * @hide + */ + @Nullable + public IBrightnessListener getBrightnessListener() { + return mBrightnessListener; + } /** * Returns the unique identifier for the display. Shouldn't be displayed to the user. @@ -266,6 +278,7 @@ public final class VirtualDisplayConfig implements Parcelable { DisplayCutout.ParcelableWrapper.writeCutoutToParcel(mDisplayCutout, dest, flags); dest.writeBoolean(mIgnoreActivitySizeRestrictions); dest.writeFloat(mDefaultBrightness); + dest.writeStrongBinder(mBrightnessListener != null ? mBrightnessListener.asBinder() : null); } @Override @@ -294,7 +307,9 @@ public final class VirtualDisplayConfig implements Parcelable { && mIsHomeSupported == that.mIsHomeSupported && mIgnoreActivitySizeRestrictions == that.mIgnoreActivitySizeRestrictions && Objects.equals(mDisplayCutout, that.mDisplayCutout) - && mDefaultBrightness == that.mDefaultBrightness; + && mDefaultBrightness == that.mDefaultBrightness + && Objects.equals(mBrightnessListener, that.mBrightnessListener); + } @Override @@ -303,7 +318,7 @@ public final class VirtualDisplayConfig implements Parcelable { mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId, mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories, mRequestedRefreshRate, mIsHomeSupported, mDisplayCutout, - mIgnoreActivitySizeRestrictions, mDefaultBrightness); + mIgnoreActivitySizeRestrictions, mDefaultBrightness, mBrightnessListener); return hashCode; } @@ -345,6 +360,43 @@ public final class VirtualDisplayConfig implements Parcelable { mDisplayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(in); mIgnoreActivitySizeRestrictions = in.readBoolean(); mDefaultBrightness = in.readFloat(); + mBrightnessListener = IBrightnessListener.Stub.asInterface(in.readStrongBinder()); + + } + + /** + * Listener for display brightness changes. + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + public interface BrightnessListener { + + /** + * Called when the display's brightness has changed. + * + * @param brightness the new brightness of the display. Value of {@code 0.0} indicates the + * minimum supported brightness and value of {@code 1.0} indicates the maximum supported + * brightness. + */ + void onBrightnessChanged(@FloatRange(from = 0.0f, to = 1.0f) float brightness); + } + + private static class BrightnessListenerDelegate extends IBrightnessListener.Stub { + + @NonNull + private final Executor mExecutor; + @NonNull + private final BrightnessListener mListener; + + BrightnessListenerDelegate(@NonNull @CallbackExecutor Executor executor, + @NonNull BrightnessListener listener) { + mExecutor = executor; + mListener = listener; + } + + @Override + public void onBrightnessChanged(float brightness) { + mExecutor.execute(() -> mListener.onBrightnessChanged(brightness)); + } } @NonNull @@ -380,6 +432,7 @@ public final class VirtualDisplayConfig implements Parcelable { private DisplayCutout mDisplayCutout = null; private boolean mIgnoreActivitySizeRestrictions = false; private float mDefaultBrightness = 0.0f; + private IBrightnessListener mBrightnessListener = null; /** * Creates a new Builder. @@ -575,7 +628,7 @@ public final class VirtualDisplayConfig implements Parcelable { * Sets the default brightness of the display. * * <p>The system will use this brightness value whenever the display should be bright, i.e. - * it is powered on and not dimmed due to user activity or app activity.</p> + * it is powered on and not modified due to user activity or app activity.</p> * * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of * {@code 1.0} indicates the maximum supported brightness.</p> @@ -583,12 +636,9 @@ public final class VirtualDisplayConfig implements Parcelable { * <p>If unset, defaults to {@code 0.0}</p> * * @see android.view.View#setKeepScreenOn(boolean) - * @see Builder#setDefaultBrightness(float) - * @see VirtualDisplay.Callback#onRequestedBrightnessChanged(float) - * @hide + * @see #setBrightnessListener(Executor, BrightnessListener) */ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) - @SystemApi @NonNull public Builder setDefaultBrightness(@FloatRange(from = 0.0f, to = 1.0f) float brightness) { if (brightness < PowerManager.BRIGHTNESS_MIN @@ -601,6 +651,22 @@ public final class VirtualDisplayConfig implements Parcelable { } /** + * Sets the listener to get notified about changes in the display brightness. + * + * @param executor The executor where the callback is executed on. + * @param listener The listener to get notified when the display brightness has changed. + */ + @SuppressLint("MissingGetterMatchingBuilder") // The hidden getter returns the AIDL object + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @NonNull + public Builder setBrightnessListener(@NonNull @CallbackExecutor Executor executor, + @NonNull BrightnessListener listener) { + mBrightnessListener = new BrightnessListenerDelegate( + Objects.requireNonNull(executor), Objects.requireNonNull(listener)); + return this; + } + + /** * Builds the {@link VirtualDisplayConfig} instance. */ @NonNull @@ -620,7 +686,8 @@ public final class VirtualDisplayConfig implements Parcelable { mIsHomeSupported, mDisplayCutout, mIgnoreActivitySizeRestrictions, - mDefaultBrightness); + mDefaultBrightness, + mBrightnessListener); } } } diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java index c0398ce1fcf1..ded94159a945 100644 --- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java +++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java @@ -23,18 +23,19 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; import static android.net.vcn.VcnUnderlyingNetworkTemplate.getMatchCriteriaString; +import static android.net.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER; +import static android.net.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER; +import static android.net.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER; +import static android.net.vcn.util.PersistableBundleUtils.STRING_SERIALIZER; import static com.android.internal.annotations.VisibleForTesting.Visibility; -import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER; -import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER; -import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER; -import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.net.NetworkCapabilities; import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria; +import android.net.vcn.util.PersistableBundleUtils; import android.os.PersistableBundle; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -44,7 +45,6 @@ import android.util.IndentingPrintWriter; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; -import com.android.server.vcn.util.PersistableBundleUtils; import java.util.ArrayList; import java.util.Collections; diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java index a27e9230d473..0d0efb2f73f9 100644 --- a/core/java/android/net/vcn/VcnConfig.java +++ b/core/java/android/net/vcn/VcnConfig.java @@ -18,10 +18,10 @@ package android.net.vcn; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER; +import static android.net.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER; import static com.android.internal.annotations.VisibleForTesting.Visibility; -import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER; -import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER; import android.annotation.IntDef; import android.annotation.NonNull; @@ -29,6 +29,7 @@ import android.annotation.Nullable; import android.content.Context; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.net.vcn.util.PersistableBundleUtils; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; @@ -37,7 +38,6 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; -import com.android.server.vcn.util.PersistableBundleUtils; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index b270062cbffc..067144e6f474 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -32,12 +32,12 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.ipsec.ike.IkeTunnelConnectionParams; import android.net.vcn.persistablebundleutils.TunnelConnectionParamsUtils; +import android.net.vcn.util.PersistableBundleUtils; import android.os.PersistableBundle; import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; -import com.android.server.vcn.util.PersistableBundleUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java index c7b2f188ba96..770a8c118a4d 100644 --- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java +++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java @@ -17,22 +17,22 @@ package android.net.vcn; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; +import static android.net.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER; +import static android.net.vcn.util.PersistableBundleUtils.STRING_SERIALIZER; import static com.android.internal.annotations.VisibleForTesting.Visibility; -import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER; -import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.net.NetworkCapabilities; import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria; +import android.net.vcn.util.PersistableBundleUtils; import android.os.PersistableBundle; import android.util.ArraySet; import android.util.IndentingPrintWriter; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.vcn.util.PersistableBundleUtils; import java.util.ArrayList; import java.util.Collections; diff --git a/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java b/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java index ce5ec75f01a2..48c1b25a97ab 100644 --- a/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java +++ b/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java @@ -20,10 +20,10 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility; import android.annotation.NonNull; import android.net.ipsec.ike.ChildSaProposal; +import android.net.vcn.util.PersistableBundleUtils; import android.os.PersistableBundle; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.vcn.util.PersistableBundleUtils; import java.util.List; import java.util.Objects; diff --git a/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java b/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java index 853a52da766a..dc1ee36b71c1 100644 --- a/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java +++ b/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java @@ -28,10 +28,10 @@ import android.net.eap.EapSessionConfig.EapMsChapV2Config; import android.net.eap.EapSessionConfig.EapSimConfig; import android.net.eap.EapSessionConfig.EapTtlsConfig; import android.net.eap.EapSessionConfig.EapUiccConfig; +import android.net.vcn.util.PersistableBundleUtils; import android.os.PersistableBundle; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.vcn.util.PersistableBundleUtils; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java index 6acb34ebb78e..6e8616fc9cb0 100644 --- a/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java +++ b/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java @@ -27,10 +27,10 @@ import android.net.ipsec.ike.IkeIpv4AddrIdentification; import android.net.ipsec.ike.IkeIpv6AddrIdentification; import android.net.ipsec.ike.IkeKeyIdIdentification; import android.net.ipsec.ike.IkeRfc822AddrIdentification; +import android.net.vcn.util.PersistableBundleUtils; import android.os.PersistableBundle; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.vcn.util.PersistableBundleUtils; import java.net.Inet4Address; import java.net.Inet6Address; diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java index 1459671f4136..b590148de51f 100644 --- a/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java +++ b/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java @@ -20,10 +20,10 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility; import android.annotation.NonNull; import android.net.ipsec.ike.IkeSaProposal; +import android.net.vcn.util.PersistableBundleUtils; import android.os.PersistableBundle; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.vcn.util.PersistableBundleUtils; import java.util.List; import java.util.Objects; diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java index d1531a119a0d..aefac2e89aea 100644 --- a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java +++ b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java @@ -35,12 +35,12 @@ import android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignRemoteConfig; import android.net.ipsec.ike.IkeSessionParams.IkeAuthEapConfig; import android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig; import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest; +import android.net.vcn.util.PersistableBundleUtils; import android.os.PersistableBundle; import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.vcn.util.PersistableBundleUtils; import java.net.InetAddress; import java.security.PrivateKey; diff --git a/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java b/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java index 0c9ee8432798..469966a48465 100644 --- a/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java +++ b/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java @@ -18,11 +18,10 @@ package android.net.vcn.persistablebundleutils; import android.annotation.NonNull; import android.net.ipsec.ike.SaProposal; +import android.net.vcn.util.PersistableBundleUtils; import android.os.PersistableBundle; import android.util.Pair; -import com.android.server.vcn.util.PersistableBundleUtils; - import java.util.ArrayList; import java.util.List; import java.util.Objects; diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java index e62acac14bd7..3f4ba345a118 100644 --- a/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java +++ b/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java @@ -34,11 +34,11 @@ import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Netma import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6Address; import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer; import android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest; +import android.net.vcn.util.PersistableBundleUtils; import android.os.PersistableBundle; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.vcn.util.PersistableBundleUtils; import java.net.Inet4Address; import java.net.Inet6Address; diff --git a/services/core/java/com/android/server/vcn/util/LogUtils.java b/core/java/android/net/vcn/util/LogUtils.java index 93728ceb27c5..7f7f85271603 100644 --- a/services/core/java/com/android/server/vcn/util/LogUtils.java +++ b/core/java/android/net/vcn/util/LogUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.vcn.util; +package android.net.vcn.util; import android.annotation.Nullable; import android.os.ParcelUuid; diff --git a/services/core/java/com/android/server/vcn/util/MtuUtils.java b/core/java/android/net/vcn/util/MtuUtils.java index 356c71f5f26a..c3123bcecf33 100644 --- a/services/core/java/com/android/server/vcn/util/MtuUtils.java +++ b/core/java/android/net/vcn/util/MtuUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.vcn.util; +package android.net.vcn.util; import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_3DES; import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC; diff --git a/services/core/java/com/android/server/vcn/util/OneWayBoolean.java b/core/java/android/net/vcn/util/OneWayBoolean.java index e79bb2d2547d..a7ef67b187b9 100644 --- a/services/core/java/com/android/server/vcn/util/OneWayBoolean.java +++ b/core/java/android/net/vcn/util/OneWayBoolean.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.vcn.util; +package android.net.vcn.util; /** * OneWayBoolean is an abstraction for a boolean that MUST only ever be flipped from false to true diff --git a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java b/core/java/android/net/vcn/util/PersistableBundleUtils.java index d6761a2b37d8..4dc42c7827f8 100644 --- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java +++ b/core/java/android/net/vcn/util/PersistableBundleUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.vcn.util; +package android.net.vcn.util; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/os/CpuHeadroomParams.java b/core/java/android/os/CpuHeadroomParams.java index 8e78b7e355f9..072c012d6db9 100644 --- a/core/java/android/os/CpuHeadroomParams.java +++ b/core/java/android/os/CpuHeadroomParams.java @@ -56,15 +56,9 @@ public final class CpuHeadroomParams { */ public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; - /** - * Minimum CPU headroom calculation window size. - */ - public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; - - /** - * Maximum CPU headroom calculation window size. - */ - public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; + private static final int CALCULATION_WINDOW_MILLIS_MIN = 50; + private static final int CALCULATION_WINDOW_MILLIS_MAX = 10000; + private static final int MAX_TID_COUNT = 5; /** * Sets the headroom calculation type. @@ -99,20 +93,18 @@ public final class CpuHeadroomParams { * Sets the headroom calculation window size in milliseconds. * <p> * - * @param windowMillis the window size in milliseconds, ranged from - * [{@link #CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN}, - * {@link #CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX}]. The smaller - * the value, the larger fluctuation in value should be expected. The - * default value can be retrieved from the + * @param windowMillis the window size in milliseconds ranges from [50, 10000]. The smaller the + * window size, the larger fluctuation in the headroom value should be + * expected. The default value can be retrieved from the * {@link #getCalculationWindowMillis}. The device will try to use the * closest feasible window size to this param. * @throws IllegalArgumentException if the window size is not in allowed range. */ public void setCalculationWindowMillis( - @IntRange(from = CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to = - CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) { - if (windowMillis < CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN - || windowMillis > CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) { + @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to = + CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) { + if (windowMillis < CALCULATION_WINDOW_MILLIS_MIN + || windowMillis > CALCULATION_WINDOW_MILLIS_MAX) { throw new IllegalArgumentException("Invalid calculation window: " + windowMillis); } mInternal.calculationWindowMillis = windowMillis; @@ -121,10 +113,10 @@ public final class CpuHeadroomParams { /** * Gets the headroom calculation window size in milliseconds. * <p> - * This will return the default value chosen by the device if not set. + * This will return the default value chosen by the device if the params is not set. */ - public @IntRange(from = CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to = - CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) long getCalculationWindowMillis() { + public @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to = + CALCULATION_WINDOW_MILLIS_MAX) long getCalculationWindowMillis() { return mInternal.calculationWindowMillis; } @@ -141,7 +133,7 @@ public final class CpuHeadroomParams { * positive. */ public void setTids(@NonNull int... tids) { - if (tids.length == 0 || tids.length > 5) { + if (tids.length == 0 || tids.length > MAX_TID_COUNT) { throw new IllegalArgumentException("Invalid number of TIDs: " + tids.length); } for (int tid : tids) { diff --git a/core/java/android/os/GpuHeadroomParams.java b/core/java/android/os/GpuHeadroomParams.java index 4dc98264e57b..126ee8cce3d0 100644 --- a/core/java/android/os/GpuHeadroomParams.java +++ b/core/java/android/os/GpuHeadroomParams.java @@ -54,15 +54,8 @@ public final class GpuHeadroomParams { */ public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; - /** - * Minimum GPU headroom calculation window size. - */ - public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; - - /** - * Maximum GPU headroom calculation window size. - */ - public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; + private static final int CALCULATION_WINDOW_MILLIS_MIN = 50; + private static final int CALCULATION_WINDOW_MILLIS_MAX = 10000; /** * Sets the headroom calculation type. @@ -82,7 +75,7 @@ public final class GpuHeadroomParams { /** * Gets the headroom calculation type. - * Default to {@link #GPU_HEADROOM_CALCULATION_TYPE_MIN} if not set. + * Default to {@link #GPU_HEADROOM_CALCULATION_TYPE_MIN} if the params is not set. */ public @GpuHeadroomCalculationType int getCalculationType() { @GpuHeadroomCalculationType int validatedType = switch ((int) mInternal.calculationType) { @@ -97,20 +90,18 @@ public final class GpuHeadroomParams { * Sets the headroom calculation window size in milliseconds. * <p> * - * @param windowMillis the window size in milliseconds, ranged from - * [{@link #GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN}, - * {@link #GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX}]. The smaller - * the value, the larger fluctuation in value should be expected. The - * default value can be retrieved from the - * {@link #getCalculationWindowMillis}. If the device will try to use the + * @param windowMillis the window size in milliseconds ranges from [50, 10000]. The smaller the + * window size, the larger fluctuation in the headroom value should be + * expected. The default value can be retrieved from the + * {@link #getCalculationWindowMillis}. The device will try to use the * closest feasible window size to this param. * @throws IllegalArgumentException if the window is invalid. */ public void setCalculationWindowMillis( - @IntRange(from = GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to = - GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) { - if (windowMillis < GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN - || windowMillis > GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) { + @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to = + CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) { + if (windowMillis < CALCULATION_WINDOW_MILLIS_MIN + || windowMillis > CALCULATION_WINDOW_MILLIS_MAX) { throw new IllegalArgumentException("Invalid calculation window: " + windowMillis); } mInternal.calculationWindowMillis = windowMillis; @@ -121,8 +112,8 @@ public final class GpuHeadroomParams { * <p> * This will return the default value chosen by the device if not set. */ - public @IntRange(from = GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to = - GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int getCalculationWindowMillis() { + public @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to = + CALCULATION_WINDOW_MILLIS_MAX) int getCalculationWindowMillis() { return mInternal.calculationWindowMillis; } diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index f9789c19b0d5..bfcc5cc6f18e 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -93,10 +93,9 @@ per-file CpuHeadroom*.aidl = file:/ADPF_OWNERS per-file GpuHeadroom*.aidl = file:/ADPF_OWNERS per-file CpuHeadroom*.java = file:/ADPF_OWNERS per-file GpuHeadroom*.java = file:/ADPF_OWNERS -per-file PerformanceHintManager.java = file:/ADPF_OWNERS per-file WorkDuration.java = file:/ADPF_OWNERS -per-file IHintManager.aidl = file:/ADPF_OWNERS -per-file IHintSession.aidl = file:/ADPF_OWNERS +per-file *Hint* = file:/ADPF_OWNERS +per-file *Session* = file:/ADPF_OWNERS # IThermal interfaces per-file IThermal* = file:/THERMAL_OWNERS diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index a1ede5fee3f3..7e73a5d04866 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -3846,7 +3846,7 @@ public class UserManager { } /** - * Return the time when the context user was unlocked elapsed milliseconds since boot, + * Return the time when the calling user was unlocked elapsed milliseconds since boot, * or 0 if not unlocked. * * @hide diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index a7195834e6bb..b5139b5b7ef6 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -445,3 +445,21 @@ flag { description: "Enable the Wallet role within profiles" bug: "356107987" } + +flag { + name: "text_classifier_choice_api_enabled" + is_fixed_read_only: true + is_exported: true + namespace: "permissions" + description: "API change to enable getTextClassifier by type" + bug: "377229653" +} + +flag { + name: "updatable_text_classifier_for_otp_detection_enabled" + is_fixed_read_only: true + is_exported: true + namespace: "permissions" + description: "Enables text classifier for OTP detection that is updatable from mainline module" + bug: "377229653" +} diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index f6bdc18c29ec..e4a3c9fa7741 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -16,6 +16,8 @@ package android.preference; +import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT; + import android.animation.LayoutTransition; import android.annotation.Nullable; import android.annotation.StringRes; @@ -54,6 +56,8 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; +import android.window.OnBackInvokedCallback; +import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.util.XmlUtils; @@ -209,6 +213,8 @@ public abstract class PreferenceActivity extends ListActivity implements private int mPreferenceHeaderItemResId = 0; private boolean mPreferenceHeaderRemoveEmptyIcon = false; + private final OnBackInvokedCallback mOnBackInvokedCallback = this::onBackInvoked; + /** * The starting request code given out to preference framework. */ @@ -699,10 +705,26 @@ public abstract class PreferenceActivity extends ListActivity implements skipButton.setVisibility(View.VISIBLE); } } + updateBackCallbackRegistrationState(); } @Override public void onBackPressed() { + onBackInvoked(); + } + + private void updateBackCallbackRegistrationState() { + if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) return; + if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0 + && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) { + getOnBackInvokedDispatcher() + .registerOnBackInvokedCallback(PRIORITY_DEFAULT, mOnBackInvokedCallback); + } else { + getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mOnBackInvokedCallback); + } + } + + private void onBackInvoked() { if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0 && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) { mCurHeader = null; @@ -713,9 +735,10 @@ public abstract class PreferenceActivity extends ListActivity implements showBreadCrumbs(mActivityTitle, null); } getListView().clearChoices(); - } else { + } else if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) { super.onBackPressed(); } + updateBackCallbackRegistrationState(); } /** @@ -1221,6 +1244,7 @@ public abstract class PreferenceActivity extends ListActivity implements getListView().clearChoices(); } showBreadCrumbs(header); + updateBackCallbackRegistrationState(); } void showBreadCrumbs(Header header) { diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 99ff38b43adc..8e379e82a5fb 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -10859,6 +10859,28 @@ public final class ContactsContract { "vnd.android.cursor.item/contact_metadata_sync_state"; } + /** + * This exception is thrown when an attempt is made to perform a write operation + * on a contact or contact group targeting a local account or a SIM account, + * and the operation is not permitted under the current conditions. + * The local account can be retrieved using {@link RawContacts#getLocalAccountName(Context)} + * and {@link RawContacts#getLocalAccountType(Context)}. + * SIM accounts can be retrieved using {@link SimContacts#getSimAccounts(ContentResolver)}. + * + * <p>Local and SIM accounts have limitations that may prevent write operations + * due to their nature, underlying implementation, or the current system state. + * For example, the SIM card may be full, read-only, or not present. + * + * <p>The specific conditions under which write operations are permitted on + * local or SIM accounts can vary. + */ + @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED) + public static class LocalSimContactsWriteException extends IllegalArgumentException { + public LocalSimContactsWriteException(@NonNull String s) { + super(s); + } + } + private static Bundle nullSafeCall(@NonNull ContentResolver resolver, @NonNull Uri uri, @NonNull String method, @Nullable String arg, @Nullable Bundle extras) { try (ContentProviderClient client = resolver.acquireContentProviderClient(uri)) { diff --git a/core/java/android/security/advancedprotection/OWNERS b/core/java/android/security/advancedprotection/OWNERS index ddac8edb6f4a..bfb7e16e15a8 100644 --- a/core/java/android/security/advancedprotection/OWNERS +++ b/core/java/android/security/advancedprotection/OWNERS @@ -2,7 +2,6 @@ achim@google.com azharaa@google.com -cpinelli@google.com eranm@google.com hanikazmi@google.com haok@google.com diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index 1dd9d46fdfb7..f8737a55a5b0 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -75,7 +75,7 @@ import java.net.UnknownHostException; @android.ravenwood.annotation.RavenwoodClassLoadHook( "com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook.onClassLoaded") // Uncomment the following annotation to switch to the Java substitution version. -@android.ravenwood.annotation.RavenwoodRedirectionClass("Log_host") +@android.ravenwood.annotation.RavenwoodRedirectionClass("Log_ravenwood") public final class Log { /** @hide */ @IntDef({ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE}) diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl index 45dbe43bbdd5..21b969c1dedb 100644 --- a/core/java/android/view/IDisplayWindowInsetsController.aidl +++ b/core/java/android/view/IDisplayWindowInsetsController.aidl @@ -60,5 +60,5 @@ oneway interface IDisplayWindowInsetsController { * Reports the requested IME visibility of the IME input target to * the IDisplayWindowInsetsController */ - void setImeInputTargetRequestedVisibility(boolean visible); + void setImeInputTargetRequestedVisibility(boolean visible, in ImeTracker.Token statsToken); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index e5be53191e9e..6d85e7589c48 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -62,6 +62,7 @@ import android.view.MotionEvent; import android.view.InputChannel; import android.view.InputDevice; import android.view.IInputFilter; +import android.view.inputmethod.ImeTracker; import android.view.AppTransitionAnimationSpec; import android.view.WindowContentFrameStats; import android.view.WindowManager; @@ -765,7 +766,8 @@ interface IWindowManager * container. */ @EnforcePermission("MANAGE_APP_TOKENS") - void updateDisplayWindowRequestedVisibleTypes(int displayId, int requestedVisibleTypes); + void updateDisplayWindowRequestedVisibleTypes(int displayId, int requestedVisibleTypes, + in @nullable ImeTracker.Token statsToken); /** * Called to get the expected window insets. diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index dd32d57bd650..4d354e0655f0 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -222,6 +222,8 @@ public interface ImeTracker { PHASE_CLIENT_ALREADY_HIDDEN, PHASE_CLIENT_VIEW_HANDLER_AVAILABLE, PHASE_SERVER_UPDATE_CLIENT_VISIBILITY, + PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE, + PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES, }) @Retention(RetentionPolicy.SOURCE) @interface Phase {} @@ -436,6 +438,12 @@ public interface ImeTracker { * app or the RemoteInsetsControlTarget). */ int PHASE_SERVER_UPDATE_CLIENT_VISIBILITY = ImeProtoEnums.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY; + /** DisplayImeController received the requested visibility for the IME and stored it. */ + int PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE = + ImeProtoEnums.PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE; + /** The control target reported its requestedVisibleTypes back to WindowManagerService. */ + int PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES = + ImeProtoEnums.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES; /** * Called when an IME request is started. diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 6303c7637a59..5dd29b26730d 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2626,10 +2626,12 @@ public final class InputMethodManager { // The view is running on a different thread than our own, so // we need to reschedule our work for over there. if (DEBUG) Log.v(TAG, "Hiding soft input: reschedule to view thread"); + final var finalStatsToken = statsToken; vh.post(() -> viewRootImpl.getInsetsController().hide( - WindowInsets.Type.ime())); + WindowInsets.Type.ime(), false /* fromIme */, finalStatsToken)); } else { - viewRootImpl.getInsetsController().hide(WindowInsets.Type.ime()); + viewRootImpl.getInsetsController().hide(WindowInsets.Type.ime(), + false /* fromIme */, statsToken); } } return true; diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java index 0bf6380eb904..eb3b76873a8f 100644 --- a/core/java/android/widget/Button.java +++ b/core/java/android/widget/Button.java @@ -134,6 +134,7 @@ public class Button extends TextView { // 1. app target sdk is 36 or above. // 2. feature flag rolled-out. // 3. device is a watch. + // 4. button uses Theme.DeviceDefault. // getButtonDefaultStyleAttr and getButtonDefaultStyleRes works together to alter the UI // while considering the conditions above. // Their results are mutual exclusive. i.e. when conditions above are all true, @@ -229,6 +230,7 @@ public class Button extends TextView { private static boolean useWearMaterial3Style(Context context) { return Flags.useWearMaterial3Ui() && CompatChanges.isChangeEnabled(WEAR_MATERIAL3_BUTTON) - && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); + && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH) + && context.getThemeResId() == com.android.internal.R.style.Theme_DeviceDefault; } } diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 7faa5d702d6b..65e5679034c8 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -58,10 +58,13 @@ flag { } flag { - name: "enable_desktop_windowing_scvh_cache" + name: "enable_desktop_windowing_scvh_cache_bug_fix" namespace: "lse_desktop_experience" description: "Enables a SurfaceControlViewHost cache for window decorations" - bug: "345146928" + bug: "360452034" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index 0c56c677e06f..6ad7fef95357 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -26,6 +26,7 @@ import static android.system.OsConstants.S_IXGRP; import static android.system.OsConstants.S_IXOTH; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; @@ -177,6 +178,13 @@ public class NativeLibraryHelper { private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath, String abiToCopy, boolean extractNativeLibs, boolean debuggable); + private static native int nativeCheckAlignment( + long handle, + String sharedLibraryPath, + String abi, + boolean extractNativeLibs, + boolean debuggable); + private static long sumNativeBinaries(Handle handle, String abi) { long sum = 0; for (long apkHandle : handle.apkHandles) { @@ -432,6 +440,51 @@ public class NativeLibraryHelper { } } + /** + * Checks alignment of APK and native libraries for 16KB device + * + * @param handle APK file to scan for native libraries + * @param libraryRoot directory for libraries + * @param abiOverride abiOverride for package + * @return {@link Modes from ApplicationInfo.PageSizeAppCompat} if successful or error code + * which suggests undefined mode + */ + @ApplicationInfo.PageSizeAppCompatFlags + public static int checkAlignmentForCompatMode( + Handle handle, + String libraryRoot, + boolean nativeLibraryRootRequiresIsa, + String abiOverride) { + // Keep the code below in sync with copyNativeBinariesForSupportedAbi + int abi = findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS); + if (abi < 0) { + return ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + + final String supportedAbi = Build.SUPPORTED_64_BIT_ABIS[abi]; + final String instructionSet = VMRuntime.getInstructionSet(supportedAbi); + String subDir = libraryRoot; + if (nativeLibraryRootRequiresIsa) { + subDir += "/" + instructionSet; + } + + int mode = ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED; + for (long apkHandle : handle.apkHandles) { + int res = + nativeCheckAlignment( + apkHandle, + subDir, + Build.SUPPORTED_64_BIT_ABIS[abi], + handle.extractNativeLibs, + handle.debuggable); + if (res == ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ERROR) { + return res; + } + mode |= res; + } + return mode; + } + public static long sumNativeBinariesWithOverride(Handle handle, String abiOverride) throws IOException { long sum = 0; diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index eb6a81031321..429a6a267bb1 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -89,6 +89,8 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT, SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION, SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION, + SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED, + SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED, }) public @interface SoftInputShowHideReason { /** Default, undefined reason. */ @@ -337,6 +339,18 @@ public @interface SoftInputShowHideReason { int HIDE_WINDOW_LEGACY_DIRECT = ImeProtoEnums.REASON_HIDE_WINDOW_LEGACY_DIRECT; /** + * Show soft input because the input target changed + * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}. + */ + int SHOW_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_SHOW_INPUT_TARGET_CHANGED; + + /** + * Hide soft input because the input target changed by + * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}. + */ + int HIDE_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_HIDE_INPUT_TARGET_CHANGED; + + /** * Show / Hide soft input by * {@link android.inputmethodservice.InputMethodService#resetStateForNewConfiguration}. */ diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index fafa08536d6c..cd17ed89058a 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -201,6 +201,9 @@ public final class Zygote { */ public static final int DEBUG_ENABLE_PTRACE = 1 << 25; + /** Load 4KB ELF files on 16KB device using appcompat mode */ + public static final int ENABLE_PAGE_SIZE_APP_COMPAT = 1 << 26; + /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; /** Default external storage should be mounted. */ diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 2bfbf843b2ab..4b90420a75ee 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -16,6 +16,7 @@ package com.android.internal.widget; +import static android.app.Flags.notificationsRedesignTemplates; import static android.widget.flags.Flags.conversationLayoutUseMaximumChildHeight; import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_EXTERNAL; @@ -692,7 +693,7 @@ public class ConversationLayout extends FrameLayout } private void updateActionListPadding() { - if (mActions != null) { + if (!notificationsRedesignTemplates() && mActions != null) { mActions.setCollapsibleIndentDimen(R.dimen.call_notification_collapsible_indent); } } diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java index 301dc392c125..cac2024f548d 100644 --- a/core/java/com/android/internal/widget/NotificationActionListLayout.java +++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java @@ -20,6 +20,7 @@ import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT; import static android.app.Flags.evenlyDividedCallStyleActionLayout; import android.annotation.DimenRes; +import android.app.Flags; import android.app.Notification; import android.content.Context; import android.content.res.TypedArray; @@ -58,7 +59,7 @@ public class NotificationActionListLayout extends LinearLayout { private int mEmphasizedPaddingBottom; private int mEmphasizedHeight; private int mRegularHeight; - @DimenRes private int mCollapsibleIndentDimen = R.dimen.notification_actions_padding_start; + @DimenRes private int mCollapsibleIndentDimen; int mNumNotGoneChildren; int mNumPriorityChildren; @@ -73,6 +74,10 @@ public class NotificationActionListLayout extends LinearLayout { public NotificationActionListLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mCollapsibleIndentDimen = Flags.notificationsRedesignTemplates() + ? R.dimen.notification_2025_actions_margin_start + : R.dimen.notification_actions_padding_start; + int[] attrIds = { android.R.attr.gravity }; TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes); mGravity = ta.getInt(0, 0); diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index 7ad18b83f0d6..b2eeff36c007 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -21,6 +21,7 @@ #include <androidfw/ApkParsing.h> #include <androidfw/ZipFileRO.h> #include <androidfw/ZipUtils.h> +#include <elf.h> #include <errno.h> #include <fcntl.h> #include <inttypes.h> @@ -38,6 +39,7 @@ #include <memory> #include <string> +#include <vector> #include "com_android_internal_content_FileSystemUtils.h" #include "core_jni_helpers.h" @@ -60,6 +62,12 @@ enum install_status_t { NO_NATIVE_LIBRARIES = -114 }; +// These code should match with PageSizeAppCompatFlags inside ApplicationInfo.java +constexpr int PAGE_SIZE_APP_COMPAT_FLAG_ERROR = -1; +constexpr int PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED = 0; +constexpr int PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED = 1 << 1; +constexpr int PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED = 1 << 2; + typedef install_status_t (*iterFunc)(JNIEnv*, void*, ZipFileRO*, ZipEntryRO, const char*); static bool @@ -524,11 +532,7 @@ static inline bool app_compat_16kb_enabled() { static const size_t kPageSize = getpagesize(); // App compat is only applicable on 16kb-page-size devices. - if (kPageSize != 0x4000) { - return false; - } - - return android::base::GetBoolProperty("bionic.linker.16kb.app_compat.enabled", false); + return kPageSize == 0x4000; } static jint @@ -626,6 +630,166 @@ com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv *env, jclass, return reinterpret_cast<jlong>(zipFile); } +static jint checkLoadSegmentAlignment(const char* fileName, off64_t offset) { + std::vector<Elf64_Phdr> programHeaders; + if (!getLoadSegmentPhdrs(fileName, offset, programHeaders)) { + ALOGE("Failed to read program headers from ELF file."); + return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + + int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED; + for (auto programHeader : programHeaders) { + if (programHeader.p_type != PT_LOAD) { + continue; + } + + // Set ELF alignment bit if 4 KB aligned LOAD segment is found + if (programHeader.p_align == 0x1000) { + ALOGI("Setting page size compat mode PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED"); + mode |= PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED; + break; + } + } + + return mode; +} + +static jint checkExtractedLibAlignment(ZipFileRO* zipFile, ZipEntryRO zipEntry, + const char* fileName, const std::string nativeLibPath) { + // Build local file path + const size_t fileNameLen = strlen(fileName); + char localFileName[nativeLibPath.size() + fileNameLen + 2]; + + if (strlcpy(localFileName, nativeLibPath.c_str(), sizeof(localFileName)) != + nativeLibPath.size()) { + ALOGE("Couldn't allocate local file name for library"); + return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + + *(localFileName + nativeLibPath.size()) = '/'; + + if (strlcpy(localFileName + nativeLibPath.size() + 1, fileName, + sizeof(localFileName) - nativeLibPath.size() - 1) != fileNameLen) { + ALOGE("Couldn't allocate local file name for library"); + return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + + struct statfs64 fsInfo; + int result = statfs64(localFileName, &fsInfo); + if (result < 0) { + ALOGE("Failed to stat file :%s", localFileName); + return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + + return checkLoadSegmentAlignment(localFileName, 0); +} + +static jint checkAlignment(JNIEnv* env, jstring javaNativeLibPath, jboolean extractNativeLibs, + ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName) { + int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED; + // Need two separate install status for APK and ELF alignment + static const size_t kPageSize = getpagesize(); + jint ret = INSTALL_SUCCEEDED; + + ScopedUtfChars nativeLibPath(env, javaNativeLibPath); + if (extractNativeLibs) { + ALOGI("extractNativeLibs specified, checking for extracted lib %s", fileName); + return checkExtractedLibAlignment(zipFile, zipEntry, fileName, nativeLibPath.c_str()); + } + + uint16_t method; + off64_t offset; + if (!zipFile->getEntryInfo(zipEntry, &method, nullptr, nullptr, &offset, nullptr, nullptr, + nullptr)) { + ALOGE("Couldn't read zip entry info from zipFile %s", zipFile->getZipFileName()); + return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + + // check if library is uncompressed and page-aligned + if (method != ZipFileRO::kCompressStored) { + ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n", + fileName); + return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + + if (offset % kPageSize != 0) { + ALOGW("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly " + "from apk.\n", + fileName, kPageSize); + mode |= PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED; + ALOGI("Setting page size compat mode " + "PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED for %s", + zipFile->getZipFileName()); + } + + int loadMode = checkLoadSegmentAlignment(zipFile->getZipFileName(), offset); + if (loadMode == PAGE_SIZE_APP_COMPAT_FLAG_ERROR) { + return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + mode |= loadMode; + return mode; +} + +// TODO(b/371049373): This function is copy of iterateOverNativeFiles with different way of handling +// and combining return values for all ELF and APKs. Find a way to consolidate two functions. +static jint com_android_internal_content_NativeLibraryHelper_checkApkAlignment( + JNIEnv* env, jclass clazz, jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi, + jboolean extractNativeLibs, jboolean debuggable) { + int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED; + ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); + if (zipFile == nullptr) { + ALOGE("zipfile handle is null"); + return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + + auto result = NativeLibrariesIterator::create(zipFile, debuggable); + if (!result.ok()) { + ALOGE("Can't iterate over native libs for file:%s", zipFile->getZipFileName()); + return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + std::unique_ptr<NativeLibrariesIterator> it(std::move(result.value())); + + const ScopedUtfChars cpuAbi(env, javaCpuAbi); + if (cpuAbi.c_str() == nullptr) { + ALOGE("cpuAbi is nullptr"); + // This would've thrown, so this return code isn't observable by Java. + return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + + while (true) { + auto next = it->next(); + if (!next.ok()) { + ALOGE("next iterator not found Error:%d", next.error()); + return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + auto entry = next.value(); + if (entry == nullptr) { + break; + } + + const char* fileName = it->currentEntry(); + const char* lastSlash = it->lastSlash(); + + // Check to make sure the CPU ABI of this file is one we support. + const char* cpuAbiOffset = fileName + APK_LIB_LEN; + const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset; + + if (cpuAbi.size() == cpuAbiRegionSize && + !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) { + int ret = checkAlignment(env, javaNativeLibPath, extractNativeLibs, zipFile, entry, + lastSlash + 1); + if (ret == PAGE_SIZE_APP_COMPAT_FLAG_ERROR) { + ALOGE("Alignment check returned for zipfile: %s, entry:%s", + zipFile->getZipFileName(), lastSlash + 1); + return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + mode |= ret; + } + } + + return mode; +} + static void com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlong apkHandle) { @@ -633,29 +797,23 @@ com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlon } static const JNINativeMethod gMethods[] = { - {"nativeOpenApk", - "(Ljava/lang/String;)J", - (void *)com_android_internal_content_NativeLibraryHelper_openApk}, - {"nativeOpenApkFd", - "(Ljava/io/FileDescriptor;Ljava/lang/String;)J", - (void *)com_android_internal_content_NativeLibraryHelper_openApkFd}, - {"nativeClose", - "(J)V", - (void *)com_android_internal_content_NativeLibraryHelper_close}, - {"nativeCopyNativeBinaries", - "(JLjava/lang/String;Ljava/lang/String;ZZ)I", - (void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries}, - {"nativeSumNativeBinaries", - "(JLjava/lang/String;Z)J", - (void *)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries}, - {"nativeFindSupportedAbi", - "(J[Ljava/lang/String;Z)I", - (void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi}, - {"hasRenderscriptBitcode", "(J)I", - (void *)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode}, + {"nativeOpenApk", "(Ljava/lang/String;)J", + (void*)com_android_internal_content_NativeLibraryHelper_openApk}, + {"nativeOpenApkFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;)J", + (void*)com_android_internal_content_NativeLibraryHelper_openApkFd}, + {"nativeClose", "(J)V", (void*)com_android_internal_content_NativeLibraryHelper_close}, + {"nativeCopyNativeBinaries", "(JLjava/lang/String;Ljava/lang/String;ZZ)I", + (void*)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries}, + {"nativeSumNativeBinaries", "(JLjava/lang/String;Z)J", + (void*)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries}, + {"nativeFindSupportedAbi", "(J[Ljava/lang/String;Z)I", + (void*)com_android_internal_content_NativeLibraryHelper_findSupportedAbi}, + {"hasRenderscriptBitcode", "(J)I", + (void*)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode}, + {"nativeCheckAlignment", "(JLjava/lang/String;Ljava/lang/String;ZZ)I", + (void*)com_android_internal_content_NativeLibraryHelper_checkApkAlignment}, }; - int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env) { return RegisterMethodsOrDie(env, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 284c2997f9a9..aeaeeca3e845 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -89,6 +89,7 @@ #if defined(__BIONIC__) extern "C" void android_reset_stack_guards(); +extern "C" void android_set_16kb_appcompat_mode(bool enable_app_compat); #endif namespace { @@ -350,6 +351,7 @@ enum RuntimeFlags : uint32_t { NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23, PROFILEABLE = 1 << 24, DEBUG_ENABLE_PTRACE = 1 << 25, + ENABLE_PAGE_SIZE_APP_COMPAT = 1 << 26, }; enum UnsolicitedZygoteMessageTypes : uint32_t { @@ -2117,6 +2119,12 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn); + if ((runtime_flags & RuntimeFlags::ENABLE_PAGE_SIZE_APP_COMPAT) != 0) { + android_set_16kb_appcompat_mode(true); + // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART + // runtime. + runtime_flags &= ~RuntimeFlags::ENABLE_PAGE_SIZE_APP_COMPAT; + } __android_log_close(); AStatsSocket_close(); diff --git a/core/res/res/drawable-watch-v36/progress_ring_wear_material3.xml b/core/res/res/drawable-watch-v36/progress_ring_wear_material3.xml new file mode 100644 index 000000000000..5c0e5f606d81 --- /dev/null +++ b/core/res/res/drawable-watch-v36/progress_ring_wear_material3.xml @@ -0,0 +1,43 @@ +<?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. + --> + +<rotate xmlns:android="http://schemas.android.com/apk/res/android" + android:fromDegrees = "270" + android:toDegrees="270" + android:pivotX="50%" + android:pivotY="50%" > + <layer-list> + <item> + <shape + android:shape="ring" + android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio" + android:thickness="@dimen/progressbar_thickness" + android:useLevel="false"> + <solid android:color="?attr/materialColorSurfaceContainer"/> + </shape> + </item> + <item> + <shape + android:shape="ring" + android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio" + android:thickness="@dimen/progressbar_thickness" + android:useLevel="true"> + <solid android:color="?attr/materialColorPrimary"/> + </shape> + </item> + </layer-list> +</rotate>
\ No newline at end of file diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml index a790e5da20de..09c02c9994f4 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_base.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml @@ -20,7 +20,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" - android:minHeight="@dimen/notification_headerless_min_height" + android:minHeight="@dimen/notification_2025_min_height" android:tag="base" > @@ -72,8 +72,8 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" - android:layout_marginBottom="@dimen/notification_headerless_margin_twoline" - android:layout_marginTop="@dimen/notification_headerless_margin_twoline" + android:layout_marginBottom="@dimen/notification_2025_margin" + android:layout_marginTop="@dimen/notification_2025_margin" android:orientation="vertical" > @@ -81,7 +81,7 @@ android:id="@+id/notification_top_line" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:minHeight="@dimen/notification_headerless_line_height" + android:minHeight="@dimen/notification_2025_content_min_height" android:clipChildren="false" android:theme="@style/Theme.DeviceDefault.Notification" > diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml index 06f5f0651452..614444d6b2f0 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_call.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml @@ -32,7 +32,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="88dp" + android:minHeight="@dimen/notification_2025_min_height" android:orientation="horizontal" > @@ -43,8 +43,7 @@ android:layout_weight="1" android:layout_marginStart="@dimen/conversation_content_start" android:orientation="vertical" - android:minHeight="68dp" - android:paddingBottom="@dimen/notification_headerless_margin_twoline" + android:paddingBottom="@dimen/notification_2025_margin" > <include diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml index 427c4e42e40c..f539105368e7 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_media.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml @@ -23,7 +23,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="@dimen/notification_min_height" + android:minHeight="@dimen/notification_2025_min_height" android:tag="media" > @@ -74,8 +74,8 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" - android:layout_marginBottom="@dimen/notification_headerless_margin_twoline" - android:layout_marginTop="@dimen/notification_headerless_margin_twoline" + android:layout_marginBottom="@dimen/notification_2025_margin" + android:layout_marginTop="@dimen/notification_2025_margin" android:orientation="vertical" > diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml index f0e4c0f272fb..ddf3ebceaa46 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml @@ -38,7 +38,7 @@ <com.android.internal.widget.NotificationMaxHeightFrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="@dimen/notification_min_height" + android:minHeight="@dimen/notification_2025_min_height" android:clipChildren="false" > @@ -98,8 +98,8 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" - android:layout_marginBottom="@dimen/notification_headerless_margin_twoline" - android:layout_marginTop="@dimen/notification_headerless_margin_twoline" + android:layout_marginBottom="@dimen/notification_2025_margin" + android:layout_marginTop="@dimen/notification_2025_margin" android:layout_marginStart="@dimen/notification_2025_content_margin_start" android:clipChildren="false" android:orientation="vertical" diff --git a/core/res/res/layout/notification_2025_template_expanded_big_picture.xml b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml new file mode 100644 index 000000000000..18bafe068fcb --- /dev/null +++ b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml @@ -0,0 +1,93 @@ +<?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 + --> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:tag="bigPicture" + android:clipChildren="false" + > + + <include layout="@layout/notification_2025_template_header" /> + + <include layout="@layout/notification_template_right_icon" /> + + <LinearLayout + android:id="@+id/notification_action_list_margin_target" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="top" + android:layout_marginTop="@dimen/notification_content_margin_top" + android:clipToPadding="false" + android:orientation="vertical" + > + + <LinearLayout + android:id="@+id/notification_main_column" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:orientation="vertical" + > + + <include layout="@layout/notification_template_part_line1" /> + + <include + layout="@layout/notification_template_progress" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_progress_bar_height" + android:layout_marginTop="@dimen/notification_progress_margin_top" + /> + + <include layout="@layout/notification_template_text_multiline" /> + </LinearLayout> + + <com.android.internal.widget.BigPictureNotificationImageView + android:id="@+id/big_picture" + android:layout_width="match_parent" + android:layout_height="0dp" + android:adjustViewBounds="true" + android:layout_weight="1" + android:layout_marginTop="13dp" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:background="@drawable/notification_big_picture_outline" + android:clipToOutline="true" + android:scaleType="centerCrop" + /> + + <ViewStub + android:layout="@layout/notification_material_reply_text" + android:id="@+id/notification_2025_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + /> + + <include + layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginTop="@dimen/notification_content_margin" + /> + + <include layout="@layout/notification_material_action_list" /> + </LinearLayout> +</FrameLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_big_text.xml b/core/res/res/layout/notification_2025_template_expanded_big_text.xml new file mode 100644 index 000000000000..9ff141b7c946 --- /dev/null +++ b/core/res/res/layout/notification_2025_template_expanded_big_text.xml @@ -0,0 +1,94 @@ +<?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 + --> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:tag="bigText" + > + + <include layout="@layout/notification_2025_template_header" /> + + <com.android.internal.widget.RemeasuringLinearLayout + android:id="@+id/notification_action_list_margin_target" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:layout_marginTop="@dimen/notification_content_margin_top" + android:layout_marginBottom="@dimen/notification_content_margin" + android:clipToPadding="false" + android:orientation="vertical" + > + + <com.android.internal.widget.RemeasuringLinearLayout + android:id="@+id/notification_main_column" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:paddingStart="@dimen/notification_2025_content_margin_start" + android:paddingEnd="@dimen/notification_content_margin_end" + android:clipToPadding="false" + android:orientation="vertical" + android:layout_weight="1" + > + + <include layout="@layout/notification_template_part_line1" /> + + <include + layout="@layout/notification_template_progress" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_progress_bar_height" + android:layout_marginTop="@dimen/notification_progress_margin_top" + android:layout_marginBottom="6dp" + /> + + <com.android.internal.widget.ImageFloatingTextView + android:id="@+id/big_text" + style="@style/Widget.DeviceDefault.Notification.Text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_text_margin_top" + android:singleLine="false" + android:gravity="top" + android:visibility="gone" + android:textAlignment="viewStart" + /> + </com.android.internal.widget.RemeasuringLinearLayout> + + <ViewStub + android:layout="@layout/notification_material_reply_text" + android:id="@+id/notification_2025_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + /> + + <include + layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginTop="@dimen/notification_content_margin" + /> + + <include layout="@layout/notification_material_action_list" /> + </com.android.internal.widget.RemeasuringLinearLayout> + + <include layout="@layout/notification_template_right_icon" /> +</FrameLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_inbox.xml b/core/res/res/layout/notification_2025_template_expanded_inbox.xml new file mode 100644 index 000000000000..9fb44ccccfa0 --- /dev/null +++ b/core/res/res/layout/notification_2025_template_expanded_inbox.xml @@ -0,0 +1,131 @@ +<?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 + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:tag="inbox" + android:clipChildren="false" + > + <include layout="@layout/notification_2025_template_header" /> + <LinearLayout + android:id="@+id/notification_action_list_margin_target" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="top" + android:layout_marginTop="@dimen/notification_content_margin_top" + android:clipToPadding="false" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/notification_main_column" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:paddingStart="@dimen/notification_2025_content_margin_start" + android:paddingEnd="@dimen/notification_content_margin_end" + android:layout_weight="1" + android:clipToPadding="false" + android:orientation="vertical" + > + <include layout="@layout/notification_template_part_line1" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + <include layout="@layout/notification_template_progress" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_progress_bar_height" + android:layout_marginTop="@dimen/notification_progress_margin_top" + android:layout_marginBottom="2dp"/> + <TextView android:id="@+id/inbox_text0" + style="@style/Widget.DeviceDefault.Notification.Text" + android:layout_width="match_parent" + android:layout_height="0dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + <TextView android:id="@+id/inbox_text1" + style="@style/Widget.DeviceDefault.Notification.Text" + android:layout_width="match_parent" + android:layout_height="0dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + <TextView android:id="@+id/inbox_text2" + style="@style/Widget.DeviceDefault.Notification.Text" + android:layout_width="match_parent" + android:layout_height="0dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + <TextView android:id="@+id/inbox_text3" + style="@style/Widget.DeviceDefault.Notification.Text" + android:layout_width="match_parent" + android:layout_height="0dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + <TextView android:id="@+id/inbox_text4" + style="@style/Widget.DeviceDefault.Notification.Text" + android:layout_width="match_parent" + android:layout_height="0dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + <TextView android:id="@+id/inbox_text5" + style="@style/Widget.DeviceDefault.Notification.Text" + android:layout_width="match_parent" + android:layout_height="0dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + <TextView android:id="@+id/inbox_text6" + style="@style/Widget.DeviceDefault.Notification.Text" + android:layout_width="match_parent" + android:layout_height="0dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + </LinearLayout> + <ViewStub android:layout="@layout/notification_material_reply_text" + android:id="@+id/notification_2025_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + <include layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginTop="@dimen/notification_content_margin" /> + <include layout="@layout/notification_material_action_list" /> + </LinearLayout> + <include layout="@layout/notification_template_right_icon" /> +</FrameLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_media.xml b/core/res/res/layout/notification_2025_template_expanded_media.xml new file mode 100644 index 000000000000..578a0b2b6d0b --- /dev/null +++ b/core/res/res/layout/notification_2025_template_expanded_media.xml @@ -0,0 +1,103 @@ +<?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 + --> + +<!-- Note: This is the expanded version of the old media style notification (different from UMO). --> + +<!-- extends FrameLayout --> +<com.android.internal.widget.MediaNotificationView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:tag="bigMediaNarrow" + > + + <include layout="@layout/notification_2025_template_header" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:id="@+id/notification_media_content" + > + + <LinearLayout + android:id="@+id/notification_main_column" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_content_margin_top" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:orientation="vertical" + > + <include layout="@layout/notification_template_part_line1"/> + <include layout="@layout/notification_template_text"/> + </LinearLayout> + + <!-- this FrameLayout's minHeight serves as a padding for the content above --> + <FrameLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_2025_media_actions_margin_start" + android:minHeight="@dimen/notification_content_margin" + > + + <!-- Nesting in FrameLayout is required to ensure that the marginStart actually applies + at the start instead of always the left, given that the media_actions LinearLayout + has layoutDirection="ltr". --> + <LinearLayout + android:id="@+id/media_actions" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/media_notification_actions_padding_bottom" + android:gravity="top" + android:orientation="horizontal" + android:layoutDirection="ltr" + > + + <include + layout="@layout/notification_material_media_action" + android:id="@+id/action0" + /> + + <include + layout="@layout/notification_material_media_action" + android:id="@+id/action1" + /> + + <include + layout="@layout/notification_material_media_action" + android:id="@+id/action2" + /> + + <include + layout="@layout/notification_material_media_action" + android:id="@+id/action3" + /> + + <include + layout="@layout/notification_material_media_action" + android:id="@+id/action4" + /> + </LinearLayout> + + </FrameLayout> + + </LinearLayout> + + <include layout="@layout/notification_template_right_icon" /> + +</com.android.internal.widget.MediaNotificationView> diff --git a/core/res/res/layout/notification_2025_template_expanded_messaging.xml b/core/res/res/layout/notification_2025_template_expanded_messaging.xml new file mode 100644 index 000000000000..5b5872657a43 --- /dev/null +++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml @@ -0,0 +1,71 @@ +<?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 + --> + +<!-- extends FrameLayout --> +<com.android.internal.widget.MessagingLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipToPadding="false" + android:clipChildren="false" + android:tag="messaging" + > + + <include layout="@layout/notification_2025_template_header"/> + + <com.android.internal.widget.RemeasuringLinearLayout + android:id="@+id/notification_action_list_margin_target" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:layout_marginTop="@dimen/notification_content_margin_top" + android:clipChildren="false" + android:orientation="vertical"> + + <com.android.internal.widget.RemeasuringLinearLayout + android:id="@+id/notification_main_column" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:layout_weight="1" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:orientation="vertical" + android:clipChildren="false" + > + <com.android.internal.widget.MessagingLinearLayout + android:id="@+id/notification_messaging" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:spacing="@dimen/notification_messaging_spacing" /> + </com.android.internal.widget.RemeasuringLinearLayout> + + <include layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_content_margin" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" /> + + <include layout="@layout/notification_material_action_list" /> + + </com.android.internal.widget.RemeasuringLinearLayout> + + <include layout="@layout/notification_template_right_icon" /> + +</com.android.internal.widget.MessagingLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_progress.xml b/core/res/res/layout/notification_2025_template_expanded_progress.xml new file mode 100644 index 000000000000..afa4bc6dd7f8 --- /dev/null +++ b/core/res/res/layout/notification_2025_template_expanded_progress.xml @@ -0,0 +1,122 @@ +<?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 + --> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:clipChildren="false" + android:tag="progress" + > + + <LinearLayout + android:id="@+id/notification_action_list_margin_target" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/notification_content_margin" + android:orientation="vertical" + > + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_gravity="top" + > + + <include layout="@layout/notification_2025_template_header" /> + + <LinearLayout + android:id="@+id/notification_main_column" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginTop="@dimen/notification_content_margin_top" + android:orientation="vertical" + > + + <include layout="@layout/notification_template_part_line1" /> + + <include layout="@layout/notification_template_text_multiline" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:layout_marginTop="@dimen/notification_progress_margin_top" + android:orientation="horizontal"> + + <com.android.internal.widget.CachingIconView + android:id="@+id/notification_progress_start_icon" + android:layout_width="@dimen/notification_progress_icon_size" + android:layout_height="@dimen/notification_progress_icon_size" + android:background="@drawable/notification_progress_icon_background" + android:clipToOutline="true" + android:importantForAccessibility="no" + android:layout_marginEnd="@dimen/notification_progress_margin_horizontal" + android:scaleType="centerCrop" + android:maxDrawableWidth="@dimen/notification_progress_icon_size" + android:maxDrawableHeight="@dimen/notification_progress_icon_size" + /> + + + <include + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="@dimen/notification_progress_tracker_height" + layout="@layout/notification_template_notification_progress_bar" + /> + + <com.android.internal.widget.CachingIconView + android:id="@+id/notification_progress_end_icon" + android:layout_width="@dimen/notification_progress_icon_size" + android:layout_height="@dimen/notification_progress_icon_size" + android:background="@drawable/notification_progress_icon_background" + android:clipToOutline="true" + android:importantForAccessibility="no" + android:scaleType="centerCrop" + android:layout_marginStart="@dimen/notification_progress_margin_horizontal" + android:maxDrawableWidth="@dimen/notification_progress_icon_size" + android:maxDrawableHeight="@dimen/notification_progress_icon_size" + /> + </LinearLayout> + </LinearLayout> + + <include layout="@layout/notification_template_right_icon" /> + </FrameLayout> + + <ViewStub + android:layout="@layout/notification_material_reply_text" + android:id="@+id/notification_2025_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + /> + + <include + layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginTop="@dimen/notification_content_margin" + /> + + <include layout="@layout/notification_material_action_list" /> + </LinearLayout> +</FrameLayout>
\ No newline at end of file diff --git a/core/res/res/values-watch-v36/dimens_material.xml b/core/res/res/values-watch-v36/dimens_material.xml index ffa3b9c614db..7232786d92c9 100644 --- a/core/res/res/values-watch-v36/dimens_material.xml +++ b/core/res/res/values-watch-v36/dimens_material.xml @@ -31,4 +31,9 @@ <!-- 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> + + <!-- values for material3 progress bar(progress indicator) --> + <item name="progressbar_inner_radius_ratio" format="float" type="dimen">2.12</item> + <dimen name="progressbar_thickness">8dp</dimen> + <dimen name="progressbar_elevation">0.1dp</dimen> </resources> diff --git a/core/res/res/values-watch-v36/styles_material.xml b/core/res/res/values-watch-v36/styles_material.xml index 7da7435930b1..20f40957a6a2 100644 --- a/core/res/res/values-watch-v36/styles_material.xml +++ b/core/res/res/values-watch-v36/styles_material.xml @@ -79,4 +79,13 @@ <item name="maxWidth">@dimen/dialog_btn_negative_width</item> <item name="maxHeight">@dimen/dialog_btn_negative_height</item> </style> + + <!-- Wear Material3 Progress Bar style: progressed ring.--> + <style name="Widget.DeviceDefault.ProgressBar.WearMaterial3"> + <item name="indeterminateOnly">false</item> + <item name="progressDrawable">@drawable/progress_ring_wear_material3</item> + <item name="minHeight">@dimen/progress_bar_height</item> + <item name="maxHeight">@dimen/progress_bar_height</item> + <item name="mirrorForRtl">true</item> + </style> </resources>
\ No newline at end of file diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index c8df662eed6d..f53acbfac71d 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -255,6 +255,19 @@ This represents 16dp for the left margin + 40dp for the icon + 16dp for the right margin --> <dimen name="notification_2025_content_margin_start">72dp</dimen> + <!-- The margin on the start of the media actions, selected to ensure that action icons which + are visually 12x12 in a 24x24 drawable will align correctly with the text. This means that + stock media action icons will align, but icons may be visually up to 20x20 and remain in-spec, + in which case they will protrude into the start column slightly. + 72dp (content margin) - 8dp (media action padding) - 6dp (visual padding within drawable) --> + <dimen name="notification_2025_media_actions_margin_start">58dp</dimen> + + <!-- The margin on the start of notification actions (2025 redesign version), to align them to + the rest of the notification content. Note that this can be set to 0 if the actions would not + fit with it included. + 72dp (content margin) - 12dp (action padding) - 4dp (button inset) --> + <dimen name="notification_2025_actions_margin_start">56dp</dimen> + <!-- The margin on the end of most content views (ignores the expander) --> <dimen name="notification_content_margin_end">16dp</dimen> @@ -377,6 +390,9 @@ <!-- the size of the notification close button --> <dimen name="notification_close_button_size">16dp</dimen> + <!-- Margin for all notification content --> + <dimen name="notification_2025_margin">16dp</dimen> + <!-- Vertical margin for the headerless notification content, when content has 1 line --> <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) --> <dimen name="notification_headerless_margin_oneline">16dp</dimen> @@ -388,10 +404,19 @@ <!-- The height of each of the 1 or 2 lines in the headerless notification template --> <dimen name="notification_headerless_line_height">24dp</dimen> - <!-- vertical margin for the headerless notification content --> + <!-- The minimum height of the notification content (even when there's only one line of text) --> + <dimen name="notification_2025_content_min_height">40dp</dimen> + + <!-- Height of a headerless notification with one or two lines --> + <!-- 16 * 2 (margins) + 40 (min content height) = 72 (notification) --> + <dimen name="notification_2025_min_height">72dp</dimen> + + <!-- Height of a headerless notification with one line --> + <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) --> <dimen name="notification_headerless_min_height">56dp</dimen> - <!-- Height of a small notification in the status bar --> + <!-- Height of a small two-line notification --> + <!-- 20 * 2 (margins) + 24 * 2 (2 lines) = 88 (notification) --> <dimen name="notification_min_height">88dp</dimen> <!-- The width of the big icons in notifications. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4f029cdcacc9..748f4b38468b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2396,6 +2396,12 @@ <java-symbol type="layout" name="notification_2025_template_header" /> <java-symbol type="layout" name="notification_2025_template_collapsed_messaging" /> <java-symbol type="layout" name="notification_2025_template_collapsed_media" /> + <java-symbol type="layout" name="notification_2025_template_expanded_big_picture" /> + <java-symbol type="layout" name="notification_2025_template_expanded_inbox" /> + <java-symbol type="layout" name="notification_2025_template_expanded_media" /> + <java-symbol type="layout" name="notification_2025_template_expanded_big_text" /> + <java-symbol type="layout" name="notification_2025_template_expanded_messaging" /> + <java-symbol type="layout" name="notification_2025_template_expanded_progress" /> <java-symbol type="layout" name="notification_template_material_base" /> <java-symbol type="layout" name="notification_template_material_heads_up_base" /> <java-symbol type="layout" name="notification_template_material_compact_heads_up_base" /> @@ -2407,6 +2413,8 @@ <java-symbol type="layout" name="notification_template_material_big_media" /> <java-symbol type="layout" name="notification_template_material_big_text" /> <java-symbol type="layout" name="notification_template_material_progress" /> + <java-symbol type="layout" name="notification_template_material_messaging" /> + <java-symbol type="layout" name="notification_template_material_big_messaging" /> <java-symbol type="layout" name="notification_template_header" /> <java-symbol type="layout" name="notification_material_media_action" /> <java-symbol type="color" name="notification_progress_background_color" /> @@ -3346,8 +3354,6 @@ <java-symbol type="bool" name="config_strongAuthRequiredOnBoot" /> <java-symbol type="layout" name="app_anr_dialog" /> - <java-symbol type="layout" name="notification_template_material_messaging" /> - <java-symbol type="layout" name="notification_template_material_big_messaging" /> <java-symbol type="id" name="aerr_wait" /> @@ -3475,6 +3481,7 @@ <java-symbol type="bool" name="config_supportPreRebootSecurityLogs" /> + <java-symbol type="dimen" name="notification_2025_actions_margin_start"/> <java-symbol type="id" name="notification_action_list_margin_target" /> <java-symbol type="dimen" name="notification_actions_padding_start"/> <java-symbol type="dimen" name="notification_actions_collapsed_priority_width"/> diff --git a/core/res/res/xml/bookmarks.xml b/core/res/res/xml/bookmarks.xml index 17860ef6d9f2..e735784ee5bb 100644 --- a/core/res/res/xml/bookmarks.xml +++ b/core/res/res/xml/bookmarks.xml @@ -20,10 +20,14 @@ Typical shortcuts (not necessarily defined here): 'b': Browser - 'p': Contacts + 'c': Contacts 'e': Email - 'c': Calendar + 'g': GMail + 'k': Calendar 'm': Maps + 'p': Music + 's': SMS + 't': Talk 'u': Calculator 'y': YouTube --> @@ -34,7 +38,7 @@ androidprv:modifierState="META" /> <bookmark category="android.intent.category.APP_CONTACTS" - androidprv:keycode="KEYCODE_P" + androidprv:keycode="KEYCODE_C" androidprv:modifierState="META" /> <bookmark category="android.intent.category.APP_EMAIL" @@ -42,13 +46,21 @@ androidprv:modifierState="META" /> <bookmark category="android.intent.category.APP_CALENDAR" - androidprv:keycode="KEYCODE_C" + androidprv:keycode="KEYCODE_K" androidprv:modifierState="META" /> <bookmark category="android.intent.category.APP_MAPS" androidprv:keycode="KEYCODE_M" androidprv:modifierState="META" /> <bookmark + category="android.intent.category.APP_MUSIC" + androidprv:keycode="KEYCODE_P" + androidprv:modifierState="META" /> + <bookmark + role="android.app.role.SMS" + androidprv:keycode="KEYCODE_S" + androidprv:modifierState="META" /> + <bookmark category="android.intent.category.APP_CALCULATOR" androidprv:keycode="KEYCODE_U" androidprv:modifierState="META" /> diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java index 7bc4abd935b6..fdfb0c34cdeb 100644 --- a/core/tests/coretests/src/android/content/IntentTest.java +++ b/core/tests/coretests/src/android/content/IntentTest.java @@ -19,8 +19,9 @@ package android.content; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; -import android.net.Uri; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; @@ -29,6 +30,7 @@ import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.security.Flags; +import android.util.ArraySet; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -40,6 +42,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; /** * Build/Install/Run: @@ -51,7 +54,6 @@ import java.util.List; public class IntentTest { private static final String TEST_ACTION = "android.content.IntentTest_test"; private static final String TEST_EXTRA_NAME = "testExtraName"; - private static final Uri TEST_URI = Uri.parse("content://com.example/people"); @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @@ -129,4 +131,111 @@ public class IntentTest { } } + @Test + @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT) + public void testFillInCreatorTokenInfo() { + // case 1: intent does not have creatorTokenInfo; fillinIntent contains creatorTokenInfo + Intent intent = new Intent(); + Intent fillInIntent = new Intent(); + fillInIntent.setCreatorToken(new Binder()); + fillInIntent.putExtra("extraKey", new Intent()); + + fillInIntent.collectExtraIntentKeys(); + intent.fillIn(fillInIntent, 0); + + // extra intent keys are merged + assertThat(intent.getExtraIntentKeys()).isEqualTo(fillInIntent.getExtraIntentKeys()); + // but creator token is not overwritten. + assertThat(intent.getCreatorToken()).isNull(); + + + // case 2: Both intent and fillInIntent contains creatorToken, intent's creatorToken is not + // overwritten. + intent = new Intent(); + IBinder creatorToken = new Binder(); + intent.setCreatorToken(creatorToken); + fillInIntent = new Intent(); + fillInIntent.setCreatorToken(new Binder()); + + intent.fillIn(fillInIntent, 0); + + assertThat(intent.getCreatorToken()).isEqualTo(creatorToken); + + + // case 3: Contains duplicate extra keys + intent = new Intent(); + intent.putExtra("key1", new Intent()); + intent.putExtra("key2", new Intent()); + fillInIntent = new Intent(); + fillInIntent.putExtra("key1", new Intent()); + fillInIntent.putExtra("key3", new Intent()); + + intent.collectExtraIntentKeys(); + Set originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys()); + + fillInIntent.collectExtraIntentKeys(); + intent.fillIn(fillInIntent, 0); + + assertThat(intent.getExtraIntentKeys()).hasSize(3); + assertTrue(intent.getExtraIntentKeys().containsAll(originalIntentKeys)); + assertTrue(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys())); + + + // case 4: Both contains a mixture of extras and clip data. NOT force to fill in clip data. + intent = new Intent(); + ClipData clipData = ClipData.newIntent("clip", new Intent()); + clipData.addItem(new ClipData.Item(new Intent())); + intent.setClipData(clipData); + intent.putExtra("key1", new Intent()); + intent.putExtra("key2", new Intent()); + fillInIntent = new Intent(); + ClipData fillInClipData = ClipData.newIntent("clip", new Intent()); + fillInClipData.addItem(new ClipData.Item(new Intent())); + fillInClipData.addItem(new ClipData.Item(new Intent())); + fillInIntent.setClipData(fillInClipData); + fillInIntent.putExtra("key1", new Intent()); + fillInIntent.putExtra("key3", new Intent()); + + intent.collectExtraIntentKeys(); + originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys()); + fillInIntent.collectExtraIntentKeys(); + intent.fillIn(fillInIntent, 0); + + // size is 5 ( 3 extras merged from both + 2 clip data in the original. + assertThat(intent.getExtraIntentKeys()).hasSize(5); + // all keys from original are kept. + assertTrue(intent.getExtraIntentKeys().containsAll(originalIntentKeys)); + // Not all keys from fillInIntent are kept - clip data keys are dropped. + assertFalse(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys())); + + + // case 5: Both contains a mixture of extras and clip data. Force to fill in clip data. + intent = new Intent(); + clipData = ClipData.newIntent("clip", new Intent()); + clipData.addItem(new ClipData.Item(new Intent())); + clipData.addItem(new ClipData.Item(new Intent())); + clipData.addItem(new ClipData.Item(new Intent())); + intent.setClipData(clipData); + intent.putExtra("key1", new Intent()); + intent.putExtra("key2", new Intent()); + fillInIntent = new Intent(); + fillInClipData = ClipData.newIntent("clip", new Intent()); + fillInClipData.addItem(new ClipData.Item(new Intent())); + fillInClipData.addItem(new ClipData.Item(new Intent())); + fillInIntent.setClipData(fillInClipData); + fillInIntent.putExtra("key1", new Intent()); + fillInIntent.putExtra("key3", new Intent()); + + intent.collectExtraIntentKeys(); + originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys()); + fillInIntent.collectExtraIntentKeys(); + intent.fillIn(fillInIntent, Intent.FILL_IN_CLIP_DATA); + + // size is 6 ( 3 extras merged from both + 3 clip data in the fillInIntent. + assertThat(intent.getExtraIntentKeys()).hasSize(6); + // all keys from fillInIntent are kept. + assertTrue(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys())); + // Not all keys from intent are kept - clip data keys are dropped. + assertFalse(intent.getExtraIntentKeys().containsAll(originalIntentKeys)); + } } diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java index b60d61408054..c55008e4aba5 100644 --- a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java @@ -24,6 +24,9 @@ import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import java.lang.reflect.Modifier; +import java.util.Arrays; + @RunWith(AndroidJUnit4.class) @SmallTest public class PackageManagerTest { @@ -46,4 +49,25 @@ public class PackageManagerTest { public void testResolveInfoFlags() throws Exception { assertThat(PackageManager.ResolveInfoFlags.of(42L).getValue()).isEqualTo(42L); } + + @Test + public void testSdkFeatureCount() throws Exception { + // Check to make sure the system feature `SdkConst` annotation processor yields sensible + // results. We don't care about the exactness, just that it's not pathologically wrong. + assertThat(PackageManager.SDK_FEATURE_COUNT).isGreaterThan(150); + assertThat(PackageManager.SDK_FEATURE_COUNT).isLessThan(500); + assertThat(PackageManager.SDK_FEATURE_COUNT) + .isWithin(50) + .of(getApproximateFeatureCountUsingReflection()); + } + + /* Return a ballpark estimate of the feature count using FEATURE_ field names. */ + private static int getApproximateFeatureCountUsingReflection() { + return (int) + Arrays.stream(PackageManager.class.getFields()) + .filter(field -> Modifier.isStatic(field.getModifiers())) + .filter(field -> Modifier.isFinal(field.getModifiers())) + .filter(field -> field.getName().startsWith("FEATURE_")) + .count(); + } } diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java index 549e66658b85..30f6636766d5 100644 --- a/core/tests/coretests/src/android/os/MessageQueueTest.java +++ b/core/tests/coretests/src/android/os/MessageQueueTest.java @@ -26,262 +26,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -@Suppress // Failing. @RunWith(AndroidJUnit4.class) public class MessageQueueTest { - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule(); - private static class BaseTestHandler extends TestHandlerThread { - Handler mHandler; - int mLastMessage; - int mCount; - - public BaseTestHandler() { - } - - public void go() { - mHandler = new Handler() { - public void handleMessage(Message msg) { - BaseTestHandler.this.handleMessage(msg); - } - }; - } - - public void handleMessage(Message msg) { - if (!msg.isInUse()) { - failure(new RuntimeException( - "msg.isInuse is false, should always be true, #" + msg.what)); - } - if (mCount <= mLastMessage) { - if (msg.what != mCount) { - failure(new RuntimeException( - "Expected message #" + mCount - + ", received #" + msg.what)); - } else if (mCount == mLastMessage) { - success(); - } - mCount++; - } else { - failure(new RuntimeException( - "Message received after done, #" + msg.what)); - } - } - } - - @Test - @MediumTest - public void testMessageOrder() throws Exception { - TestHandlerThread tester = new BaseTestHandler() { - public void go() { - super.go(); - long now = SystemClock.uptimeMillis() + 200; - mLastMessage = 4; - mCount = 0; - mHandler.sendMessageAtTime(mHandler.obtainMessage(2), now + 1); - mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now + 2); - mHandler.sendMessageAtTime(mHandler.obtainMessage(4), now + 2); - mHandler.sendMessageAtTime(mHandler.obtainMessage(0), now + 0); - mHandler.sendMessageAtTime(mHandler.obtainMessage(1), now + 0); - } - }; - - tester.doTest(1000); - } - - @Test - @MediumTest - public void testAtFrontOfQueue() throws Exception { - TestHandlerThread tester = new BaseTestHandler() { - public void go() { - super.go(); - long now = SystemClock.uptimeMillis() + 200; - mLastMessage = 3; - mCount = 0; - mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now); - mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(2)); - mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(0)); - } - - public void handleMessage(Message msg) { - super.handleMessage(msg); - if (msg.what == 0) { - mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(1)); - } - } - }; - - tester.doTest(1000); - } - - private static class TestFieldIntegrityHandler extends TestHandlerThread { - Handler mHandler; - int mLastMessage; - int mCount; - - public TestFieldIntegrityHandler() { - } - - public void go() { - mHandler = new Handler() { - public void handleMessage(Message msg) { - TestFieldIntegrityHandler.this.handleMessage(msg); - } - }; - } - - public void handleMessage(Message msg) { - if (!msg.isInUse()) { - failure(new RuntimeException( - "msg.isInuse is false, should always be true, #" + msg.what)); - } - if (mCount <= mLastMessage) { - if (msg.what != mCount) { - failure(new RuntimeException( - "Expected message #" + mCount - + ", received #" + msg.what)); - } else if (mCount == mLastMessage) { - success(); - } - mCount++; - } else { - failure(new RuntimeException( - "Message received after done, #" + msg.what)); - } - } - } - - @Test - @MediumTest - public void testFieldIntegrity() throws Exception { - - TestHandlerThread tester = new TestFieldIntegrityHandler() { - Bundle mBundle; - - public void go() { - super.go(); - mLastMessage = 1; - mCount = 0; - mHandler.sendMessage(mHandler.obtainMessage(0)); - } - - public void handleMessage(Message msg) { - super.handleMessage(msg); - if (msg.what == 0) { - msg.flags = Message.FLAGS_TO_CLEAR_ON_COPY_FROM; - msg.what = 1; - msg.arg1 = 456; - msg.arg2 = 789; - msg.obj = this; - msg.replyTo = null; - mBundle = new Bundle(); - msg.data = mBundle; - msg.data.putString("key", "value"); - - Message newMsg = mHandler.obtainMessage(); - newMsg.copyFrom(msg); - if (newMsg.isInUse() != false) { - failure(new RuntimeException( - "newMsg.isInUse is true should be false after copyFrom")); - } - if (newMsg.flags != 0) { - failure(new RuntimeException(String.format( - "newMsg.flags is %d should be 0 after copyFrom", newMsg.flags))); - } - if (newMsg.what != 1) { - failure(new RuntimeException(String.format( - "newMsg.what is %d should be %d after copyFrom", newMsg.what, 1))); - } - if (newMsg.arg1 != 456) { - failure(new RuntimeException(String.format( - "newMsg.arg1 is %d should be %d after copyFrom", msg.arg1, 456))); - } - if (newMsg.arg2 != 789) { - failure(new RuntimeException(String.format( - "newMsg.arg2 is %d should be %d after copyFrom", msg.arg2, 789))); - } - if (newMsg.obj != this) { - failure(new RuntimeException( - "newMsg.obj should be 'this' after copyFrom")); - } - if (newMsg.replyTo != null) { - failure(new RuntimeException( - "newMsg.replyTo should be null after copyFrom")); - } - if (newMsg.data == mBundle) { - failure(new RuntimeException( - "newMsg.data should NOT be mBundle after copyFrom")); - } - if (!newMsg.data.getString("key").equals(mBundle.getString("key"))) { - failure(new RuntimeException(String.format( - "newMsg.data.getString(\"key\") is %s and does not equal" + - " mBundle.getString(\"key\") which is %s after copyFrom", - newMsg.data.getString("key"), mBundle.getString("key")))); - } - if (newMsg.when != 0) { - failure(new RuntimeException(String.format( - "newMsg.when is %d should be 0 after copyFrom", newMsg.when))); - } - if (newMsg.target != mHandler) { - failure(new RuntimeException( - "newMsg.target is NOT mHandler after copyFrom")); - } - if (newMsg.callback != null) { - failure(new RuntimeException( - "newMsg.callback is NOT null after copyFrom")); - } - - mHandler.sendMessage(newMsg); - } else if (msg.what == 1) { - if (msg.isInUse() != true) { - failure(new RuntimeException(String.format( - "msg.isInUse is false should be true after when processing %d", - msg.what))); - } - if (msg.arg1 != 456) { - failure(new RuntimeException(String.format( - "msg.arg1 is %d should be %d when processing # %d", - msg.arg1, 456, msg.what))); - } - if (msg.arg2 != 789) { - failure(new RuntimeException(String.format( - "msg.arg2 is %d should be %d when processing # %d", - msg.arg2, 789, msg.what))); - } - if (msg.obj != this) { - failure(new RuntimeException(String.format( - "msg.obj should be 'this' when processing # %d", msg.what))); - } - if (msg.replyTo != null) { - failure(new RuntimeException(String.format( - "msg.replyTo should be null when processing # %d", msg.what))); - } - if (!msg.data.getString("key").equals(mBundle.getString("key"))) { - failure(new RuntimeException(String.format( - "msg.data.getString(\"key\") is %s and does not equal" + - " mBundle.getString(\"key\") which is %s when processing # %d", - msg.data.getString("key"), mBundle.getString("key"), msg.what))); - } - if (msg.when != 0) { - failure(new RuntimeException(String.format( - "msg.when is %d should be 0 when processing # %d", - msg.when, msg.what))); - } - if (msg.target != null) { - failure(new RuntimeException(String.format( - "msg.target is NOT null when processing # %d", msg.what))); - } - if (msg.callback != null) { - failure(new RuntimeException(String.format( - "msg.callback is NOT null when processing # %d", msg.what))); - } - } else { - failure(new RuntimeException(String.format( - "Unexpected msg.what is %d" + msg.what))); - } - } - }; - - tester.doTest(1000); - } } diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java index e4e965f1cadb..b8d1979e99b8 100644 --- a/core/tests/coretests/src/android/os/PowerManagerTest.java +++ b/core/tests/coretests/src/android/os/PowerManagerTest.java @@ -30,11 +30,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.InstrumentationRegistry; @@ -54,7 +53,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; @RunWith(AndroidJUnit4.class) -@IgnoreUnderRavenwood(blockedBy = PowerManager.class) +@DisabledOnRavenwood(blockedBy = PowerManager.class) public class PowerManagerTest { private static final String TAG = "PowerManagerTest"; @@ -83,19 +82,14 @@ public class PowerManagerTest { String[] keys, String[] values); static { - if (!RavenwoodRule.isUnderRavenwood()) { + if (!RavenwoodRule.isOnRavenwood()) { System.loadLibrary("powermanagertest_jni"); } } - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule(); - // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect. @Rule - public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood() - ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() - : DeviceFlagsValueProvider.createCheckFlagsRule(); + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); /** * Setup any common data for the upcoming tests. diff --git a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java index 58a434a5052a..a04a6625458c 100644 --- a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java +++ b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java @@ -18,12 +18,10 @@ package android.os; import static org.junit.Assert.assertThrows; -import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; -import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -34,16 +32,11 @@ import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) -@IgnoreUnderRavenwood(blockedBy = WorkDuration.class) +@DisabledOnRavenwood(blockedBy = WorkDuration.class) public class WorkDurationUnitTest { - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule(); - // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect. @Rule - public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood() - ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() - : DeviceFlagsValueProvider.createCheckFlagsRule(); + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Before public void setUp() { diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index dcaab8e645ac..897fc543517e 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -607,8 +607,6 @@ applications that come with the platform <!-- Permission required for CTS test - IntrusionDetectionManagerTest --> <permission name="android.permission.READ_INTRUSION_DETECTION_STATE" /> <permission name="android.permission.MANAGE_INTRUSION_DETECTION_STATE" /> - <!-- Permissions required for CTS test - BugreportManagerTest --> - <permission name="android.permission.CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt index 6339a8703f01..087378bef6a1 100644 --- a/framework-jarjar-rules.txt +++ b/framework-jarjar-rules.txt @@ -4,7 +4,6 @@ rule android.hidl.** android.internal.hidl.@1 # Framework-specific renames. rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1 -rule com.android.server.vcn.util.** com.android.server.vcn.repackaged.util.@1 # for modules-utils-build dependency rule com.android.modules.utils.build.** android.internal.modules.utils.build.@1 diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml index 3dbf7542ac6e..fcf74e3c1936 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml @@ -46,15 +46,19 @@ <TextView android:id="@+id/application_name" android:layout_width="0dp" - android:layout_height="20dp" - android:maxWidth="86dp" + android:layout_height="wrap_content" + android:maxWidth="130dp" android:textAppearance="@android:style/TextAppearance.Material.Title" android:textSize="14sp" android:textFontWeight="500" - android:lineHeight="20dp" + android:lineHeight="20sp" android:layout_gravity="center_vertical" android:layout_weight="1" android:layout_marginStart="8dp" + android:singleLine="true" + android:ellipsize="none" + android:requiresFadingEdge="horizontal" + android:fadingEdgeLength="28dp" android:clickable="false" android:focusable="false" tools:text="Gmail"/> 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 b82496e45415..3b53c3fbe03f 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 @@ -274,8 +274,11 @@ public class BubbleController implements ConfigurationChangeListener, private final DragAndDropController mDragAndDropController; /** Used to send bubble events to launcher. */ private Bubbles.BubbleStateListener mBubbleStateListener; - /** Used to track previous navigation mode to detect switch to buttons navigation. */ - private boolean mIsPrevNavModeGestures; + /** + * Used to track previous navigation mode to detect switch to buttons navigation. Set to + * true to switch the bubble bar to the opposite side for 3 nav buttons mode on device boot. + */ + private boolean mIsPrevNavModeGestures = true; /** Used to send updates to the views from {@link #mBubbleDataListener}. */ private BubbleViewCallback mBubbleViewCallback; @@ -357,7 +360,6 @@ public class BubbleController implements ConfigurationChangeListener, } }; mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this); - mIsPrevNavModeGestures = ContextUtils.isGestureNavigationMode(mContext); } private void registerOneHandedState(OneHandedController oneHanded) { @@ -593,9 +595,9 @@ public class BubbleController implements ConfigurationChangeListener, if (mBubbleStateListener != null) { boolean isCurrentNavModeGestures = ContextUtils.isGestureNavigationMode(mContext); if (mIsPrevNavModeGestures && !isCurrentNavModeGestures) { - BubbleBarLocation navButtonsLocation = ContextUtils.isRtl(mContext) + BubbleBarLocation bubbleBarLocation = ContextUtils.isRtl(mContext) ? BubbleBarLocation.RIGHT : BubbleBarLocation.LEFT; - mBubblePositioner.setBubbleBarLocation(navButtonsLocation); + mBubblePositioner.setBubbleBarLocation(bubbleBarLocation); } mIsPrevNavModeGestures = isCurrentNavModeGestures; BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index 38087c066918..38b859220256 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -403,8 +403,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged @Override // TODO(b/335404678): pass control target - public void setImeInputTargetRequestedVisibility(boolean visible) { + public void setImeInputTargetRequestedVisibility(boolean visible, + @NonNull ImeTracker.Token statsToken) { if (android.view.inputmethod.Flags.refactorInsetsController()) { + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE); mImeRequestedVisible = visible; dispatchImeRequested(mDisplayId, mImeRequestedVisible); @@ -414,21 +417,21 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged // therefore have to start the show animation from here startAnimation(mImeRequestedVisible /* show */, false /* forceRestart */); - setVisibleDirectly(mImeRequestedVisible || mAnimation != null); + setVisibleDirectly(mImeRequestedVisible || mAnimation != null, statsToken); } } /** * Sends the local visibility state back to window manager. Needed for legacy adjustForIme. */ - private void setVisibleDirectly(boolean visible) { + private void setVisibleDirectly(boolean visible, @Nullable ImeTracker.Token statsToken) { mInsetsState.setSourceVisible(InsetsSource.ID_IME, visible); mRequestedVisibleTypes = visible ? mRequestedVisibleTypes | WindowInsets.Type.ime() : mRequestedVisibleTypes & ~WindowInsets.Type.ime(); try { mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId, - mRequestedVisibleTypes); + mRequestedVisibleTypes, statsToken); } catch (RemoteException e) { } } @@ -640,7 +643,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged t.hide(animatingLeash); removeImeSurface(mDisplayId); if (android.view.inputmethod.Flags.refactorInsetsController()) { - setVisibleDirectly(false /* visible */); + setVisibleDirectly(false /* visible */, statsToken); } ImeTracker.forLogging().onHidden(mStatsToken); } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) { @@ -669,13 +672,13 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged if (!android.view.inputmethod.Flags.refactorInsetsController() && !show) { // When going away, queue up insets change first, otherwise any bounds changes // can have a "flicker" of ime-provided insets. - setVisibleDirectly(false /* visible */); + setVisibleDirectly(false /* visible */, null /* statsToken */); } mAnimation.start(); if (!android.view.inputmethod.Flags.refactorInsetsController() && show) { // When showing away, queue up insets change last, otherwise any bounds changes // can have a "flicker" of ime-provided insets. - setVisibleDirectly(true /* visible */); + setVisibleDirectly(true /* visible */, null /* statsToken */); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java index c4c177cbcc28..c45f09be8430 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java @@ -16,6 +16,7 @@ package com.android.wm.shell.common; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.os.RemoteException; @@ -223,13 +224,14 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan } } - private void setImeInputTargetRequestedVisibility(boolean visible) { + private void setImeInputTargetRequestedVisibility(boolean visible, + @NonNull ImeTracker.Token statsToken) { CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId); if (listeners == null) { return; } for (OnInsetsChangedListener listener : listeners) { - listener.setImeInputTargetRequestedVisibility(visible); + listener.setImeInputTargetRequestedVisibility(visible, statsToken); } } @@ -276,10 +278,11 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan } @Override - public void setImeInputTargetRequestedVisibility(boolean visible) + public void setImeInputTargetRequestedVisibility(boolean visible, + @NonNull ImeTracker.Token statsToken) throws RemoteException { mMainExecutor.execute(() -> { - PerDisplay.this.setImeInputTargetRequestedVisibility(visible); + PerDisplay.this.setImeInputTargetRequestedVisibility(visible, statsToken); }); } } @@ -345,7 +348,10 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan * Called to set the requested visibility of the IME in DisplayImeController. Invoked by * {@link com.android.server.wm.DisplayContent.RemoteInsetsControlTarget}. * @param visible requested status of the IME + * @param statsToken the token tracking the current IME request */ - default void setImeInputTargetRequestedVisibility(boolean visible) {} + default void setImeInputTargetRequestedVisibility(boolean visible, + @NonNull ImeTracker.Token statsToken) { + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index c99d9ba862c1..9d4b4bbb33de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -54,6 +54,7 @@ import com.android.wm.shell.compatui.api.CompatUIEvent; import com.android.wm.shell.compatui.api.CompatUIHandler; import com.android.wm.shell.compatui.api.CompatUIInfo; import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -65,6 +66,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -194,7 +196,7 @@ public class CompatUIController implements OnDisplaysChangedListener, private final CompatUIStatusManager mCompatUIStatusManager; @NonNull - private final IntPredicate mInDesktopModePredicate; + private final Optional<DesktopUserRepositories> mDesktopUserRepositories; public CompatUIController(@NonNull Context context, @NonNull ShellInit shellInit, @@ -210,7 +212,7 @@ public class CompatUIController implements OnDisplaysChangedListener, @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler, @NonNull AccessibilityManager accessibilityManager, @NonNull CompatUIStatusManager compatUIStatusManager, - @NonNull IntPredicate isDesktopModeEnablePredicate) { + @NonNull Optional<DesktopUserRepositories> desktopUserRepositories) { mContext = context; mShellController = shellController; mDisplayController = displayController; @@ -226,7 +228,7 @@ public class CompatUIController implements OnDisplaysChangedListener, mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis( DISAPPEAR_DELAY_MS, flags); mCompatUIStatusManager = compatUIStatusManager; - mInDesktopModePredicate = isDesktopModeEnablePredicate; + mDesktopUserRepositories = desktopUserRepositories; shellInit.addInitCallback(this::onInit, this); } @@ -267,7 +269,6 @@ public class CompatUIController implements OnDisplaysChangedListener, updateActiveTaskInfo(taskInfo); } - // We're showing the first reachability education so we ignore incoming TaskInfo // until the education flow has completed or we double tap. The double-tap // basically cancel all the onboarding flow. We don't have to ignore events in case @@ -865,7 +866,11 @@ public class CompatUIController implements OnDisplaysChangedListener, } private boolean isInDesktopMode(@Nullable TaskInfo taskInfo) { - return taskInfo != null && Flags.skipCompatUiEducationInDesktopMode() - && mInDesktopModePredicate.test(taskInfo.displayId); + if (mDesktopUserRepositories.isEmpty() || taskInfo == null) { + return false; + } + boolean isDesktopModeShowing = mDesktopUserRepositories.get().getCurrent() + .getVisibleTaskCount(taskInfo.displayId) > 0; + return Flags.skipCompatUiEducationInDesktopMode() && isDesktopModeShowing; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt index b50716ad07a3..8b830e769c70 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt @@ -22,6 +22,7 @@ import android.view.SurfaceControl import android.window.TransitionInfo import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags.appCompatRefactoring +import com.android.wm.shell.common.transition.TransitionStateHolder import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT import com.android.wm.shell.shared.TransitionUtil.isClosingType import com.android.wm.shell.sysui.ShellInit @@ -33,7 +34,8 @@ import com.android.wm.shell.transition.Transitions class LetterboxTransitionObserver( shellInit: ShellInit, private val transitions: Transitions, - private val letterboxController: LetterboxController + private val letterboxController: LetterboxController, + private val transitionStateHolder: TransitionStateHolder ) : Transitions.TransitionObserver { companion object { @@ -71,11 +73,11 @@ class LetterboxTransitionObserver( change.endAbsBounds.height() ) with(letterboxController) { - if (isClosingType(change.mode)) { - destroyLetterboxSurface( - key, - startTransaction - ) + // TODO(b/380274087) Handle return to home from a recents transition. + if (isClosingType(change.mode) && + !transitionStateHolder.isRecentsTransitionRunning()) { + // For the other types of close we need to check the recents. + destroyLetterboxSurface(key, finishTransaction) } else { val isTopActivityLetterboxed = ti.appCompatTaskInfo.isTopActivityLetterboxed if (isTopActivityLetterboxed) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index cb9c20e9b7ec..47084e17d029 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -87,8 +87,8 @@ import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler; import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository; import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator; import com.android.wm.shell.desktopmode.DesktopMode; -import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.freeform.FreeformComponents; @@ -138,7 +138,6 @@ import dagger.Module; import dagger.Provides; import java.util.Optional; -import java.util.function.IntPredicate; /** * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only @@ -267,7 +266,7 @@ public abstract class WMShellBaseModule { Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler, Lazy<AccessibilityManager> accessibilityManager, CompatUIRepository compatUIRepository, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, @NonNull CompatUIState compatUIState, @NonNull CompatUIComponentIdGenerator componentIdGenerator, @NonNull CompatUIComponentFactory compatUIComponentFactory, @@ -280,10 +279,6 @@ public abstract class WMShellBaseModule { new DefaultCompatUIHandler(compatUIRepository, compatUIState, componentIdGenerator, compatUIComponentFactory, mainExecutor)); } - final IntPredicate inDesktopModePredicate = - desktopRepository.<IntPredicate>map(modeTaskRepository -> displayId -> - modeTaskRepository.getVisibleTaskCount(displayId) > 0) - .orElseGet(() -> displayId -> false); return Optional.of( new CompatUIController( context, @@ -300,7 +295,7 @@ public abstract class WMShellBaseModule { compatUIShellCommandHandler.get(), accessibilityManager.get(), compatUIStatusManager, - inDesktopModePredicate)); + desktopUserRepositories)); } @WMSingleton @@ -704,14 +699,14 @@ public abstract class WMShellBaseModule { ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, ActivityTaskManager activityTaskManager, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, TaskStackTransitionObserver taskStackTransitionObserver, @ShellMainThread ShellExecutor mainExecutor ) { return Optional.ofNullable( RecentTasksController.create(context, shellInit, shellController, shellCommandHandler, taskStackListener, activityTaskManager, - desktopRepository, taskStackTransitionObserver, mainExecutor)); + desktopUserRepositories, taskStackTransitionObserver, mainExecutor)); } @BindsOptionalOf @@ -1002,16 +997,16 @@ public abstract class WMShellBaseModule { @BindsOptionalOf @DynamicOverride - abstract DesktopRepository optionalDesktopRepository(); + abstract DesktopUserRepositories optionalDesktopUserRepositories(); @WMSingleton @Provides - static Optional<DesktopRepository> provideDesktopRepository(Context context, - @DynamicOverride Optional<Lazy<DesktopRepository>> desktopRepository) { + static Optional<DesktopUserRepositories> provideDesktopUserRepositories(Context context, + @DynamicOverride Optional<Lazy<DesktopUserRepositories>> desktopUserRepositories) { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. - return desktopRepository.flatMap((lazy) -> { + return desktopUserRepositories.flatMap((lazy) -> { if (DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.of(lazy.get()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 974535385334..0bf3d9b4515f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -68,6 +68,7 @@ import com.android.wm.shell.common.MultiInstanceHelper; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.common.transition.TransitionStateHolder; import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler; import com.android.wm.shell.compatui.letterbox.LetterboxController; import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver; @@ -91,6 +92,7 @@ import com.android.wm.shell.desktopmode.DesktopTaskChangeListener; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksLimiter; import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler; @@ -150,6 +152,8 @@ import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel; import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel; import com.android.wm.shell.windowdecor.WindowDecorViewModel; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer; +import com.android.wm.shell.windowdecor.common.viewhost.DefaultWindowDecorViewHostSupplier; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController; import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController; import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel; @@ -337,6 +341,13 @@ public abstract class WMShellModule { return new AdditionalSystemViewContainer.Factory(); } + @WMSingleton + @Provides + static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier( + @ShellMainThread @NonNull CoroutineScope mainScope) { + return new DefaultWindowDecorViewHostSupplier(mainScope); + } + // // Freeform // @@ -362,7 +373,7 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, Optional<DesktopTasksController> desktopTasksController, LaunchAdjacentController launchAdjacentController, WindowDecorViewModel windowDecorViewModel, @@ -374,7 +385,7 @@ public abstract class WMShellModule { context, init, shellTaskOrganizer, - desktopRepository, + desktopUserRepositories, desktopTasksController, launchAdjacentController, windowDecorViewModel, @@ -690,7 +701,7 @@ public abstract class WMShellModule { DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, DragToDesktopTransitionHandler dragToDesktopTransitionHandler, - @DynamicOverride DesktopRepository desktopRepository, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, Optional<DesktopImmersiveController> desktopImmersiveController, DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver, LaunchAdjacentController launchAdjacentController, @@ -726,7 +737,7 @@ public abstract class WMShellModule { toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, desktopImmersiveController.get(), - desktopRepository, + desktopUserRepositories, recentsTransitionHandler, multiInstanceHelper, mainExecutor, @@ -749,7 +760,7 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, ReturnToDragStartAnimator returnToDragStartAnimator, - @DynamicOverride DesktopRepository desktopRepository, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, DesktopModeEventLogger desktopModeEventLogger) { return new DesktopTilingDecorViewModel( context, @@ -760,7 +771,7 @@ public abstract class WMShellModule { shellTaskOrganizer, toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, - desktopRepository, + desktopUserRepositories, desktopModeEventLogger ); } @@ -768,10 +779,10 @@ public abstract class WMShellModule { @WMSingleton @Provides static Optional<TaskChangeListener> provideDesktopTaskChangeListener( - Context context, @DynamicOverride DesktopRepository desktopRepository) { + Context context, @DynamicOverride DesktopUserRepositories desktopUserRepositories) { if (ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue() && DesktopModeStatus.canEnterDesktopMode(context)) { - return Optional.of(new DesktopTaskChangeListener(desktopRepository)); + return Optional.of(new DesktopTaskChangeListener(desktopUserRepositories)); } return Optional.empty(); } @@ -781,7 +792,7 @@ public abstract class WMShellModule { static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter( Context context, Transitions transitions, - @DynamicOverride DesktopRepository desktopRepository, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer shellTaskOrganizer, InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler) { @@ -794,7 +805,7 @@ public abstract class WMShellModule { return Optional.of( new DesktopTasksLimiter( transitions, - desktopRepository, + desktopUserRepositories, shellTaskOrganizer, maxTaskLimit, interactionJankMonitor, @@ -808,7 +819,7 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, Transitions transitions, - @DynamicOverride DesktopRepository desktopRepository, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, DisplayController displayController, ShellTaskOrganizer shellTaskOrganizer, ShellCommandHandler shellCommandHandler) { @@ -817,7 +828,7 @@ public abstract class WMShellModule { new DesktopImmersiveController( shellInit, transitions, - desktopRepository, + desktopUserRepositories, displayController, shellTaskOrganizer, shellCommandHandler)); @@ -856,14 +867,16 @@ public abstract class WMShellModule { InputManager inputManager, ShellTaskOrganizer shellTaskOrganizer, FocusTransitionObserver focusTransitionObserver, - @ShellMainThread ShellExecutor mainExecutor) { + @ShellMainThread ShellExecutor mainExecutor, + DisplayController displayController) { if (DesktopModeStatus.canEnterDesktopMode(context) && useKeyGestureEventHandler() && manageKeyGestures() && (Flags.enableMoveToNextDisplayShortcut() || Flags.enableTaskResizingKeyboardShortcuts())) { return Optional.of(new DesktopModeKeyGestureHandler(context, desktopModeWindowDecorViewModel, desktopTasksController, - inputManager, shellTaskOrganizer, focusTransitionObserver, mainExecutor)); + inputManager, shellTaskOrganizer, focusTransitionObserver, + mainExecutor, displayController)); } return Optional.empty(); } @@ -880,7 +893,7 @@ public abstract class WMShellModule { ShellCommandHandler shellCommandHandler, IWindowManager windowManager, ShellTaskOrganizer taskOrganizer, - @DynamicOverride DesktopRepository desktopRepository, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, DisplayController displayController, ShellController shellController, DisplayInsetsController displayInsetsController, @@ -907,7 +920,7 @@ public abstract class WMShellModule { } return Optional.of(new DesktopModeWindowDecorViewModel(context, shellExecutor, mainHandler, mainChoreographer, bgExecutor, shellInit, shellCommandHandler, windowManager, - taskOrganizer, desktopRepository, displayController, shellController, + taskOrganizer, desktopUserRepositories, displayController, shellController, displayInsetsController, syncQueue, transitions, desktopTasksController, desktopImmersiveController.get(), rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser, @@ -925,7 +938,7 @@ public abstract class WMShellModule { @ShellAnimationThread ShellExecutor animExecutor, ShellInit shellInit, Transitions transitions, - @DynamicOverride DesktopRepository desktopRepository) { + @DynamicOverride DesktopUserRepositories desktopUserRepositories) { if (!DesktopModeStatus.canEnterDesktopMode(context) || !ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() || !Flags.enableDesktopSystemDialogsTransitions()) { @@ -934,7 +947,7 @@ public abstract class WMShellModule { return Optional.of( new SystemModalsTransitionHandler( context, mainExecutor, animExecutor, shellInit, transitions, - desktopRepository)); + desktopUserRepositories)); } @WMSingleton @@ -993,16 +1006,17 @@ public abstract class WMShellModule { @WMSingleton @Provides @DynamicOverride - static DesktopRepository provideDesktopRepository( + static DesktopUserRepositories provideDesktopUserRepositories( Context context, ShellInit shellInit, DesktopPersistentRepository desktopPersistentRepository, DesktopRepositoryInitializer desktopRepositoryInitializer, - @ShellMainThread CoroutineScope mainScope + @ShellMainThread CoroutineScope mainScope, + UserManager userManager ) { - return new DesktopRepository(context, shellInit, desktopPersistentRepository, + return new DesktopUserRepositories(context, shellInit, desktopPersistentRepository, desktopRepositoryInitializer, - mainScope); + mainScope, userManager); } @WMSingleton @@ -1013,7 +1027,7 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, TaskStackListenerImpl taskStackListener, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, - @DynamicOverride DesktopRepository desktopRepository) { + @DynamicOverride DesktopUserRepositories desktopUserRepositories) { if (DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.of( new DesktopActivityOrientationChangeHandler( @@ -1022,7 +1036,7 @@ public abstract class WMShellModule { shellTaskOrganizer, taskStackListener, toggleResizeDesktopTaskTransitionHandler, - desktopRepository)); + desktopUserRepositories)); } return Optional.empty(); } @@ -1031,12 +1045,12 @@ public abstract class WMShellModule { @Provides static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver( Context context, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, Transitions transitions, ShellTaskOrganizer shellTaskOrganizer, Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler, ShellInit shellInit) { - return desktopRepository.flatMap( + return desktopUserRepositories.flatMap( repository -> Optional.of( new DesktopTasksTransitionObserver( @@ -1053,7 +1067,7 @@ public abstract class WMShellModule { static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler( Context context, Transitions transitions, - @DynamicOverride DesktopRepository desktopRepository, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, FreeformTaskTransitionHandler freeformTaskTransitionHandler, CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler, Optional<DesktopImmersiveController> desktopImmersiveController, @@ -1071,7 +1085,7 @@ public abstract class WMShellModule { new DesktopMixedTransitionHandler( context, transitions, - desktopRepository, + desktopUserRepositories, freeformTaskTransitionHandler, closeDesktopTaskTransitionHandler, desktopImmersiveController.get(), @@ -1316,9 +1330,11 @@ public abstract class WMShellModule { static LetterboxTransitionObserver provideLetterboxTransitionObserver( @NonNull ShellInit shellInit, @NonNull Transitions transitions, - @NonNull LetterboxController letterboxController + @NonNull LetterboxController letterboxController, + @NonNull TransitionStateHolder transitionStateHolder ) { - return new LetterboxTransitionObserver(shellInit, transitions, letterboxController); + return new LetterboxTransitionObserver(shellInit, transitions, letterboxController, + transitionStateHolder); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 3cd5df3121c1..cfdfe3d52011 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -42,7 +42,7 @@ import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; -import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipParamsChangedForwarder; @@ -171,7 +171,7 @@ public abstract class Pip1Module { PipParamsChangedForwarder pipParamsChangedForwarder, Optional<SplitScreenController> splitScreenControllerOptional, Optional<PipPerfHintController> pipPerfHintControllerOptional, - Optional<DesktopRepository> desktopRepositoryOptional, + Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @@ -181,7 +181,7 @@ public abstract class Pip1Module { pipBoundsAlgorithm, menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, splitScreenControllerOptional, pipPerfHintControllerOptional, - desktopRepositoryOptional, rootTaskDisplayAreaOrganizer, displayController, + desktopUserRepositoriesOptional, rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 7507e0458c48..3a9961917f79 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -39,7 +39,7 @@ import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; -import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.pip2.phone.PhonePipMenuController; import com.android.wm.shell.pip2.phone.PipController; import com.android.wm.shell.pip2.phone.PipMotionHelper; @@ -131,10 +131,10 @@ public abstract class Pip2Module { PipBoundsState pipBoundsState, @ShellMainThread ShellExecutor mainExecutor, PipTransitionState pipTransitionState, - Optional<DesktopRepository> desktopRepositoryOptional, + Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState, - desktopRepositoryOptional, rootTaskDisplayAreaOrganizer); + desktopUserRepositoriesOptional, rootTaskDisplayAreaOrganizer); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt index 606aa6cd3353..c0bf40b9c461 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt @@ -39,7 +39,7 @@ class DesktopActivityOrientationChangeHandler( private val shellTaskOrganizer: ShellTaskOrganizer, private val taskStackListener: TaskStackListenerImpl, private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler, - private val taskRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, ) { init { @@ -81,7 +81,9 @@ class DesktopActivityOrientationChangeHandler( ) { if (!Flags.respectOrientationChangeForUnresizeable()) return val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return - if (!isDesktopModeShowing(task.displayId) || !task.isFreeform || task.isResizeable) return + val taskRepository = desktopUserRepositories.current + val isDesktopModeShowing = taskRepository.getVisibleTaskCount(task.displayId) > 0 + if (!isDesktopModeShowing || !task.isFreeform || task.isResizeable) return val taskBounds = task.configuration.windowConfiguration.bounds val taskHeight = taskBounds.height() @@ -106,7 +108,4 @@ class DesktopActivityOrientationChangeHandler( resizeHandler.startTransition(wct) } } - - private fun isDesktopModeShowing(displayId: Int): Boolean = - taskRepository.getVisibleTaskCount(displayId) > 0 }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt index dd95273dd4f3..79be698773da 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt @@ -50,7 +50,7 @@ import java.io.PrintWriter class DesktopImmersiveController( shellInit: ShellInit, private val transitions: Transitions, - private val desktopRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val displayController: DisplayController, private val shellTaskOrganizer: ShellTaskOrganizer, private val shellCommandHandler: ShellCommandHandler, @@ -60,14 +60,14 @@ class DesktopImmersiveController( constructor( shellInit: ShellInit, transitions: Transitions, - desktopRepository: DesktopRepository, + desktopUserRepositories: DesktopUserRepositories, displayController: DisplayController, shellTaskOrganizer: ShellTaskOrganizer, shellCommandHandler: ShellCommandHandler, ) : this( shellInit, transitions, - desktopRepository, + desktopUserRepositories, displayController, shellTaskOrganizer, shellCommandHandler, @@ -177,8 +177,9 @@ class DesktopImmersiveController( reason: ExitReason, ): ExitResult { if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit - val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId) - ?: return ExitResult.NoExit + val immersiveTask = + desktopUserRepositories.current.getTaskInFullImmersiveState(displayId) + ?: return ExitResult.NoExit if (immersiveTask == excludeTaskId) { return ExitResult.NoExit } @@ -210,7 +211,7 @@ class DesktopImmersiveController( reason: ExitReason, ): ExitResult { if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit - if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) { + if (desktopUserRepositories.current.isTaskInFullImmersiveState(taskInfo.taskId)) { // A full immersive task is being minimized, make sure the immersive state is broken // (i.e. resize back to max bounds). wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo)) @@ -377,6 +378,7 @@ class DesktopImmersiveController( startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, ) { + val desktopRepository: DesktopRepository = desktopUserRepositories.current // Check if this is a pending external exit transition. val pendingExit = pendingExternalExitTransitions .firstOrNull { pendingExit -> pendingExit.transition == transition } @@ -412,6 +414,7 @@ class DesktopImmersiveController( } val startBounds = immersiveChange.startAbsBounds logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction) + when (state.direction) { Direction.ENTER -> { desktopRepository.setTaskInFullImmersiveState( @@ -484,7 +487,8 @@ class DesktopImmersiveController( val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: error("Expected non-null display layout for displayId: ${taskInfo.displayId}") return if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { - desktopRepository.removeBoundsBeforeFullImmersive(taskInfo.taskId) + desktopUserRepositories.current + .removeBoundsBeforeFullImmersive(taskInfo.taskId) ?: if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) { calculateInitialBounds(displayLayout, taskInfo) } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt index 82c2ebc7ec77..96bbd58949bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt @@ -49,7 +49,7 @@ import com.android.wm.shell.transition.Transitions.TransitionFinishCallback class DesktopMixedTransitionHandler( private val context: Context, private val transitions: Transitions, - private val desktopRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler, private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler, private val desktopImmersiveController: DesktopImmersiveController, @@ -405,7 +405,7 @@ class DesktopMixedTransitionHandler( private fun isLastDesktopTask(change: TransitionInfo.Change): Boolean = change.taskInfo?.let { - desktopRepository.getExpandedTaskCount(it.displayId) == 1 + desktopUserRepositories.getProfile(it.userId).getExpandedTaskCount(it.displayId) == 1 } ?: false private fun findCloseDesktopTaskChange(info: TransitionInfo): TransitionInfo.Change? { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt index 43544f6fd4b3..250e1779aac3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt @@ -31,7 +31,8 @@ import android.content.Context import com.android.hardware.input.Flags.manageKeyGestures import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread @@ -48,7 +49,8 @@ class DesktopModeKeyGestureHandler( private val shellTaskOrganizer: ShellTaskOrganizer, private val focusTransitionObserver: FocusTransitionObserver, @ShellMainThread private val mainExecutor: ShellExecutor, - ) : KeyGestureEventHandler { + private val displayController: DisplayController, +) : KeyGestureEventHandler { init { inputManager.registerKeyGestureEventHandler(this) @@ -99,12 +101,15 @@ class DesktopModeKeyGestureHandler( } KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW -> { logV("Key gesture TOGGLE_MAXIMIZE_FREEFORM_WINDOW is handled") - getGloballyFocusedFreeformTask()?.let { + getGloballyFocusedFreeformTask()?.let { taskInfo -> mainExecutor.execute { desktopTasksController.get().toggleDesktopTaskSize( - it, - ResizeTrigger.MAXIMIZE_MENU, - DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, + taskInfo, + ToggleTaskSizeInteraction( + isMaximized = isTaskMaximized(taskInfo, displayController), + source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT, + inputMethod = DesktopModeEventLogger.Companion.InputMethod.KEYBOARD + ) ) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt index 2c432bcb55ab..38219984d651 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt @@ -103,8 +103,12 @@ class DesktopModeUiEventLogger( DESKTOP_WINDOW_CORNER_DRAG_RESIZE(1722), @UiEvent(doc = "Tap on the window header maximize button in desktop windowing mode") DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP(1723), + @UiEvent(doc = "Tap on the window header restore button in desktop windowing mode") + DESKTOP_WINDOW_RESTORE_BUTTON_TAP(2017), @UiEvent(doc = "Double tap on window header to maximize it in desktop windowing mode") DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE(1724), + @UiEvent(doc = "Double tap on window header to restore from maximize in desktop windowing") + DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_RESTORE(2018), @UiEvent(doc = "Tap on the window Handle to open the Handle Menu") DESKTOP_WINDOW_APP_HANDLE_TAP(1998), @UiEvent(doc = "Tap on the desktop mode option under app handle menu") @@ -136,7 +140,11 @@ class DesktopModeUiEventLogger( @UiEvent(doc = "Tap on the tile to left option in the maximize button menu") DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_LEFT(2012), @UiEvent(doc = "Tap on the tile to right option in the maximize button menu") - DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT(2013); + DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT(2013), + @UiEvent(doc = "Moving the desktop window by dragging the header") + DESKTOP_WINDOW_MOVE_BY_HEADER_DRAG(2021), + @UiEvent(doc = "Double tap on the window header to refocus a desktop window") + DESKTOP_WINDOW_HEADER_TAP_TO_REFOCUS(2022); override fun getId(): Int = mId } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index c7cf31081c8b..42c3b1ccd1a7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -28,6 +28,7 @@ import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.graphics.Rect import android.os.SystemProperties import android.util.Size +import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float = @@ -211,6 +212,31 @@ fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float { minOf(appBounds.height(), appBounds.width()).toFloat() } +/** Returns whether the task is maximized. */ +fun isTaskMaximized( + taskInfo: RunningTaskInfo, + displayController: DisplayController +): Boolean { + val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) + ?: error("Could not get display layout for display=${taskInfo.displayId}") + val stableBounds = Rect() + displayLayout.getStableBounds(stableBounds) + return isTaskMaximized(taskInfo, stableBounds) +} + +/** Returns whether the task is maximized. */ +fun isTaskMaximized( + taskInfo: RunningTaskInfo, + stableBounds: Rect +): Boolean { + val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds + return if (taskInfo.isResizeable) { + isTaskBoundsEqual(currentTaskBounds, stableBounds) + } else { + isTaskWidthOrHeightEqual(currentTaskBounds, stableBounds) + } +} + /** Returns true if task's width or height is maximized else returns false. */ fun isTaskWidthOrHeightEqual(taskBounds: Rect, stableBounds: Rect): Boolean { return taskBounds.width() == stableBounds.width() || diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index 7fcb7678f6af..ca3dc2d5426e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.desktopmode -import android.content.Context +import android.content.pm.UserInfo import android.graphics.Rect import android.graphics.Region import android.util.ArrayMap @@ -30,11 +30,8 @@ import androidx.core.util.keyIterator import androidx.core.util.valueIterator import com.android.internal.protolog.ProtoLog import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository -import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread -import com.android.wm.shell.shared.desktopmode.DesktopModeStatus -import com.android.wm.shell.sysui.ShellInit import java.io.PrintWriter import java.util.concurrent.Executor import java.util.function.Consumer @@ -43,13 +40,10 @@ import kotlinx.coroutines.launch /** Tracks desktop data for Android Desktop Windowing. */ class DesktopRepository ( - private val context: Context, - shellInit: ShellInit, private val persistentRepository: DesktopPersistentRepository, - private val repositoryInitializer: DesktopRepositoryInitializer, @ShellMainThread private val mainCoroutineScope: CoroutineScope, + val userId: Int, ){ - /** * Task data tracked per desktop. * @@ -117,16 +111,6 @@ class DesktopRepository ( this[displayId] ?: DesktopTaskData().also { this[displayId] = it } } - init { - if (DesktopModeStatus.canEnterDesktopMode(context)) { - shellInit.addInitCallback(::initRepoFromPersistentStorage, this) - } - } - - private fun initRepoFromPersistentStorage() { - repositoryInitializer.initialize(this) - } - /** Adds [activeTasksListener] to be notified of updates to active tasks. */ fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) { activeTasksListeners.add(activeTasksListener) @@ -276,6 +260,8 @@ class DesktopRepository ( * the set of visible tasks on that display and notifies listeners. */ fun updateTask(displayId: Int, taskId: Int, isVisible: Boolean) { + logD("updateTask taskId=%d, displayId=%d, isVisible=%b", taskId, displayId, isVisible) + if (isVisible) { // If task is visible, remove it from any other display besides [displayId]. removeVisibleTask(taskId, excludedDisplayId = displayId) @@ -495,6 +481,7 @@ class DesktopRepository ( persistentRepository.addOrUpdateDesktop( // Use display id as desktop id for now since only once desktop per display // is supported. + userId = userId, desktopId = displayId, visibleTasks = desktopTaskDataByDisplayIdCopy.visibleTasks, minimizedTasks = desktopTaskDataByDisplayIdCopy.minimizedTasks, @@ -529,6 +516,7 @@ class DesktopRepository ( ) pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}") pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}") + pw.println("${innerPrefix}wallpaperActivityToken=${wallpaperActivityToken}") } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt index 94ac2e665f61..80d8bfba99d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt @@ -23,10 +23,12 @@ import com.android.wm.shell.freeform.TaskChangeListener /** Manages tasks handling specific to Android Desktop Mode. */ class DesktopTaskChangeListener( - private val desktopRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, ) : TaskChangeListener { override fun onTaskOpening(taskInfo: RunningTaskInfo) { + val desktopRepository: DesktopRepository = + desktopUserRepositories.getProfile(taskInfo.userId) if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) { desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) return @@ -37,6 +39,8 @@ class DesktopTaskChangeListener( } override fun onTaskChanging(taskInfo: RunningTaskInfo) { + val desktopRepository: DesktopRepository = + desktopUserRepositories.getProfile(taskInfo.userId) if (!desktopRepository.isActiveTask(taskInfo.taskId)) return // Case 1: Freeform task is changed in Desktop Mode. @@ -61,6 +65,8 @@ class DesktopTaskChangeListener( } override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) { + val desktopRepository: DesktopRepository = + desktopUserRepositories.getProfile(taskInfo.userId) if (!desktopRepository.isActiveTask(taskInfo.taskId)) return if (!isFreeformTask(taskInfo)) { desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) @@ -74,6 +80,8 @@ class DesktopTaskChangeListener( } override fun onTaskClosing(taskInfo: RunningTaskInfo) { + val desktopRepository: DesktopRepository = + desktopUserRepositories.getProfile(taskInfo.userId) if (!desktopRepository.isActiveTask(taskInfo.taskId)) return // TODO: b/370038902 - Handle Activity#finishAndRemoveTask. if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() || 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 c479ab382acb..5cb94f870a2d 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 @@ -84,6 +84,7 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener @@ -158,7 +159,7 @@ class DesktopTasksController( private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler, private val desktopImmersiveController: DesktopImmersiveController, - private val taskRepository: DesktopRepository, + private val userRepositories: DesktopUserRepositories, private val recentsTransitionHandler: RecentsTransitionHandler, private val multiInstanceHelper: MultiInstanceHelper, @ShellMainThread private val mainExecutor: ShellExecutor, @@ -176,6 +177,7 @@ class DesktopTasksController( UserChangeListener { private val desktopMode: DesktopModeImpl + private var taskRepository: DesktopRepository private var visualIndicator: DesktopModeVisualIndicator? = null private var userId: Int private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler = @@ -228,6 +230,7 @@ class DesktopTasksController( shellInit.addInitCallback({ onInit() }, this) } userId = ActivityManager.getCurrentUser() + taskRepository = userRepositories.getProfile(userId) } private fun onInit() { @@ -795,32 +798,24 @@ class DesktopTasksController( */ fun toggleDesktopTaskSize( taskInfo: RunningTaskInfo, - resizeTrigger: ResizeTrigger, - inputMethod: InputMethod, - maximizeCujRecorder: (() -> Unit)? = null, - unmaximizeCujRecorder: (() -> Unit)? = null, + interaction: ToggleTaskSizeInteraction ) { val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds desktopModeEventLogger.logTaskResizingStarted( - resizeTrigger, - inputMethod, + interaction.resizeTrigger, + interaction.inputMethod, taskInfo, currentTaskBounds.width(), currentTaskBounds.height(), displayController ) - val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return - - val stableBounds = Rect().apply { displayLayout.getStableBounds(this) } val destinationBounds = Rect() - - val isMaximized = isTaskMaximized(taskInfo, stableBounds) + val isMaximized = interaction.direction == ToggleTaskSizeInteraction.Direction.RESTORE // If the task is currently maximized, we will toggle it not to be and vice versa. This is // helpful to eliminate the current task from logic to calculate taskbar corner rounding. - val willMaximize = !isMaximized + val willMaximize = interaction.direction == ToggleTaskSizeInteraction.Direction.MAXIMIZE if (isMaximized) { - unmaximizeCujRecorder?.invoke() // The desktop task is at the maximized width and/or height of the stable bounds. // If the task's pre-maximize stable bounds were saved, toggle the task to those bounds. // Otherwise, toggle to the default bounds. @@ -836,7 +831,6 @@ class DesktopTasksController( } } } else { - maximizeCujRecorder?.invoke() // Save current bounds so that task can be restored back to original bounds if necessary // and toggle to the stable bounds. desktopTilingDecorViewModel.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId) @@ -857,10 +851,16 @@ class DesktopTasksController( taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding) val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds) + interaction.uiEvent?.let { uiEvent -> + desktopModeUiEventLogger.log(taskInfo, uiEvent) + } desktopModeEventLogger.logTaskResizingEnded( - resizeTrigger, inputMethod, - taskInfo, destinationBounds.width(), - destinationBounds.height(), displayController + interaction.resizeTrigger, + interaction.inputMethod, + taskInfo, + destinationBounds.width(), + destinationBounds.height(), + displayController, ) toggleResizeDesktopTaskTransitionHandler.startTransition(wct) } @@ -871,10 +871,7 @@ class DesktopTasksController( currentDragBounds: Rect, motionEvent: MotionEvent ) { - val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return - val stableBounds = Rect() - displayLayout.getStableBounds(stableBounds) - if (isTaskMaximized(taskInfo, stableBounds)) { + if (isTaskMaximized(taskInfo, displayController)) { // Handle the case where we attempt to drag-to-maximize when already maximized: the task // position won't need to change but we want to animate the surface going back to the // maximized position. @@ -892,8 +889,11 @@ class DesktopTasksController( toggleDesktopTaskSize( taskInfo, - ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, - DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent), + ToggleTaskSizeInteraction( + direction = ToggleTaskSizeInteraction.Direction.MAXIMIZE, + source = ToggleTaskSizeInteraction.Source.HEADER_DRAG_TO_TOP, + inputMethod = DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent), + ) ) } @@ -911,19 +911,6 @@ class DesktopTasksController( } } - private fun isTaskMaximized( - taskInfo: RunningTaskInfo, - stableBounds: Rect - ): Boolean { - val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds - - return if (taskInfo.isResizeable) { - isTaskBoundsEqual(currentTaskBounds, stableBounds) - } else { - isTaskWidthOrHeightEqual(currentTaskBounds, stableBounds) - } - } - private fun isMaximizedToStableBoundsEdges( taskInfo: RunningTaskInfo, stableBounds: Rect @@ -1254,7 +1241,7 @@ class DesktopTasksController( /* requestCode= */ 0, intent, PendingIntent.FLAG_IMMUTABLE, - /* bundle= */ null, + /* options= */ null, userHandle ) wct.sendPendingIntent(pendingIntent, intent, options.toBundle()) @@ -1739,8 +1726,7 @@ class DesktopTasksController( /** Handle task closing by removing wallpaper activity if it's the last active task */ private fun handleTaskClosing(task: RunningTaskInfo, transition: IBinder, requestType: Int): WindowContainerTransaction? { logV("handleTaskClosing") - if (!isDesktopModeShowing(task.displayId)) - return null + if (!isDesktopModeShowing(task.displayId)) return null val wct = WindowContainerTransaction() performDesktopExitCleanupIfNeeded(task.taskId, wct) @@ -1757,6 +1743,7 @@ class DesktopTasksController( ) ) } + taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( doesAnyTaskRequireTaskbarRounding( task.displayId, @@ -2342,7 +2329,9 @@ class DesktopTasksController( // TODO(b/366397912): Support full multi-user mode in Windowing. override fun onUserChanged(newUserId: Int, userContext: Context) { + logV("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId) userId = newUserId + taskRepository = userRepositories.getProfile(userId) desktopTilingDecorViewModel.onUserChange() } @@ -2365,6 +2354,7 @@ class DesktopTasksController( val innerPrefix = "$prefix " pw.println("${prefix}DesktopTasksController") DesktopModeStatus.dump(pw, innerPrefix, context) + pw.println("${prefix}userId=$userId") taskRepository.dump(pw, innerPrefix) } @@ -2393,6 +2383,7 @@ class DesktopTasksController( displayId: Int, transitionSource: DesktopModeTransitionSource ) { + logV("moveFocusedTaskToDesktop") mainExecutor.execute { this@DesktopTasksController.moveFocusedTaskToDesktop(displayId, transitionSource) } @@ -2402,12 +2393,14 @@ class DesktopTasksController( displayId: Int, transitionSource: DesktopModeTransitionSource ) { + logV("moveFocusedTaskToFullscreen") mainExecutor.execute { this@DesktopTasksController.enterFullscreen(displayId, transitionSource) } } override fun moveFocusedTaskToStageSplit(displayId: Int, leftOrTop: Boolean) { + logV("moveFocusedTaskToStageSplit") mainExecutor.execute { this@DesktopTasksController.enterSplit(displayId, leftOrTop) } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 77af627a948a..62b200a6174e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode +import android.app.ActivityManager import android.content.Context import android.os.Handler import android.os.IBinder @@ -31,6 +32,7 @@ import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.sysui.UserChangeListener; import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionObserver @@ -43,7 +45,7 @@ import com.android.wm.shell.transition.Transitions.TransitionObserver */ class DesktopTasksLimiter ( transitions: Transitions, - private val taskRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val shellTaskOrganizer: ShellTaskOrganizer, private val maxTasksLimit: Int, private val interactionJankMonitor: InteractionJankMonitor, @@ -54,12 +56,15 @@ class DesktopTasksLimiter ( @VisibleForTesting val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover() + private var userId: Int + init { require(maxTasksLimit > 0) { "DesktopTasksLimiter: maxTasksLimit should be greater than 0. Current value: $maxTasksLimit." } transitions.registerObserver(minimizeTransitionObserver) - taskRepository.addActiveTaskListener(leftoverMinimizedTasksRemover) + userId = ActivityManager.getCurrentUser() + desktopUserRepositories.current.addActiveTaskListener(leftoverMinimizedTasksRemover) logV("Starting limiter with a maximum of %d tasks", maxTasksLimit) } @@ -84,6 +89,7 @@ class DesktopTasksLimiter ( startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction ) { + val taskRepository = desktopUserRepositories.current val taskToMinimize = pendingTransitionTokensAndTasks.remove(transition) ?: return if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return if (!isTaskReadyForMinimize(info, taskToMinimize)) { @@ -114,6 +120,7 @@ class DesktopTasksLimiter ( ): Boolean { val taskChange = info.changes.find { change -> change.taskInfo?.taskId == taskDetails.taskId } + val taskRepository = desktopUserRepositories.current if (taskChange == null) return !taskRepository.isVisibleTask(taskDetails.taskId) return taskChange.mode == TRANSIT_TO_BACK } @@ -151,7 +158,8 @@ class DesktopTasksLimiter ( } @VisibleForTesting - inner class LeftoverMinimizedTasksRemover : DesktopRepository.ActiveTasksListener { + inner class LeftoverMinimizedTasksRemover + : DesktopRepository.ActiveTasksListener, UserChangeListener { override fun onActiveTasksChanged(displayId: Int) { // If back navigation is enabled, we shouldn't remove the leftover tasks if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return @@ -161,6 +169,7 @@ class DesktopTasksLimiter ( } fun removeLeftoverMinimizedTasks(displayId: Int, wct: WindowContainerTransaction) { + val taskRepository = desktopUserRepositories.current if (taskRepository.getExpandedTasksOrdered(displayId).isNotEmpty()) return val remainingMinimizedTasks = taskRepository.getMinimizedTasks(displayId) if (remainingMinimizedTasks.isEmpty()) return @@ -173,6 +182,15 @@ class DesktopTasksLimiter ( } } } + + override fun onUserChanged(newUserId: Int, userContext: Context) { + // Removes active task listener for the previous repository + desktopUserRepositories.getProfile(userId).removeActiveTasksListener(this); + + // Sets active listener for the current repository. + userId = newUserId + desktopUserRepositories.getProfile(newUserId).addActiveTaskListener(this); + } } /** @@ -183,6 +201,7 @@ class DesktopTasksLimiter ( */ private fun minimizeTask(displayId: Int, taskId: Int) { logV("Minimize taskId=%d, displayId=%d", taskId, displayId) + val taskRepository = desktopUserRepositories.current taskRepository.minimizeTask(displayId, taskId) } @@ -196,7 +215,7 @@ class DesktopTasksLimiter ( newFrontTaskId: Int, ): Int? { logV("addAndGetMinimizeTaskChanges, newFrontTask=%d", newFrontTaskId) - + val taskRepository = desktopUserRepositories.current val taskIdToMinimize = getTaskIdToMinimize( taskRepository.getExpandedTasksOrdered(displayId), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index c39c715e685c..d6bb7f95c196 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode +import android.app.ActivityManager import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.Context import android.os.IBinder @@ -43,7 +44,7 @@ import com.android.wm.shell.transition.Transitions */ class DesktopTasksTransitionObserver( private val context: Context, - private val desktopRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val transitions: Transitions, private val shellTaskOrganizer: ShellTaskOrganizer, private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler, @@ -51,11 +52,13 @@ class DesktopTasksTransitionObserver( ) : Transitions.TransitionObserver { private var transitionToCloseWallpaper: IBinder? = null + private var currentProfileId: Int init { if (DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback(::onInit, this) } + currentProfileId = ActivityManager.getCurrentUser() } fun onInit() { @@ -89,6 +92,7 @@ class DesktopTasksTransitionObserver( val taskInfo = change.taskInfo if (taskInfo == null || taskInfo.taskId == -1) continue + val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId) if (desktopRepository.isActiveTask(taskInfo.taskId) && taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) { desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) @@ -105,7 +109,7 @@ class DesktopTasksTransitionObserver( if (taskInfo == null || taskInfo.taskId == -1) { continue } - + val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId) val visibleTaskCount = desktopRepository.getVisibleTaskCount(taskInfo.displayId) if (visibleTaskCount > 0 && change.mode == TRANSIT_TO_BACK && @@ -128,12 +132,13 @@ class DesktopTasksTransitionObserver( if (taskInfo == null || taskInfo.taskId == -1) { continue } - + val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId) if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 1 && change.mode == TRANSIT_CLOSE && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM && desktopRepository.wallpaperActivityToken != null) { transitionToCloseWallpaper = transition + currentProfileId = taskInfo.userId } } } @@ -150,6 +155,7 @@ class DesktopTasksTransitionObserver( // TODO: b/332682201 Update repository state if (transitionToCloseWallpaper == transition) { // TODO: b/362469671 - Handle merging the animation when desktop is also closing. + val desktopRepository = desktopUserRepositories.getProfile(currentProfileId) desktopRepository.wallpaperActivityToken?.let { wallpaperActivityToken -> transitions.startTransition( TRANSIT_CLOSE, @@ -167,6 +173,7 @@ class DesktopTasksTransitionObserver( info.changes.forEach { change -> change.taskInfo?.let { taskInfo -> if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) { + val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId) when (change.mode) { WindowManager.TRANSIT_OPEN -> { desktopRepository.wallpaperActivityToken = taskInfo.token diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt new file mode 100644 index 000000000000..1e5a1b274729 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt @@ -0,0 +1,109 @@ +/* + * 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.wm.shell.desktopmode + +import android.app.ActivityManager +import android.content.Context +import android.content.pm.UserInfo +import android.os.UserManager +import android.util.SparseArray +import com.android.window.flags.Flags +import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository +import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.sysui.UserChangeListener +import kotlinx.coroutines.CoroutineScope + +/** Manages per-user DesktopRepository instances. */ +class DesktopUserRepositories( + context: Context, + shellInit: ShellInit, + private val persistentRepository: DesktopPersistentRepository, + private val repositoryInitializer: DesktopRepositoryInitializer, + @ShellMainThread private val mainCoroutineScope: CoroutineScope, + userManager: UserManager, +) : UserChangeListener { + private var userId: Int + private var userIdToProfileIdsMap: MutableMap<Int, List<Int>> = mutableMapOf() + + // TODO(b/357060209): Add caching for this logic to improve efficiency. + val current: DesktopRepository + get() = desktopRepoByUserId.getOrCreate(userId) + + private val desktopRepoByUserId = + object : SparseArray<DesktopRepository>() { + /** Gets [DesktopRepository] for existing [userId] or creates a new one. */ + fun getOrCreate(userId: Int): DesktopRepository = + this[userId] + ?: DesktopRepository( + persistentRepository, + mainCoroutineScope, + userId) + .also { this[userId] = it } + } + + init { + userId = ActivityManager.getCurrentUser() + if (DesktopModeStatus.canEnterDesktopMode(context)) { + shellInit.addInitCallback(::initRepoFromPersistentStorage, this) + } + if (Flags.enableDesktopWindowingHsum()) { + userIdToProfileIdsMap[userId] = userManager.getProfiles(userId).map { it.id } + } + } + + private fun initRepoFromPersistentStorage() { + repositoryInitializer.initialize(this) + } + + /** Returns [DesktopRepository] for the parent user id. */ + fun getProfile(profileId: Int): DesktopRepository { + if (Flags.enableDesktopWindowingHsum()) { + for ((uid, profileIds) in userIdToProfileIdsMap) { + if (profileId in profileIds) { + return desktopRepoByUserId.getOrCreate(uid) + } + } + } + return desktopRepoByUserId.getOrCreate(profileId) + } + + override fun onUserChanged(newUserId: Int, userContext: Context) { + logD("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId) + userId = newUserId + } + + override fun onUserProfilesChanged(profiles: MutableList<UserInfo>) { + logD("onUserProfilesChanged profiles=%s", profiles.toString()) + if (Flags.enableDesktopWindowingHsum()) { + // TODO(b/366397912): Remove all persisted profile data when the profile changes. + userIdToProfileIdsMap[userId] = profiles.map { it.id } + } + } + + private fun logD(msg: String, vararg arguments: Any?) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + companion object { + private const val TAG = "DesktopUserRepositories" + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt new file mode 100644 index 000000000000..7afd8d7f6e48 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt @@ -0,0 +1,149 @@ +/* + * 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.wm.shell.desktopmode.common + +import com.android.internal.jank.Cuj +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger +import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.AmbiguousSource +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.Source + +/** Represents a user interaction to toggle a desktop task's size from to maximize or vice versa. */ +data class ToggleTaskSizeInteraction( + val direction: Direction, + val source: Source, + val inputMethod: InputMethod, +) { + constructor( + isMaximized: Boolean, + source: Source, + inputMethod: InputMethod, + ) : this( + direction = if (isMaximized) Direction.RESTORE else Direction.MAXIMIZE, + source = source, + inputMethod = inputMethod, + ) + + val jankTag: String? = + when (source) { + Source.HEADER_BUTTON_TO_MAXIMIZE -> "caption_bar_button" + Source.HEADER_BUTTON_TO_RESTORE -> "caption_bar_button" + Source.KEYBOARD_SHORTCUT -> null + Source.HEADER_DRAG_TO_TOP -> null + Source.MAXIMIZE_MENU_TO_MAXIMIZE -> "maximize_menu" + Source.MAXIMIZE_MENU_TO_RESTORE -> "maximize_menu" + Source.DOUBLE_TAP_TO_MAXIMIZE -> "double_tap" + Source.DOUBLE_TAP_TO_RESTORE -> "double_tap" + } + val uiEvent: DesktopUiEventEnum? = + when (source) { + Source.HEADER_BUTTON_TO_MAXIMIZE -> + DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP + Source.HEADER_BUTTON_TO_RESTORE -> DesktopUiEventEnum.DESKTOP_WINDOW_RESTORE_BUTTON_TAP + Source.KEYBOARD_SHORTCUT -> null + Source.HEADER_DRAG_TO_TOP -> null + Source.MAXIMIZE_MENU_TO_MAXIMIZE -> { + DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_MAXIMIZE + } + Source.MAXIMIZE_MENU_TO_RESTORE -> { + DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE + } + Source.DOUBLE_TAP_TO_MAXIMIZE -> { + DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE + } + Source.DOUBLE_TAP_TO_RESTORE -> { + DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_RESTORE + } + } + val resizeTrigger = + when (source) { + Source.HEADER_BUTTON_TO_MAXIMIZE -> ResizeTrigger.MAXIMIZE_BUTTON + Source.HEADER_BUTTON_TO_RESTORE -> ResizeTrigger.MAXIMIZE_BUTTON + Source.KEYBOARD_SHORTCUT -> ResizeTrigger.UNKNOWN_RESIZE_TRIGGER + Source.HEADER_DRAG_TO_TOP -> ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER + Source.MAXIMIZE_MENU_TO_MAXIMIZE -> ResizeTrigger.MAXIMIZE_MENU + Source.MAXIMIZE_MENU_TO_RESTORE -> ResizeTrigger.MAXIMIZE_MENU + Source.DOUBLE_TAP_TO_MAXIMIZE -> ResizeTrigger.DOUBLE_TAP_APP_HEADER + Source.DOUBLE_TAP_TO_RESTORE -> ResizeTrigger.DOUBLE_TAP_APP_HEADER + } + val cujTracing: Int? = + when (source) { + Source.HEADER_BUTTON_TO_MAXIMIZE -> Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW + Source.HEADER_BUTTON_TO_RESTORE -> Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW + Source.KEYBOARD_SHORTCUT -> null + Source.HEADER_DRAG_TO_TOP -> null + Source.MAXIMIZE_MENU_TO_MAXIMIZE -> null + Source.MAXIMIZE_MENU_TO_RESTORE -> null + Source.DOUBLE_TAP_TO_MAXIMIZE -> null + Source.DOUBLE_TAP_TO_RESTORE -> null + } + + /** The direction to which the task is being resized. */ + enum class Direction { + MAXIMIZE, + RESTORE, + } + + /** The user interaction source. */ + enum class Source { + HEADER_BUTTON_TO_MAXIMIZE, + HEADER_BUTTON_TO_RESTORE, + KEYBOARD_SHORTCUT, + HEADER_DRAG_TO_TOP, + MAXIMIZE_MENU_TO_MAXIMIZE, + MAXIMIZE_MENU_TO_RESTORE, + DOUBLE_TAP_TO_MAXIMIZE, + DOUBLE_TAP_TO_RESTORE, + } + + /** + * Temporary sources for interactions that should be broken into more specific sources, for + * example, the header button click should use [Source.HEADER_BUTTON_TO_MAXIMIZE] and + * [Source.HEADER_BUTTON_TO_RESTORE]. + * + * TODO: b/341320112 - break these out into different [Source]s. + */ + enum class AmbiguousSource { + HEADER_BUTTON, + MAXIMIZE_MENU, + DOUBLE_TAP, + } +} + +/** Returns the non-ambiguous [Source] based on the maximized state of the task. */ +fun AmbiguousSource.toSource(isMaximized: Boolean): Source { + return when (this) { + AmbiguousSource.HEADER_BUTTON -> + if (isMaximized) { + Source.HEADER_BUTTON_TO_RESTORE + } else { + Source.HEADER_BUTTON_TO_MAXIMIZE + } + AmbiguousSource.MAXIMIZE_MENU -> + if (isMaximized) { + Source.MAXIMIZE_MENU_TO_RESTORE + } else { + Source.MAXIMIZE_MENU_TO_MAXIMIZE + } + AmbiguousSource.DOUBLE_TAP -> + if (isMaximized) { + Source.DOUBLE_TAP_TO_RESTORE + } else { + Source.DOUBLE_TAP_TO_MAXIMIZE + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt index 826de08557bd..a428ce18a49e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt @@ -29,7 +29,7 @@ import com.android.app.animation.Interpolators import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing -import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil.isClosingMode import com.android.wm.shell.shared.TransitionUtil.isClosingType @@ -46,7 +46,7 @@ class SystemModalsTransitionHandler( private val animExecutor: ShellExecutor, private val shellInit: ShellInit, private val transitions: Transitions, - private val desktopRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, ) : TransitionHandler { private val showingSystemModalsIds = mutableSetOf<Int>() @@ -156,7 +156,7 @@ class SystemModalsTransitionHandler( } private fun isDesktopModeShowing(displayId: Int): Boolean = - desktopRepository.getVisibleTaskCount(displayId) > 0 + desktopUserRepositories.current.getVisibleTaskCount(displayId) > 0 override fun handleRequest( transition: IBinder, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt index bfe1b12c9605..ac0a6275cfbd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt @@ -116,7 +116,12 @@ class AppToWebEducationController( } private inline fun runIfEducationFeatureEnabled(block: () -> Unit) { - if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppToWebEducation()) block() + if ( + canEnterDesktopMode(context) && + Flags.enableDesktopWindowingAppToWebEducationIntegration() + ) { + block() + } } private fun showEducation(captionState: CaptionState, colorScheme: EducationColorScheme) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt index 9e646f430c98..b7de1f86601c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt @@ -73,8 +73,8 @@ class DesktopPersistentRepository( * Reads and returns the [DesktopRepositoryState] proto object from the DataStore for a user. If * the DataStore is empty or there's an error reading, it returns the default value of Proto. */ - private suspend fun getDesktopRepositoryState( - userId: Int = DEFAULT_USER_ID + suspend fun getDesktopRepositoryState( + userId: Int ): DesktopRepositoryState? = try { dataStoreFlow @@ -85,12 +85,20 @@ class DesktopPersistentRepository( null } + suspend fun getUserDesktopRepositoryMap(): Map<Int, DesktopRepositoryState>? = + try { + dataStoreFlow.first().desktopRepoByUserMap + } catch (e: Exception) { + Log.e(TAG, "Unable to read from datastore", e) + null + } + /** * Reads the [Desktop] of a desktop filtering by the [userId] and [desktopId]. Executes the * [callback] using the [mainCoroutineScope]. */ suspend fun readDesktop( - userId: Int = DEFAULT_USER_ID, + userId: Int, desktopId: Int = DEFAULT_DESKTOP_ID, ): Desktop? = try { @@ -103,7 +111,7 @@ class DesktopPersistentRepository( /** Adds or updates a desktop stored in the datastore */ suspend fun addOrUpdateDesktop( - userId: Int = DEFAULT_USER_ID, + userId: Int, desktopId: Int = 0, visibleTasks: ArraySet<Int> = ArraySet(), minimizedTasks: ArraySet<Int> = ArraySet(), @@ -111,9 +119,9 @@ class DesktopPersistentRepository( ) { // TODO: b/367609270 - Improve the API to support multi-user try { - dataStore.updateData { desktopPersistentRepositories: DesktopPersistentRepositories -> + dataStore.updateData { persistentRepositories: DesktopPersistentRepositories -> val currentRepository = - desktopPersistentRepositories.getDesktopRepoByUserOrDefault( + persistentRepositories.getDesktopRepoByUserOrDefault( userId, DesktopRepositoryState.getDefaultInstance()) val desktop = getDesktop(currentRepository, desktopId) @@ -125,7 +133,7 @@ class DesktopPersistentRepository( ) .updateZOrder(freeformTasksInZOrder) - desktopPersistentRepositories + persistentRepositories .toBuilder() .putDesktopRepoByUser( userId, @@ -135,7 +143,7 @@ class DesktopPersistentRepository( .build()) .build() } - } catch (exception: IOException) { + } catch (exception: Exception) { Log.e( TAG, "Error in updating desktop mode related data, data is " + @@ -154,7 +162,6 @@ class DesktopPersistentRepository( private const val TAG = "DesktopPersistenceRepo" private const val DESKTOP_REPOSITORIES_DATASTORE_FILE = "desktop_persistent_repositories.pb" - private const val DEFAULT_USER_ID = 1000 private const val DEFAULT_DESKTOP_ID = 0 object DesktopPersistentRepositoriesSerializer : Serializer<DesktopPersistentRepositories> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt index 771c3d1cb9a1..a26ebbf4c99a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt @@ -16,9 +16,9 @@ package com.android.wm.shell.desktopmode.persistence -import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories -/** Interface for initializing the [DesktopRepository]. */ +/** Interface for initializing the [DesktopUserRepositories]. */ fun interface DesktopRepositoryInitializer { - fun initialize(repository: DesktopRepository) + fun initialize(userRepositories: DesktopUserRepositories) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt index d8156561ff19..9539cbe0e677 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt @@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode.persistence import android.content.Context import android.window.DesktopModeFlags import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import kotlinx.coroutines.CoroutineScope @@ -35,32 +36,52 @@ class DesktopRepositoryInitializerImpl( private val persistentRepository: DesktopPersistentRepository, @ShellMainThread private val mainCoroutineScope: CoroutineScope, ) : DesktopRepositoryInitializer { - override fun initialize(repository: DesktopRepository) { + override fun initialize(userRepositories: DesktopUserRepositories) { if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) return // TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized mainCoroutineScope.launch { - val desktop = persistentRepository.readDesktop() ?: return@launch - - val maxTasks = - DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 } - ?: desktop.zOrderedTasksCount - - var visibleTasksCount = 0 - desktop.zOrderedTasksList - // Reverse it so we initialize the repo from bottom to top. - .reversed() - .mapNotNull { taskId -> desktop.tasksByTaskIdMap[taskId] } - .forEach { task -> - if (task.desktopTaskState == DesktopTaskState.VISIBLE - && visibleTasksCount < maxTasks - ) { - visibleTasksCount++ - repository.addTask(desktop.displayId, task.taskId, isVisible = false) - } else { - repository.addTask(desktop.displayId, task.taskId, isVisible = false) - repository.minimizeTask(desktop.displayId, task.taskId) + val desktopUserPersistentRepositoryMap = + persistentRepository.getUserDesktopRepositoryMap() ?: return@launch + for (userId in desktopUserPersistentRepositoryMap.keys) { + val repository = userRepositories.getProfile(userId) + val desktopRepositoryState = + persistentRepository.getDesktopRepositoryState(userId) ?: continue + val desktopByDesktopIdMap = desktopRepositoryState.desktopMap + for (desktopId in desktopByDesktopIdMap.keys) { + val persistentDesktop = + persistentRepository.readDesktop(userId, desktopId) ?: continue + val maxTasks = + DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 } + ?: persistentDesktop.zOrderedTasksCount + var visibleTasksCount = 0 + persistentDesktop.zOrderedTasksList + // Reverse it so we initialize the repo from bottom to top. + .reversed() + .mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] } + .forEach { task -> + if (task.desktopTaskState == DesktopTaskState.VISIBLE + && visibleTasksCount < maxTasks) { + visibleTasksCount++ + repository.addTask( + persistentDesktop.displayId, + task.taskId, + isVisible = false + ) + } else { + repository.addTask( + persistentDesktop.displayId, + task.taskId, + isVisible = false + ) + repository.minimizeTask( + persistentDesktop.displayId, + task.taskId + ) + } + } } } } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index cd20d97c7964..a17d55fdc2fe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -30,6 +30,7 @@ import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; @@ -49,7 +50,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, private final Context mContext; private final ShellTaskOrganizer mShellTaskOrganizer; - private final Optional<DesktopRepository> mDesktopRepository; + private final Optional<DesktopUserRepositories> mDesktopUserRepositories; private final Optional<DesktopTasksController> mDesktopTasksController; private final WindowDecorViewModel mWindowDecorationViewModel; private final LaunchAdjacentController mLaunchAdjacentController; @@ -61,7 +62,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, Optional<DesktopTasksController> desktopTasksController, LaunchAdjacentController launchAdjacentController, WindowDecorViewModel windowDecorationViewModel, @@ -69,7 +70,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, mContext = context; mShellTaskOrganizer = shellTaskOrganizer; mWindowDecorationViewModel = windowDecorationViewModel; - mDesktopRepository = desktopRepository; + mDesktopUserRepositories = desktopUserRepositories; mDesktopTasksController = desktopTasksController; mLaunchAdjacentController = launchAdjacentController; mTaskChangeListener = taskChangeListener; @@ -99,8 +100,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, if (!DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue() && DesktopModeStatus.canEnterDesktopMode(mContext)) { - mDesktopRepository.ifPresent(repository -> { - repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible); + mDesktopUserRepositories.ifPresent(userRepositories -> { + DesktopRepository currentRepo = userRepositories.getProfile(taskInfo.userId); + currentRepo.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible); }); } updateLaunchAdjacentController(); @@ -113,21 +115,22 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, mTasks.remove(taskInfo.taskId); if (!DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue() && - DesktopModeStatus.canEnterDesktopMode(mContext)) { - mDesktopRepository.ifPresent(repository -> { - // TODO: b/370038902 - Handle Activity#finishAndRemoveTask. - if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() - || repository.isClosingTask(taskInfo.taskId)) { - // A task that's vanishing should be removed: - // - If it's closed by the X button which means it's marked as a closing task. - repository.removeClosingTask(taskInfo.taskId); - repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId); - } else { - repository.updateTask(taskInfo.displayId, taskInfo.taskId, /* isVisible= */ - false); - repository.minimizeTask(taskInfo.displayId, taskInfo.taskId); - } - }); + DesktopModeStatus.canEnterDesktopMode(mContext) + && mDesktopUserRepositories.isPresent()) { + DesktopRepository repository = + mDesktopUserRepositories.get().getProfile(taskInfo.userId); + // TODO: b/370038902 - Handle Activity#finishAndRemoveTask. + if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() + || repository.isClosingTask(taskInfo.taskId)) { + // A task that's vanishing should be removed: + // - If it's closed by the X button which means it's marked as a closing task. + repository.removeClosingTask(taskInfo.taskId); + repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId); + } else { + repository.updateTask(taskInfo.displayId, taskInfo.taskId, /* isVisible= */ + false); + repository.minimizeTask(taskInfo.displayId, taskInfo.taskId); + } } mWindowDecorationViewModel.onTaskVanished(taskInfo); updateLaunchAdjacentController(); @@ -148,11 +151,11 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, // does not propagate all task info changes. mTaskChangeListener.ifPresent(listener -> listener.onNonTransitionTaskChanging(taskInfo)); - } else { - mDesktopRepository.ifPresent(repository -> { - repository.updateTask(taskInfo.displayId, taskInfo.taskId, - taskInfo.isVisible); - }); + } else if (mDesktopUserRepositories.isPresent()) { + DesktopRepository currentRepo = + mDesktopUserRepositories.get().getProfile(taskInfo.userId); + currentRepo.updateTask(taskInfo.displayId, taskInfo.taskId, + taskInfo.isVisible); } } updateLaunchAdjacentController(); @@ -176,10 +179,11 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Focus Changed: #%d focused=%b", taskInfo.taskId, taskInfo.isFocused); - if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) { - mDesktopRepository.ifPresent(repository -> { - repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible); - }); + if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused + && mDesktopUserRepositories.isPresent()) { + DesktopRepository repository = + mDesktopUserRepositories.get().getProfile(taskInfo.userId); + repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index fb4afe41e193..af187682d379 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -94,6 +94,7 @@ import com.android.wm.shell.common.pip.PipPerfHintController; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.animation.Interpolators; @@ -152,7 +153,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private final Optional<SplitScreenController> mSplitScreenOptional; @Nullable private final PipPerfHintController mPipPerfHintController; - private final Optional<DesktopRepository> mDesktopRepositoryOptional; + private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional; private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private final DisplayController mDisplayController; protected final ShellTaskOrganizer mTaskOrganizer; @@ -398,7 +399,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @NonNull PipParamsChangedForwarder pipParamsChangedForwarder, Optional<SplitScreenController> splitScreenOptional, Optional<PipPerfHintController> pipPerfHintControllerOptional, - Optional<DesktopRepository> desktopRepositoryOptional, + Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, @NonNull DisplayController displayController, @NonNull PipUiEventLogger pipUiEventLogger, @@ -426,7 +427,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); mSplitScreenOptional = splitScreenOptional; mPipPerfHintController = pipPerfHintControllerOptional.orElse(null); - mDesktopRepositoryOptional = desktopRepositoryOptional; + mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mDisplayController = displayController; mTaskOrganizer = shellTaskOrganizer; @@ -764,7 +765,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // previous freeform bounds that is saved in DesktopRepository. // 2) If PiP was entered through other means (e.g. user swipe up), exit to initial // freeform bounds. Note that this case has a flicker at the moment (b/379984108). - Rect freeformBounds = mDesktopRepositoryOptional.get().removeBoundsBeforeMinimize( + Rect freeformBounds = getCurrentRepo().removeBoundsBeforeMinimize( mTaskInfo.taskId); return freeformBounds != null ? freeformBounds @@ -779,11 +780,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, /** Returns whether PiP is exiting while we're in desktop mode. */ // TODO(b/377581840): Update this check to include non-minimized cases, e.g. split to PiP etc. private boolean isPipExitingToDesktopMode() { - return Flags.enableDesktopWindowingPip() && mDesktopRepositoryOptional.isPresent() - && (mDesktopRepositoryOptional.get().getVisibleTaskCount(mTaskInfo.displayId) > 0 + DesktopRepository currentRepo = getCurrentRepo(); + return Flags.enableDesktopWindowingPip() && currentRepo != null + && (currentRepo.getVisibleTaskCount(mTaskInfo.displayId) > 0 || isDisplayInFreeform()); } + private DesktopRepository getCurrentRepo() { + return mDesktopUserRepositoriesOptional.map(DesktopUserRepositories::getCurrent).orElse( + null); + } + private void exitLaunchIntoPipTask(WindowContainerTransaction wct) { wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */); mTaskOrganizer.applyTransaction(wct); 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 7145e0699765..4461a5c6a70c 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 @@ -39,7 +39,7 @@ import com.android.window.flags.Flags; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; -import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; @@ -58,7 +58,7 @@ public class PipScheduler { private final PipBoundsState mPipBoundsState; private final ShellExecutor mMainExecutor; private final PipTransitionState mPipTransitionState; - private final Optional<DesktopRepository> mDesktopRepositoryOptional; + private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional; private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private PipTransitionController mPipTransitionController; private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory @@ -72,13 +72,13 @@ public class PipScheduler { PipBoundsState pipBoundsState, ShellExecutor mainExecutor, PipTransitionState pipTransitionState, - Optional<DesktopRepository> desktopRepositoryOptional, + Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { mContext = context; mPipBoundsState = pipBoundsState; mMainExecutor = mainExecutor; mPipTransitionState = pipTransitionState; - mDesktopRepositoryOptional = desktopRepositoryOptional; + mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mSurfaceControlTransactionFactory = @@ -268,8 +268,8 @@ public class PipScheduler { /** Returns whether PiP is exiting while we're in desktop mode. */ private boolean isPipExitingToDesktopMode() { - return Flags.enableDesktopWindowingPip() && mDesktopRepositoryOptional.isPresent() - && (mDesktopRepositoryOptional.get().getVisibleTaskCount( + return Flags.enableDesktopWindowingPip() && mDesktopUserRepositoriesOptional.isPresent() + && (mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount( Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId) > 0 || isDisplayInFreeform()); } 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 8f02c1b157b5..b171db2c3793 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 @@ -627,6 +627,12 @@ public class PipTransition extends PipTransitionController implements finishTransition(); }); cacheAndStartTransitionAnimator(animator); + + // Save the PiP bounds in case, we re-enter the PiP with the same component. + float snapFraction = mPipBoundsAlgorithm.getSnapFraction( + mPipBoundsState.getBounds()); + mPipBoundsState.saveReentryState(snapFraction); + return true; } @@ -912,11 +918,6 @@ public class PipTransition extends PipTransitionController implements "Unexpected bundle for " + mPipTransitionState); break; case PipTransitionState.EXITED_PIP: - // Save the PiP bounds in case, we re-enter the PiP with the same component. - float snapFraction = mPipBoundsAlgorithm.getSnapFraction( - mPipBoundsState.getBounds()); - mPipBoundsState.saveReentryState(snapFraction); - mPipTransitionState.setPinnedTaskLeash(null); mPipTransitionState.setPipTaskInfo(null); break; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 363c95fcf010..441f96728d0c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -63,6 +63,7 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; @@ -72,6 +73,7 @@ import com.android.wm.shell.shared.split.SplitBounds; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.sysui.UserChangeListener; import java.io.PrintWriter; import java.util.ArrayList; @@ -89,13 +91,14 @@ import java.util.function.Consumer; */ public class RecentTasksController implements TaskStackListenerCallback, RemoteCallable<RecentTasksController>, DesktopRepository.ActiveTasksListener, - TaskStackTransitionObserver.TaskStackTransitionObserverListener { + TaskStackTransitionObserver.TaskStackTransitionObserverListener, UserChangeListener { private static final String TAG = RecentTasksController.class.getSimpleName(); private final Context mContext; private final ShellController mShellController; private final ShellCommandHandler mShellCommandHandler; - private final Optional<DesktopRepository> mDesktopRepository; + private final Optional<DesktopUserRepositories> mDesktopUserRepositories; + private final ShellExecutor mMainExecutor; private final TaskStackListenerImpl mTaskStackListener; private final RecentTasksImpl mImpl = new RecentTasksImpl(); @@ -108,6 +111,8 @@ public class RecentTasksController implements TaskStackListenerCallback, // Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1) private final SparseIntArray mSplitTasks = new SparseIntArray(); + + private int mUserId; /** * Maps taskId to {@link SplitBounds} for both taskIDs. * Meaning there will be two taskId integers mapping to the same object. @@ -133,7 +138,7 @@ public class RecentTasksController implements TaskStackListenerCallback, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, ActivityTaskManager activityTaskManager, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, TaskStackTransitionObserver taskStackTransitionObserver, @ShellMainThread ShellExecutor mainExecutor ) { @@ -141,7 +146,7 @@ public class RecentTasksController implements TaskStackListenerCallback, return null; } return new RecentTasksController(context, shellInit, shellController, shellCommandHandler, - taskStackListener, activityTaskManager, desktopRepository, + taskStackListener, activityTaskManager, desktopUserRepositories, taskStackTransitionObserver, mainExecutor); } @@ -151,7 +156,7 @@ public class RecentTasksController implements TaskStackListenerCallback, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, ActivityTaskManager activityTaskManager, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, TaskStackTransitionObserver taskStackTransitionObserver, ShellExecutor mainExecutor) { mContext = context; @@ -160,7 +165,7 @@ public class RecentTasksController implements TaskStackListenerCallback, mActivityTaskManager = activityTaskManager; mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC); mTaskStackListener = taskStackListener; - mDesktopRepository = desktopRepository; + mDesktopUserRepositories = desktopUserRepositories; mTaskStackTransitionObserver = taskStackTransitionObserver; mMainExecutor = mainExecutor; shellInit.addInitCallback(this::onInit, this); @@ -175,12 +180,15 @@ public class RecentTasksController implements TaskStackListenerCallback, } @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) - private void onInit() { + void onInit() { mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS, this::createExternalInterface, this); mShellCommandHandler.addDumpCallback(this::dump, this); + mUserId = ActivityManager.getCurrentUser(); + mDesktopUserRepositories.ifPresent( + desktopUserRepositories -> + desktopUserRepositories.getCurrent().addActiveTaskListener(this)); mTaskStackListener.addListener(this); - mDesktopRepository.ifPresent(it -> it.addActiveTaskListener(this)); mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this, mMainExecutor); mContext.getSystemService(KeyguardManager.class).addKeyguardLockedStateListener( @@ -291,9 +299,9 @@ public class RecentTasksController implements TaskStackListenerCallback, */ @Override public void onRecentTaskRemovedForAddTask(int taskId) { - mDesktopRepository.ifPresent( - repo -> repo.removeFreeformTask(INVALID_DISPLAY, taskId) - ); + mDesktopUserRepositories.ifPresent( + desktopUserRepositories -> desktopUserRepositories.getCurrent().removeFreeformTask( + INVALID_DISPLAY, taskId)); } public void onTaskAdded(RunningTaskInfo taskInfo) { @@ -512,10 +520,9 @@ public class RecentTasksController implements TaskStackListenerCallback, // If it's not in the mapping, then it was already paired with another task continue; } - - if (DesktopModeStatus.canEnterDesktopMode(mContext) - && mDesktopRepository.isPresent() - && mDesktopRepository.get().isActiveTask(taskInfo.taskId)) { + if (DesktopModeStatus.canEnterDesktopMode(mContext) && + mDesktopUserRepositories.isPresent() + && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskInfo.taskId)) { // Freeform tasks will be added as a separate entry if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) { mostRecentFreeformTaskIndex = groupedTasks.size(); @@ -531,7 +538,7 @@ public class RecentTasksController implements TaskStackListenerCallback, taskInfo.lastNonFullscreenBounds.top); } freeformTasks.add(taskInfo); - if (mDesktopRepository.get().isMinimizedTask(taskInfo.taskId)) { + if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskInfo.taskId)) { minimizedFreeformTasks.add(taskInfo.taskId); } continue; @@ -703,6 +710,21 @@ public class RecentTasksController implements TaskStackListenerCallback, } } + @Override + public void onUserChanged(int newUserId, @NonNull Context userContext) { + if (mDesktopUserRepositories.isEmpty()) return; + + DesktopRepository previousUserRepository = + mDesktopUserRepositories.get().getProfile(mUserId); + mUserId = newUserId; + DesktopRepository currentUserRepository = + mDesktopUserRepositories.get().getProfile(newUserId); + + // No-op if both profile ids map to the same user. + if (previousUserRepository.getUserId() == currentUserRepository.getUserId()) return; + previousUserRepository.removeActiveTasksListener(this); + currentUserRepository.addActiveTaskListener(this); + } /** * The interface for calls from outside the host process. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java index 4ea4613185e1..d8884f6d8d38 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java @@ -41,9 +41,9 @@ public class DefaultSurfaceAnimator { @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) { + @Nullable Rect clipRect) { final DefaultAnimationAdapter adapter = new DefaultAnimationAdapter(anim, leash, - position, clipRect, cornerRadius, isActivity); + position, clipRect, cornerRadius); buildSurfaceAnimation(animations, anim, finishCallback, pool, mainExecutor, adapter); } @@ -138,11 +138,9 @@ public class DefaultSurfaceAnimator { @Nullable final Rect mClipRect; @Nullable private final Rect mAnimClipRect; final float mCornerRadius; - final boolean mIsActivity; DefaultAnimationAdapter(@NonNull Animation anim, @NonNull SurfaceControl leash, - @Nullable Point position, @Nullable Rect clipRect, float cornerRadius, - boolean isActivity) { + @Nullable Point position, @Nullable Rect clipRect, float cornerRadius) { super(leash); mAnim = anim; mPosition = (position != null && (position.x != 0 || position.y != 0)) @@ -150,7 +148,6 @@ public class DefaultSurfaceAnimator { mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null; mAnimClipRect = mClipRect != null ? new Rect() : null; mCornerRadius = cornerRadius; - mIsActivity = isActivity; } @Override @@ -160,10 +157,6 @@ public class DefaultSurfaceAnimator { final SurfaceControl leash = mLeash; transformation.clear(); mAnim.getTransformation(currentPlayTime, transformation); - if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader() - && mIsActivity && mAnim.getExtensionEdges() != 0) { - t.setEdgeExtensionEffect(leash, mAnim.getExtensionEdges()); - } if (mPosition != null) { transformation.getMatrix().postTranslate(mPosition.x, mPosition.y); } 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 9fcf98b9efc2..e80016d07f15 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 @@ -506,6 +506,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { if (!isTask && a.getExtensionEdges() != 0x0) { if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) { + startTransaction.setEdgeExtensionEffect( + change.getLeash(), a.getExtensionEdges()); finishTransaction.setEdgeExtensionEffect(change.getLeash(), /* edge */ 0); } else { if (!TransitionUtil.isOpeningType(mode)) { @@ -564,7 +566,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, mTransactionPool, mMainExecutor, animRelOffset, cornerRadius, - clipRect, change.getActivityComponent() != null); + clipRect); final TransitionInfo.AnimationOptions options; if (Flags.moveAnimationOptionsToChange()) { @@ -876,8 +878,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a.restrictDuration(MAX_ANIMATION_DURATION); a.scaleCurrentDuration(mTransitionAnimationScaleSetting); buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool, - mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(), - change.getActivityComponent() != null); + mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds()); } private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations, @@ -901,8 +902,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a.restrictDuration(MAX_ANIMATION_DURATION); a.scaleCurrentDuration(mTransitionAnimationScaleSetting); buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool, - mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(), - change.getActivityComponent() != null); + mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds()); } private static int getWallpaperTransitType(TransitionInfo info) { 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 6f3aa11a8f52..aa42b7f0ca76 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 @@ -347,21 +347,21 @@ class ScreenRotationAnimation { @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, - null /* clipRect */, false /* isActivity */); + null /* clipRect */); } private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, - null /* clipRect */, false /* isActivity */); + null /* clipRect */); } private void buildScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { buildSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, - null /* clipRect */, false /* isActivity */); + null /* clipRect */); } private void buildLumaAnimation(@NonNull ArrayList<Animator> animations, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index c9f2d2e8c0e2..885f3dbed275 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -63,6 +63,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; import com.android.wm.shell.shared.FocusTransitionListener; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.FocusTransitionObserver; @@ -119,8 +120,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT public CaptionWindowDecorViewModel( Context context, Handler mainHandler, + @ShellMainThread ShellExecutor shellExecutor, @ShellBackgroundThread ShellExecutor bgExecutor, - ShellExecutor shellExecutor, Choreographer mainChoreographer, IWindowManager windowManager, ShellInit shellInit, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt index 101467df03ac..ff52a45c94e2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt @@ -32,7 +32,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.window.flags.Flags import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.common.DisplayController -import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer @@ -51,7 +51,7 @@ class DesktopHeaderManageWindowsMenu( private val displayController: DisplayController, private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer, context: Context, - private val desktopRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val surfaceControlBuilderSupplier: Supplier<SurfaceControl.Builder>, private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>, snapshotList: List<Pair<Int, TaskSnapshot>>, @@ -76,6 +76,7 @@ class DesktopHeaderManageWindowsMenu( val flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + val desktopRepository = desktopUserRepositories.getProfile(callerTaskInfo.userId) menuViewContainer = if (Flags.enableFullyImmersiveInDesktop() && desktopRepository.isTaskInFullImmersiveState(callerTaskInfo.taskId)) { // Use system view container so that forcibly shown system bars take effect in diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 04ef7c1dc461..e8b02dcb7a71 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -111,13 +111,17 @@ import com.android.wm.shell.desktopmode.DesktopImmersiveController; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger; import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum; +import com.android.wm.shell.desktopmode.DesktopModeUtils; import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator; import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition; import com.android.wm.shell.desktopmode.DesktopTasksLimiter; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction; +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeUtilsKt; import com.android.wm.shell.desktopmode.education.AppHandleEducationController; import com.android.wm.shell.desktopmode.education.AppToWebEducationController; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; @@ -166,7 +170,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private final ActivityTaskManager mActivityTaskManager; private final ShellCommandHandler mShellCommandHandler; private final ShellTaskOrganizer mTaskOrganizer; - private final DesktopRepository mDesktopRepository; + private final DesktopUserRepositories mDesktopUserRepositories; private final ShellController mShellController; private final Context mContext; private final @ShellMainThread Handler mMainHandler; @@ -244,7 +248,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, ShellCommandHandler shellCommandHandler, IWindowManager windowManager, ShellTaskOrganizer taskOrganizer, - DesktopRepository desktopRepository, + DesktopUserRepositories desktopUserRepositories, DisplayController displayController, ShellController shellController, DisplayInsetsController displayInsetsController, @@ -275,7 +279,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, shellCommandHandler, windowManager, taskOrganizer, - desktopRepository, + desktopUserRepositories, displayController, shellController, displayInsetsController, @@ -315,7 +319,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, ShellCommandHandler shellCommandHandler, IWindowManager windowManager, ShellTaskOrganizer taskOrganizer, - DesktopRepository desktopRepository, + DesktopUserRepositories desktopUserRepositories, DisplayController displayController, ShellController shellController, DisplayInsetsController displayInsetsController, @@ -349,7 +353,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mBgExecutor = bgExecutor; mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); mTaskOrganizer = taskOrganizer; - mDesktopRepository = desktopRepository; + mDesktopUserRepositories = desktopUserRepositories; mShellController = shellController; mDisplayController = displayController; mDisplayInsetsController = displayInsetsController; @@ -580,34 +584,59 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, >= MANAGE_WINDOWS_MINIMUM_INSTANCES); } - private void onMaximizeOrRestore(int taskId, String source, ResizeTrigger resizeTrigger, - MotionEvent motionEvent) { + private void onToggleSizeInteraction( + int taskId, @NonNull ToggleTaskSizeInteraction.AmbiguousSource source, + @Nullable MotionEvent motionEvent) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); if (decoration == null) { return; } - mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, resizeTrigger, - DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent), () -> { - mInteractionJankMonitor.begin( - decoration.mTaskSurface, mContext, mMainHandler, - Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source); - return null; - }, () -> { - mInteractionJankMonitor.begin( - decoration.mTaskSurface, mContext, mMainHandler, - Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW, source); - return null; - }); + final ToggleTaskSizeInteraction interaction = + createToggleSizeInteraction(decoration, source, motionEvent); + if (interaction == null) { + return; + } + if (interaction.getCujTracing() != null) { + mInteractionJankMonitor.begin( + decoration.mTaskSurface, mContext, mMainHandler, + interaction.getCujTracing(), interaction.getJankTag()); + } + mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, interaction); decoration.closeHandleMenu(); decoration.closeMaximizeMenu(); } - private void onEnterOrExitImmersive(int taskId) { - final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); + private ToggleTaskSizeInteraction createToggleSizeInteraction( + @NonNull DesktopModeWindowDecoration decoration, + @NonNull ToggleTaskSizeInteraction.AmbiguousSource source, + @Nullable MotionEvent motionEvent) { + final RunningTaskInfo taskInfo = decoration.mTaskInfo; + + final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(taskInfo.displayId); + if (displayLayout == null) { + return null; + } + final Rect stableBounds = new Rect(); + displayLayout.getStableBounds(stableBounds); + boolean isMaximized = DesktopModeUtils.isTaskMaximized(taskInfo, stableBounds); + + return new ToggleTaskSizeInteraction( + isMaximized + ? ToggleTaskSizeInteraction.Direction.RESTORE + : ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeUtilsKt.toSource(source, isMaximized), + DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent) + ); + } + + private void onEnterOrExitImmersive(RunningTaskInfo taskInfo) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); if (decoration == null) { return; } - if (mDesktopRepository.isTaskInFullImmersiveState(taskId)) { + final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile( + taskInfo.userId); + if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) { mDesktopModeUiEventLogger.log(decoration.mTaskInfo, DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE); mDesktopImmersiveController.moveTaskToNonImmersive( @@ -802,7 +831,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private boolean mIsResizeGesture; private boolean mIsDragging; private boolean mTouchscreenInUse; - private boolean mHasLongClicked; private int mDragPointerId = -1; private MotionEvent mMotionEvent; @@ -868,12 +896,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, && TaskInfoKt.getRequestingImmersive(decoration.mTaskInfo)) { // Task is requesting immersive, so it should either enter or exit immersive, // depending on immersive state. - onEnterOrExitImmersive(decoration.mTaskInfo.taskId); + onEnterOrExitImmersive(decoration.mTaskInfo); } else { // Full immersive is disabled or task doesn't request/support it, so just // toggle between maximize/restore states. - onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button", - ResizeTrigger.MAXIMIZE_BUTTON, mMotionEvent); + onToggleSizeInteraction(decoration.mTaskInfo.taskId, + ToggleTaskSizeInteraction.AmbiguousSource.HEADER_BUTTON, mMotionEvent); } } else if (id == R.id.minimize_window) { mDesktopTasksController.minimizeTask(decoration.mTaskInfo); @@ -961,7 +989,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, if (decoration.isMaximizeMenuActive()) { decoration.closeMaximizeMenu(); } else { - mHasLongClicked = true; mDesktopModeUiEventLogger.log(decoration.mTaskInfo, DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_REVEAL_MENU); decoration.createMaximizeMenu(); @@ -1000,6 +1027,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private void moveTaskToFront(RunningTaskInfo taskInfo) { if (!mFocusTransitionObserver.hasGlobalFocus(taskInfo)) { + mDesktopModeUiEventLogger.log(taskInfo, + DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_TAP_TO_REFOCUS); mDesktopTasksController.moveTaskToFront(taskInfo); } } @@ -1056,8 +1085,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window || id == R.id.open_menu_button || id == R.id.minimize_window); + final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile( + taskInfo.userId); final boolean dragAllowed = - !mDesktopRepository.isTaskInFullImmersiveState(taskInfo.taskId); + !desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId); switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { if (dragAllowed) { @@ -1068,7 +1099,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, updateDragStatus(e.getActionMasked()); mOnDragStartInitialBounds.set(initialBounds); } - mHasLongClicked = false; // Do not consume input event if a button is touched, otherwise it would // prevent the button's ripple effect from showing. return !touchingButton; @@ -1102,6 +1132,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, if (!wasDragging) { return false; } + mDesktopModeUiEventLogger.log(taskInfo, + DesktopUiEventEnum.DESKTOP_WINDOW_MOVE_BY_HEADER_DRAG); if (e.findPointerIndex(mDragPointerId) == -1) { mDragPointerId = e.getPointerId(0); } @@ -1123,7 +1155,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, newTaskBounds, decoration.calculateValidDragArea(), new Rect(mOnDragStartInitialBounds), e, mWindowDecorByTaskId.get(taskInfo.taskId)); - if (touchingButton && !mHasLongClicked) { + if (touchingButton) { // We need the input event to not be consumed here to end the ripple // effect on the touched button. We will reset drag state in the ensuing // onClick call that results. @@ -1165,11 +1197,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, && action != MotionEvent.ACTION_CANCEL)) { return false; } - if (mDesktopRepository.isTaskInFullImmersiveState(mTaskId)) { + final DesktopRepository desktopRepository = mDesktopUserRepositories.getCurrent(); + if (desktopRepository.isTaskInFullImmersiveState(mTaskId)) { // Disallow double-tap to resize when in full immersive. return false; } - onMaximizeOrRestore(mTaskId, "double_tap", ResizeTrigger.DOUBLE_TAP_APP_HEADER, e); + onToggleSizeInteraction(mTaskId, + ToggleTaskSizeInteraction.AmbiguousSource.DOUBLE_TAP, e); return true; } } @@ -1577,7 +1611,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mContext.createContextAsUser(UserHandle.of(taskInfo.userId), 0 /* flags */), mDisplayController, mSplitScreenController, - mDesktopRepository, + mDesktopUserRepositories, mTaskOrganizer, taskInfo, taskSurface, @@ -1608,12 +1642,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, final DesktopModeTouchEventListener touchEventListener = new DesktopModeTouchEventListener(taskInfo, taskPositioner); windowDecoration.setOnMaximizeOrRestoreClickListener(() -> { - onMaximizeOrRestore(taskInfo.taskId, "maximize_menu", ResizeTrigger.MAXIMIZE_MENU, + onToggleSizeInteraction(taskInfo.taskId, + ToggleTaskSizeInteraction.AmbiguousSource.MAXIMIZE_MENU, touchEventListener.mMotionEvent); return Unit.INSTANCE; }); windowDecoration.setOnImmersiveOrRestoreClickListener(() -> { - onEnterOrExitImmersive(taskInfo.taskId); + onEnterOrExitImmersive(taskInfo); return Unit.INSTANCE; }); windowDecoration.setOnLeftSnapClickListener(() -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 5eb031218ee1..bd84cccc4e0b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -99,7 +99,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.CaptionState; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; -import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; @@ -206,14 +206,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired; private final MultiInstanceHelper mMultiInstanceHelper; private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository; - private final DesktopRepository mDesktopRepository; + private final DesktopUserRepositories mDesktopUserRepositories; public DesktopModeWindowDecoration( Context context, @NonNull Context userContext, DisplayController displayController, SplitScreenController splitScreenController, - DesktopRepository desktopRepository, + DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -228,10 +228,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin MultiInstanceHelper multiInstanceHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, DesktopModeEventLogger desktopModeEventLogger) { - this (context, userContext, displayController, splitScreenController, desktopRepository, - taskOrganizer, taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue, - appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser, - assistContentRequester, + this (context, userContext, displayController, splitScreenController, + desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, handler, + bgExecutor, choreographer, syncQueue, appHeaderViewHolderFactory, + rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper( context.getSystemService(WindowManager.class)), @@ -246,7 +246,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @NonNull Context userContext, DisplayController displayController, SplitScreenController splitScreenController, - DesktopRepository desktopRepository, + DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -287,7 +287,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mMultiInstanceHelper = multiInstanceHelper; mWindowManagerWrapper = windowManagerWrapper; mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository; - mDesktopRepository = desktopRepository; + mDesktopUserRepositories = desktopUserRepositories; } /** @@ -437,7 +437,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin public void updateDisabledResizingEdge( DragResizeWindowGeometry.DisabledEdge disabledResizingEdge, boolean shouldDelayUpdate) { mDisabledResizingEdge = disabledResizingEdge; - final boolean inFullImmersive = mDesktopRepository + final boolean inFullImmersive = mDesktopUserRepositories.getCurrent() .isTaskInFullImmersiveState(mTaskInfo.taskId); if (shouldDelayUpdate) { return; @@ -541,7 +541,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mOpenByDefaultDialog.relayout(taskInfo); } - final boolean inFullImmersive = mDesktopRepository + final boolean inFullImmersive = mDesktopUserRepositories.getProfile(taskInfo.userId) .isTaskInFullImmersiveState(taskInfo.taskId); updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible, @@ -1302,9 +1302,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mMaximizeMenu = mMaximizeMenuFactory.create(mSyncQueue, mRootTaskDisplayAreaOrganizer, mDisplayController, mTaskInfo, mContext, calculateMaximizeMenuPosition(menuWidth), mSurfaceControlTransactionSupplier); + mMaximizeMenu.show( /* isTaskInImmersiveMode= */ Flags.enableFullyImmersiveInDesktop() - && mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId), + && mDesktopUserRepositories.getProfile(mTaskInfo.userId) + .isTaskInFullImmersiveState(mTaskInfo.taskId), /* menuWidth= */ menuWidth, /* showImmersiveOption= */ Flags.enableFullyImmersiveInDesktop() && TaskInfoKt.getRequestingImmersive(mTaskInfo), @@ -1394,7 +1396,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin && mMinimumInstancesFound; final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion .shouldShowChangeAspectRatioButton(mTaskInfo); - final boolean inDesktopImmersive = mDesktopRepository + final boolean inDesktopImmersive = mDesktopUserRepositories.getProfile(mTaskInfo.userId) .isTaskInFullImmersiveState(mTaskInfo.taskId); final boolean isBrowserApp = isBrowserApp(); mHandleMenu = mHandleMenuFactory.create( @@ -1433,7 +1435,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin /* openInBrowserClickListener= */ (intent) -> { mOpenInBrowserClickListener.accept(intent); onCapturedLinkExpired(); - if (Flags.enableDesktopWindowingAppToWebEducation()) { + if (Flags.enableDesktopWindowingAppToWebEducationIntegration()) { mWindowDecorCaptionHandleRepository.onAppToWebUsage(); } return Unit.INSTANCE; @@ -1474,7 +1476,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mDisplayController, mRootTaskDisplayAreaOrganizer, mContext, - mDesktopRepository, + mDesktopUserRepositories, mSurfaceControlBuilderSupplier, mSurfaceControlTransactionSupplier, snapshotList, @@ -1686,7 +1688,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin /** Returns true if at least one education flag is enabled. */ private boolean isEducationEnabled() { return Flags.enableDesktopWindowingAppHandleEducation() - || Flags.enableDesktopWindowingAppToWebEducation(); + || Flags.enableDesktopWindowingAppToWebEducationIntegration(); } @Override @@ -1770,7 +1772,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin void setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition) { if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) return; final boolean inFullImmersive = - mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId); + mDesktopUserRepositories.getProfile(mTaskInfo.userId) + .isTaskInFullImmersiveState(mTaskInfo.taskId); asAppHeader(mWindowDecorViewHolder).bindData(new AppHeaderViewHolder.HeaderData( mTaskInfo, TaskInfoKt.getRequestingImmersive(mTaskInfo), @@ -1798,8 +1801,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return !animatingTaskResizeOrReposition; } final boolean inImmersiveAndRequesting = - mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId) - && TaskInfoKt.getRequestingImmersive(mTaskInfo); + mDesktopUserRepositories.getProfile(mTaskInfo.userId) + .isTaskInFullImmersiveState(mTaskInfo.taskId) + && TaskInfoKt.getRequestingImmersive(mTaskInfo); return !animatingTaskResizeOrReposition && !inImmersiveAndRequesting; } @@ -1820,7 +1824,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @NonNull Context userContext, DisplayController displayController, SplitScreenController splitScreenController, - DesktopRepository desktopRepository, + DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -1840,7 +1844,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin userContext, displayController, splitScreenController, - desktopRepository, + desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt new file mode 100644 index 000000000000..c470eef2578c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt @@ -0,0 +1,147 @@ +/* + * 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.wm.shell.windowdecor.common.viewhost + +import android.content.Context +import android.content.res.Configuration +import android.view.Display +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.View +import android.view.WindowManager +import android.view.WindowlessWindowManager +import androidx.tracing.Trace +import com.android.internal.annotations.VisibleForTesting +import com.android.wm.shell.shared.annotations.ShellMainThread +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +typealias SurfaceControlViewHostFactory = + (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost + +/** + * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHost]. + * + * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and + * any attempts to do will throw, which means that once a [View] is added using [updateView] or + * [updateViewAsync], only its properties and binding may be changed, its children views may be + * added, removed or changed and its [WindowManager.LayoutParams] may be changed. It also supports + * asynchronously updating the view hierarchy using [updateViewAsync], in which case the update work + * will be posted on the [ShellMainThread] with no delay. + */ +class DefaultWindowDecorViewHost( + private val context: Context, + @ShellMainThread private val mainScope: CoroutineScope, + private val display: Display, + private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s -> + SurfaceControlViewHost(c, d, wwm, s) + }, +) : WindowDecorViewHost { + + private val rootSurface: SurfaceControl = + SurfaceControl.Builder() + .setName("DefaultWindowDecorViewHost surface") + .setContainerLayer() + .setCallsite("DefaultWindowDecorViewHost#init") + .build() + + private var wwm: WindowlessWindowManager? = null + @VisibleForTesting var viewHost: SurfaceControlViewHost? = null + private var currentUpdateJob: Job? = null + + override val surfaceControl: SurfaceControl + get() = rootSurface + + override fun updateView( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + onDrawTransaction: SurfaceControl.Transaction?, + ) { + Trace.beginSection("DefaultWindowDecorViewHost#updateView") + clearCurrentUpdateJob() + updateViewHost(view, attrs, configuration, onDrawTransaction) + Trace.endSection() + } + + override fun updateViewAsync( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + ) { + Trace.beginSection("DefaultWindowDecorViewHost#updateViewAsync") + clearCurrentUpdateJob() + currentUpdateJob = + mainScope.launch { + updateViewHost(view, attrs, configuration, onDrawTransaction = null) + } + Trace.endSection() + } + + override fun release(t: SurfaceControl.Transaction) { + clearCurrentUpdateJob() + viewHost?.release() + t.remove(rootSurface) + } + + private fun updateViewHost( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + onDrawTransaction: SurfaceControl.Transaction?, + ) { + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost") + if (wwm == null) { + wwm = WindowlessWindowManager(configuration, rootSurface, null) + } + requireWindowlessWindowManager().setConfiguration(configuration) + if (viewHost == null) { + viewHost = + surfaceControlViewHostFactory.invoke( + context, + display, + requireWindowlessWindowManager(), + "DefaultWindowDecorViewHost#updateViewHost", + ) + } + onDrawTransaction?.let { requireViewHost().rootSurfaceControl.applyTransactionOnDraw(it) } + if (requireViewHost().view == null) { + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-setView") + requireViewHost().setView(view, attrs) + Trace.endSection() + } else { + check(requireViewHost().view == view) { "Changing view is not allowed" } + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-relayout") + requireViewHost().relayout(attrs) + Trace.endSection() + } + Trace.endSection() + } + + private fun clearCurrentUpdateJob() { + currentUpdateJob?.cancel() + currentUpdateJob = null + } + + private fun requireWindowlessWindowManager(): WindowlessWindowManager { + return wwm ?: error("Expected non-null windowless window manager") + } + + private fun requireViewHost(): SurfaceControlViewHost { + return viewHost ?: error("Expected non-null view host") + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt new file mode 100644 index 000000000000..27ffd6cd8076 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt @@ -0,0 +1,37 @@ +/* + * 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.wm.shell.windowdecor.common.viewhost + +import android.content.Context +import android.view.Display +import android.view.SurfaceControl +import com.android.wm.shell.shared.annotations.ShellMainThread +import kotlinx.coroutines.CoroutineScope + +/** + * A supplier of [DefaultWindowDecorViewHost]s. It creates a new one every time one is requested. + */ +class DefaultWindowDecorViewHostSupplier(@ShellMainThread private val mainScope: CoroutineScope) : + WindowDecorViewHostSupplier<DefaultWindowDecorViewHost> { + + override fun acquire(context: Context, display: Display): DefaultWindowDecorViewHost { + return DefaultWindowDecorViewHost(context, mainScope, display) + } + + override fun release(viewHost: DefaultWindowDecorViewHost, t: SurfaceControl.Transaction) { + viewHost.release(t) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt new file mode 100644 index 000000000000..7c1479e9f9bd --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt @@ -0,0 +1,45 @@ +/* + * 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.wm.shell.windowdecor.common.viewhost + +import android.content.res.Configuration +import android.view.SurfaceControl +import android.view.View +import android.view.WindowManager +import com.android.wm.shell.windowdecor.WindowDecoration + +/** + * An interface for a utility that hosts a [WindowDecoration]'s [View] hierarchy under a + * [SurfaceControl]. + */ +interface WindowDecorViewHost { + /** The surface where the underlying [View] hierarchy is being rendered. */ + val surfaceControl: SurfaceControl + + /** Synchronously update the view hierarchy of this view host. */ + fun updateView( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + onDrawTransaction: SurfaceControl.Transaction?, + ) + + /** Asynchronously update the view hierarchy of this view host. */ + fun updateViewAsync(view: View, attrs: WindowManager.LayoutParams, configuration: Configuration) + + /** Releases the underlying [View] hierarchy and removes the backing [SurfaceControl]. */ + fun release(t: SurfaceControl.Transaction) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt new file mode 100644 index 000000000000..00e29ecaebe3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt @@ -0,0 +1,36 @@ +/* + * 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.wm.shell.windowdecor.common.viewhost + +import android.content.Context +import android.view.Display +import android.view.SurfaceControl + +/** An interface for a supplier of [WindowDecorViewHost]s. */ +interface WindowDecorViewHostSupplier<T : WindowDecorViewHost> { + /** Acquire a [WindowDecorViewHost]. */ + fun acquire(context: Context, display: Display): T + + /** + * Release a [WindowDecorViewHost] when it is no longer used. + * + * @param viewHost the [WindowDecorViewHost] to release + * @param t a transaction that may be used to remove any underlying backing [SurfaceControl] + * that are hosting this [WindowDecorViewHost]. The supplier is not expected to apply the + * transaction. It should be applied by the owner of this supplier. + */ + fun release(viewHost: T, t: SurfaceControl.Transaction) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt index 0e40a5350a43..9db69d5c1bc5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt @@ -33,6 +33,7 @@ import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler import com.android.wm.shell.transition.Transitions @@ -48,7 +49,7 @@ class DesktopTilingDecorViewModel( private val shellTaskOrganizer: ShellTaskOrganizer, private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val returnToDragStartAnimator: ReturnToDragStartAnimator, - private val taskRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val desktopModeEventLogger: DesktopModeEventLogger, ) : DisplayChangeController.OnDisplayChangingListener { @VisibleForTesting @@ -81,7 +82,7 @@ class DesktopTilingDecorViewModel( shellTaskOrganizer, toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, - taskRepository, + desktopUserRepositories, desktopModeEventLogger, ) tilingTransitionHandlerByDisplayId.put(displayId, newHandler) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt index 3b5c6ca2e58a..7ceac52dd2a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt @@ -49,6 +49,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler import com.android.wm.shell.transition.Transitions @@ -72,7 +73,7 @@ class DesktopTilingWindowDecoration( private val shellTaskOrganizer: ShellTaskOrganizer, private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val returnToDragStartAnimator: ReturnToDragStartAnimator, - private val taskRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val desktopModeEventLogger: DesktopModeEventLogger, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }, ) : @@ -630,6 +631,7 @@ class DesktopTilingWindowDecoration( private fun allTiledTasksVisible(): Boolean { val leftTiledTask = leftTaskResizingHelper ?: return false val rightTiledTask = rightTaskResizingHelper ?: return false + val taskRepository = desktopUserRepositories.current return taskRepository.isVisibleTask(leftTiledTask.taskInfo.taskId) && taskRepository.isVisibleTask(rightTiledTask.taskInfo.taskId) } diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt index d9c36cc70790..f6d2cc09d7b0 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt @@ -62,6 +62,14 @@ import android.tools.traces.wm.TransitionType class DesktopModeFlickerScenarios { companion object { + // In DesktopMode, window snap can be done with just a single window. In this case, the + // divider tiling between left and right window won't be shown, and hence its states are not + // obtainable in test. + // As the test should just focus on ensuring window goes to one side of the screen, an + // acceptable approach is to ensure snapped window still fills > 95% of either side of the + // screen. + private const val SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO = 0.05 + val END_DRAG_TO_DESKTOP = FlickerConfigEntry( scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"), @@ -230,9 +238,11 @@ class DesktopModeFlickerScenarios { TaggedCujTransitionMatcher(associatedTransitionRequired = false) ) .build(), - assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + - listOf(AppWindowCoversLeftHalfScreenAtEnd(DESKTOP_MODE_APP)) - .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf( + AppWindowCoversLeftHalfScreenAtEnd( + DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO + ) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) val SNAP_RESIZE_RIGHT_WITH_BUTTON = @@ -245,9 +255,11 @@ class DesktopModeFlickerScenarios { TaggedCujTransitionMatcher(associatedTransitionRequired = false) ) .build(), - assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + - listOf(AppWindowCoversRightHalfScreenAtEnd(DESKTOP_MODE_APP)) - .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf( + AppWindowCoversRightHalfScreenAtEnd( + DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO + ) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) val SNAP_RESIZE_LEFT_WITH_DRAG = diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml index 706c63244890..1de47df78853 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml index 7df1675f541c..34d001c858f6 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml index 7df1675f541c..34d001c858f6 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml index d87c1795cf7b..9c1a8f17aeee 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml index 99969e71238a..02b2cec8dbdb 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml index 19c3e4048d69..a136936c0838 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml @@ -25,7 +25,7 @@ <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> <!-- Turns off Wi-fi --> - <option name="wifi" value="off"/> + <option name="wifi" value="on"/> <!-- Turns off Bluetooth --> <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> @@ -107,4 +109,11 @@ <option name="collect-on-run-ended-only" value="true"/> <option name="clean-up" value="true"/> </metrics_collector> + <!-- Enable mocking GPS location by the test app --> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" + value="appops set com.android.shell android:mock_location allow"/> + <option name="teardown-command" + value="appops set com.android.shell android:mock_location deny"/> + </target_preparer> </configuration> diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml index 7505860709e9..34e4e744dae7 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt index fd4328dee0a1..609a2849f915 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt @@ -27,6 +27,7 @@ import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.subject.exceptions.ExceptionMessageBuilder import android.tools.flicker.subject.exceptions.IncorrectRegionException import android.tools.flicker.subject.layers.LayerSubject +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.EnterPipTransition import org.junit.Assume @@ -65,6 +66,8 @@ import kotlin.math.abs @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { + override val pipApp: PipAppHelper = PipAppHelper(instrumentation) + override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } } override val defaultEnterPip: FlickerBuilder.() -> Unit = { diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt index d4ad4ef8a401..5698023240ab 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt @@ -22,6 +22,7 @@ import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.traces.component.ComponentNameMatcher +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.wm.shell.Flags import org.junit.FixMethodOrder import org.junit.Test @@ -57,6 +58,8 @@ import org.junit.runners.Parameterized @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) : AutoEnterPipOnGoToHomeTest(flicker) { + override val pipApp: PipAppHelper = PipAppHelper(instrumentation) + override val defaultEnterPip: FlickerBuilder.() -> Unit = { setup { pipApp.launchViaIntent(wmHelper) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt index 53725fa046c6..880e4cd4e5f7 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt @@ -21,6 +21,7 @@ import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.ClosePipTransition import org.junit.FixMethodOrder @@ -56,6 +57,8 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) { + override val pipApp: PipAppHelper = PipAppHelper(instrumentation) + override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.closePipWindow(wmHelper) } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt index a1551b7924fe..4399a237bcbb 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt @@ -21,6 +21,7 @@ import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.EnterPipTransition import org.junit.Assume @@ -47,6 +48,7 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { + override val pipApp: PipAppHelper = PipAppHelper(instrumentation) override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } } override val defaultEnterPip: FlickerBuilder.() -> Unit = { diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt index ea5b3e5b08df..49efd1d56256 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt @@ -31,6 +31,7 @@ import android.tools.traces.component.ComponentNameMatcher import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION import com.android.wm.shell.Flags @@ -72,6 +73,7 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) { + override val pipApp: PipAppHelper = PipAppHelper(instrumentation) private val testApp = FixedOrientationAppHelper(instrumentation) private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt index a109c4bba2b3..97cc9d29929c 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt @@ -20,6 +20,7 @@ import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.EnterPipTransition import org.junit.FixMethodOrder @@ -53,6 +54,8 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) open class EnterPipViaAppUiButtonTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { + override val pipApp: PipAppHelper = PipAppHelper(instrumentation) + override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.clickEnterPipButton(wmHelper) } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt index 14ec303206ee..b5b7847e205d 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt @@ -20,6 +20,7 @@ import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition import org.junit.FixMethodOrder @@ -56,6 +57,8 @@ import org.junit.runners.Parameterized @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) { + override val pipApp: PipAppHelper = PipAppHelper(instrumentation) + override val thisTransition: FlickerBuilder.() -> Unit = { setup { // launch an app behind the pip one diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt index 8a34b5e27fdb..f9a9df43a009 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt @@ -20,6 +20,7 @@ import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition import org.junit.FixMethodOrder @@ -54,6 +55,8 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) { + override val pipApp: PipAppHelper = PipAppHelper(instrumentation) + override val thisTransition: FlickerBuilder.() -> Unit = { setup { // launch an app behind the pip one diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt index 12e23285ea68..79e2e4e5a82c 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt @@ -27,6 +27,7 @@ import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.helpers.WindowUtils import android.tools.traces.parsers.toFlickerComponent +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.wm.shell.Flags @@ -68,6 +69,7 @@ import org.junit.runners.Parameterized @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { + override val pipApp: PipAppHelper = PipAppHelper(instrumentation) private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) /** Second app used to enter split screen mode */ private val secondAppForSplitScreen = diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt index 04016a93e53d..14ae93a81c6d 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt @@ -23,6 +23,7 @@ import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder @@ -37,6 +38,8 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class PipAspectRatioChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { + override val pipApp: PipAppHelper = PipAppHelper(instrumentation) + override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.changeAspectRatio(wmHelper) } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt index 6bcaabc3b680..81162c6f53f5 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt @@ -49,7 +49,8 @@ class PipDragTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true") setup { tapl.setEnableRotation(true) - pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras) + pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras) + pipApp.waitForPip(wmHelper) // determine the direction of dragging to test for isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt index d82bfdd6dc2f..6118d73796a1 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt @@ -59,7 +59,8 @@ class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { // Launch the PIP activity and wait for it to enter PiP mode setRotation(Rotation.ROTATION_0) RemoveAllTasksButHomeRule.removeAllTasksButHome() - pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras) + pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras) + pipApp.waitForPip(wmHelper) // get the initial region bounds and cache them val initRegion = pipApp.getWindowRect(wmHelper) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index dbc97d072f9b..61c59cc45504 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt @@ -25,6 +25,7 @@ import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.flicker.subject.exceptions.IncorrectRegionException +import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder @@ -40,6 +41,8 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { + override val pipApp: PipAppHelper = PipAppHelper(instrumentation) + override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt index 65b60ce1022b..0867f654bcaf 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt @@ -18,7 +18,6 @@ package com.android.wm.shell.flicker.pip.apps import android.platform.test.annotations.Postsubmit import android.tools.Rotation -import android.tools.device.apphelpers.StandardAppHelper import android.tools.flicker.junit.FlickerBuilderProvider import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest @@ -29,8 +28,6 @@ import org.junit.Test import org.junit.runners.Parameterized abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { - protected abstract val standardAppHelper: StandardAppHelper - protected abstract val permissions: Array<String> @FlickerBuilderProvider @@ -39,7 +36,7 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran instrumentation.uiAutomation.adoptShellPermissionIdentity() for (permission in permissions) { instrumentation.uiAutomation.grantRuntimePermission( - standardAppHelper.packageName, + pipApp.packageName, permission ) } @@ -48,18 +45,18 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran } } - /** Checks [standardAppHelper] window remains visible throughout the animation */ + /** Checks [pipApp] window remains visible throughout the animation */ @Postsubmit @Test override fun pipAppWindowAlwaysVisible() { - flicker.assertWm { this.isAppWindowVisible(standardAppHelper.packageNameMatcher) } + flicker.assertWm { this.isAppWindowVisible(pipApp.packageNameMatcher) } } - /** Checks [standardAppHelper] layer remains visible throughout the animation */ + /** Checks [pipApp] layer remains visible throughout the animation */ @Postsubmit @Test override fun pipAppLayerAlwaysVisible() { - flicker.assertLayers { this.isVisible(standardAppHelper.packageNameMatcher) } + flicker.assertLayers { this.isVisible(pipApp.packageNameMatcher) } } /** Checks the content overlay appears then disappears during the animation */ @@ -70,39 +67,39 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran } /** - * Checks that [standardAppHelper] window remains inside the display bounds throughout the whole + * Checks that [pipApp] window remains inside the display bounds throughout the whole * animation */ @Postsubmit @Test override fun pipWindowRemainInsideVisibleBounds() { - flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) { + flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) { coversAtMost(displayBounds) } } /** - * Checks that the [standardAppHelper] layer remains inside the display bounds throughout the + * Checks that the [pipApp] layer remains inside the display bounds throughout the * whole animation */ @Postsubmit @Test override fun pipLayerOrOverlayRemainInsideVisibleBounds() { flicker.assertLayersVisibleRegion( - standardAppHelper.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY) + pipApp.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY) ) { coversAtMost(displayBounds) } } - /** Checks that the visible region of [standardAppHelper] always reduces during the animation */ + /** Checks that the visible region of [pipApp] always reduces during the animation */ @Postsubmit @Test override fun pipLayerReduces() { flicker.assertLayers { val pipLayerList = this.layers { - standardAppHelper.packageNameMatcher.layerMatchesAnyOf(it) && it.isVisible + pipApp.packageNameMatcher.layerMatchesAnyOf(it) && it.isVisible } pipLayerList.zipWithNext { previous, current -> current.visibleRegion.notBiggerThan(previous.visibleRegion.region) @@ -110,14 +107,14 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran } } - /** Checks that [standardAppHelper] window becomes pinned */ + /** Checks that [pipApp] window becomes pinned */ @Postsubmit @Test override fun pipWindowBecomesPinned() { flicker.assertWm { - invoke("pipWindowIsNotPinned") { it.isNotPinned(standardAppHelper.packageNameMatcher) } + invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp.packageNameMatcher) } .then() - .invoke("pipWindowIsPinned") { it.isPinned(standardAppHelper.packageNameMatcher) } + .invoke("pipWindowIsPinned") { it.isPinned(pipApp.packageNameMatcher) } } } @@ -129,14 +126,14 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran } /** - * Checks that the focus changes between the [standardAppHelper] window and the launcher when + * Checks that the focus changes between the [pipApp] window and the launcher when * closing the pip window */ @Postsubmit @Test override fun focusChanges() { flicker.assertEventLog { - this.focusChanges(standardAppHelper.packageName, "NexusLauncherActivity") + this.focusChanges(pipApp.packageName, "NexusLauncherActivity") } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt index 7b04b766a191..651c92308c04 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt @@ -29,6 +29,8 @@ import android.tools.device.apphelpers.MapsAppHelper import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +import android.tools.traces.component.ComponentRegexMatcher import androidx.test.filters.RequiresDevice import org.junit.Assume import org.junit.FixMethodOrder @@ -63,7 +65,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) { - override val standardAppHelper: MapsAppHelper = MapsAppHelper(instrumentation) + override val pipApp: MapsAppHelper = MapsAppHelper(instrumentation) override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS, Manifest.permission.ACCESS_FINE_LOCATION) @@ -110,23 +112,23 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition // normal app open through the Launcher All Apps // var mapsAddressOption = "Golden Gate Bridge" - // standardAppHelper.open() - // standardAppHelper.doSearch(mapsAddressOption) - // standardAppHelper.getDirections() - // standardAppHelper.startNavigation(); + // pipApp.open() + // pipApp.doSearch(mapsAddressOption) + // pipApp.getDirections() + // pipApp.startNavigation(); - standardAppHelper.launchViaIntent( + pipApp.launchViaIntent( wmHelper, MapsAppHelper.getMapIntent(MapsAppHelper.INTENT_NAVIGATION) ) - standardAppHelper.waitForNavigationToStart() + pipApp.waitForNavigationToStart() } } override val defaultTeardown: FlickerBuilder.() -> Unit = { teardown { - standardAppHelper.exit(wmHelper) + pipApp.exit(wmHelper) mainHandler.removeCallbacks(updateLocation) // the main looper callback might have tried to provide a new location after the // provider is no longer in test mode, causing a crash, this prevents it from happening @@ -137,14 +139,14 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } } - /** Checks [standardAppHelper] layer remains visible throughout the animation */ + /** Checks [pipApp] layer remains visible throughout the animation */ @Postsubmit @Test override fun pipAppLayerAlwaysVisible() { // For Maps the transition goes through the UI mode change that adds a snapshot overlay so // we assert only start/end layers matching the app instead. - flicker.assertLayersStart { this.isVisible(standardAppHelper.packageNameMatcher) } - flicker.assertLayersEnd { this.isVisible(standardAppHelper.packageNameMatcher) } + flicker.assertLayersStart { this.isVisible(pipApp.packageNameMatcher) } + flicker.assertLayersEnd { this.isVisible(pipApp.packageNameMatcher) } } @Postsubmit @@ -154,4 +156,15 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition Assume.assumeFalse(flicker.scenario.isGesturalNavigation) super.focusChanges() } + + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + ignoreLayers = VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + + ComponentRegexMatcher(Regex("Background for .* SurfaceView\\[com\\.google\\.android\\.apps\\.maps/com\\.google\\.android\\.maps\\.MapsActivity\\]\\#\\d+")) + ) + } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt index 691194609343..be4cd780e45e 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt @@ -61,7 +61,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) { - override val standardAppHelper: NetflixAppHelper = NetflixAppHelper(instrumentation) + override val pipApp: NetflixAppHelper = NetflixAppHelper(instrumentation) private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) @@ -69,17 +69,17 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit override val defaultEnterPip: FlickerBuilder.() -> Unit = { setup { - standardAppHelper.launchViaIntent( + pipApp.launchViaIntent( wmHelper, NetflixAppHelper.getNetflixWatchVideoIntent("81605060"), ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME, NetflixAppHelper.WATCH_ACTIVITY) ) - standardAppHelper.waitForVideoPlaying() + pipApp.waitForVideoPlaying() } } override val defaultTeardown: FlickerBuilder.() -> Unit = { - teardown { standardAppHelper.exit(wmHelper) } + teardown { pipApp.exit(wmHelper) } } override val thisTransition: FlickerBuilder.() -> Unit = { @@ -143,7 +143,7 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit // might go outside of bounds as we resize from landscape fullscreen to destination bounds, // and once the animation is over we assert that it's fully within the display bounds, at // which point the device also performs orientation change from landscape to portrait - flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) { + flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) { regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds) } } @@ -156,7 +156,7 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit // and once the animation is over we assert that it's fully within the display bounds, at // which point the device also performs orientation change from landscape to portrait // since Netflix uses source rect hint, there is no PiP overlay present - flicker.assertLayersVisibleRegion(standardAppHelper.packageNameMatcher) { + flicker.assertLayersVisibleRegion(pipApp.packageNameMatcher) { regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds) } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt index 5e54f30dae8a..3e4ff3075f73 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt @@ -57,23 +57,23 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) { - override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation) + override val pipApp: YouTubeAppHelper = YouTubeAppHelper(instrumentation) override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS) override val defaultEnterPip: FlickerBuilder.() -> Unit = { setup { - standardAppHelper.launchViaIntent( + pipApp.launchViaIntent( wmHelper, YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"), ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "") ) - standardAppHelper.waitForVideoPlaying() + pipApp.waitForVideoPlaying() } } override val defaultTeardown: FlickerBuilder.() -> Unit = { - teardown { standardAppHelper.exit(wmHelper) } + teardown { pipApp.exit(wmHelper) } } override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt index 159cba4a559e..2c6cb503465c 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt @@ -63,7 +63,7 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) : YouTubeEnterPipTest(flicker) { - override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation) + override val pipApp: YouTubeAppHelper = YouTubeAppHelper(instrumentation) private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) @@ -71,13 +71,13 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) : override val defaultEnterPip: FlickerBuilder.() -> Unit = { setup { - standardAppHelper.launchViaIntent( + pipApp.launchViaIntent( wmHelper, YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"), ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "") ) - standardAppHelper.enterFullscreen() - standardAppHelper.waitForVideoPlaying() + pipApp.enterFullscreen() + pipApp.waitForVideoPlaying() } } @@ -101,7 +101,7 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) : // might go outside of bounds as we resize from landscape fullscreen to destination bounds, // and once the animation is over we assert that it's fully within the display bounds, at // which point the device also performs orientation change from landscape to portrait - flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) { + flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) { regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds) } } @@ -114,7 +114,7 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) : // and once the animation is over we assert that it's fully within the display bounds, at // which point the device also performs orientation change from landscape to portrait // since YouTube uses source rect hint, there is no PiP overlay present - flicker.assertLayersVisibleRegion(standardAppHelper.packageNameMatcher) { + flicker.assertLayersVisibleRegion(pipApp.packageNameMatcher) { regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds) } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt index 6dd3a175da65..a72de0f6daf4 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt @@ -71,7 +71,9 @@ abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(fl @Presubmit @Test open fun pipLayerOrOverlayRemainInsideVisibleBounds() { - flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)) { + flicker.assertLayersVisibleRegion( + pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY) + ) { coversAtMost(displayBounds) } } @@ -117,7 +119,9 @@ abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(fl @Presubmit @Test open fun focusChanges() { - flicker.assertEventLog { this.focusChanges(pipApp.packageName, "NexusLauncherActivity") } + flicker.assertEventLog { + this.focusChanges(pipApp.packageName, "NexusLauncherActivity") + } } companion object { diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt index c37bf3579e93..7b6625ddc429 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt @@ -27,6 +27,7 @@ import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import android.tools.helpers.WindowUtils import android.tools.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.PipApp import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.testapp.ActivityOptions @@ -40,7 +41,6 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) { @Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() - protected val pipApp = PipAppHelper(instrumentation) protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation) @@ -63,6 +63,11 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) { } } + /** + * Defines the test app to run PIP flicker test. + */ + protected open val pipApp: PipApp = PipAppHelper(instrumentation) + /** Defines the transition used to run the test */ protected open val thisTransition: FlickerBuilder.() -> Unit = {} @@ -85,10 +90,11 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) { /** Defines the default method of entering PiP */ protected open val defaultEnterPip: FlickerBuilder.() -> Unit = { setup { - pipApp.launchViaIntentAndWaitForPip( + pipApp.launchViaIntent( wmHelper, stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true") ) + pipApp.waitForPip(wmHelper) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt index c4954f90179c..feb3edc9dab7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt @@ -20,6 +20,7 @@ import android.app.Instrumentation import android.graphics.Point import android.os.SystemClock import android.tools.Rotation +import android.tools.device.apphelpers.IStandardAppHelper import android.tools.device.apphelpers.StandardAppHelper import android.tools.flicker.rules.ChangeDisplayOrientationRule import android.tools.traces.component.ComponentNameMatcher @@ -102,8 +103,8 @@ object SplitScreenUtils { wmHelper: WindowManagerStateHelper, tapl: LauncherInstrumentation, device: UiDevice, - primaryApp: StandardAppHelper, - secondaryApp: StandardAppHelper, + primaryApp: IStandardAppHelper, + secondaryApp: IStandardAppHelper, rotation: Rotation ) { primaryApp.launchViaIntent(wmHelper) @@ -117,8 +118,8 @@ object SplitScreenUtils { fun enterSplitViaIntent( wmHelper: WindowManagerStateHelper, - primaryApp: StandardAppHelper, - secondaryApp: StandardAppHelper + primaryApp: IStandardAppHelper, + secondaryApp: IStandardAppHelper ) { val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true") primaryApp.launchViaIntent(wmHelper, null, null, stringExtras) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java index 0373bbd43043..6f3a3ec4fd20 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -172,10 +172,10 @@ public class DisplayImeControllerTest extends ShellTestCase { var mockPp = mock(DisplayImeController.ImePositionProcessor.class); mDisplayImeController.addPositionProcessor(mockPp); - mPerDisplay.setImeInputTargetRequestedVisibility(true); + mPerDisplay.setImeInputTargetRequestedVisibility(true, null /* statsToken */); verify(mockPp).onImeRequested(anyInt(), eq(true)); - mPerDisplay.setImeInputTargetRequestedVisibility(false); + mPerDisplay.setImeInputTargetRequestedVisibility(false, null /* statsToken */); verify(mockPp).onImeRequested(anyInt(), eq(false)); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt index 7b1d27a8b823..64772d037383 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt @@ -18,7 +18,6 @@ package com.android.wm.shell.common.transition import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.server.testutils.any import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener @@ -35,6 +34,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mockito.mock import org.mockito.Mockito.verify +import org.mockito.kotlin.any import org.mockito.kotlin.never /** diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index d5287e742c2c..67573dabf2ec 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.ActivityManager.RunningTaskInfo; import android.app.TaskInfo; @@ -61,12 +62,16 @@ import com.android.wm.shell.common.DockStateReader; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.api.CompatUIInfo; +import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import dagger.Lazy; +import java.util.Optional; + import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -130,6 +135,10 @@ public class CompatUIControllerTest extends ShellTestCase { private CompatUIShellCommandHandler mCompatUIShellCommandHandler; @Mock private AccessibilityManager mAccessibilityManager; + @Mock + private DesktopUserRepositories mDesktopUserRepositories; + @Mock + private DesktopRepository mDesktopRepository; @Captor ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor; @@ -137,7 +146,6 @@ public class CompatUIControllerTest extends ShellTestCase { @NonNull private CompatUIStatusManager mCompatUIStatusManager; - private boolean mInDesktopModePredicateResult; @Before public void setUp() { @@ -152,6 +160,8 @@ public class CompatUIControllerTest extends ShellTestCase { doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId(); doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean()); doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean()); + doReturn(mDesktopRepository).when(mDesktopUserRepositories).getCurrent(); + doReturn(mDesktopRepository).when(mDesktopUserRepositories).getProfile(anyInt()); doReturn(DISPLAY_ID).when(mMockRestartDialogLayout).getDisplayId(); doReturn(TASK_ID).when(mMockRestartDialogLayout).getTaskId(); @@ -164,7 +174,7 @@ public class CompatUIControllerTest extends ShellTestCase { mMockDisplayController, mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader, mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager, - mCompatUIStatusManager, i -> mInDesktopModePredicateResult) { + mCompatUIStatusManager, Optional.of(mDesktopUserRepositories)) { @Override CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { @@ -707,13 +717,17 @@ public class CompatUIControllerTest extends ShellTestCase { @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) @EnableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE) public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagEnabled() { - mInDesktopModePredicateResult = false; TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true); + when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(0); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + verify(mController, never()).removeLayouts(taskInfo.taskId); - mInDesktopModePredicateResult = true; + when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(2); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + verify(mController).removeLayouts(taskInfo.taskId); } @@ -721,13 +735,17 @@ public class CompatUIControllerTest extends ShellTestCase { @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) @DisableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE) public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagDisabled() { - mInDesktopModePredicateResult = false; + when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(0); TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + verify(mController, never()).removeLayouts(taskInfo.taskId); - mInDesktopModePredicateResult = true; + when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(2); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + verify(mController, never()).removeLayouts(taskInfo.taskId); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt index 9c6afcb8be63..07bfefe0b275 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt @@ -25,9 +25,12 @@ import android.testing.AndroidTestingRunner import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CLOSE import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn import com.android.window.flags.Flags import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.common.transition.TransitionStateHolder +import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.TransitionObserverInputBuilder @@ -37,6 +40,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -154,21 +158,38 @@ class LetterboxTransitionObserverTest : ShellTestCase() { } @Test - fun `When closing change letterbox surface destroy is triggered`() { + fun `When closing change with no recents running letterbox surfaces are destroyed`() { runTestScenario { r -> executeTransitionObserverTest(observerFactory = r.observerFactory) { r.invokeShellInit() inputBuilder { buildTransitionInfo() + r.configureRecentsState(running = false) r.createClosingChange(inputBuilder = this) } validateOutput { r.destroyEventDetected(expected = true) - r.creationEventDetected(expected = false) - r.visibilityEventDetected(expected = false, visible = false) - r.updateSurfaceBoundsEventDetected(expected = false) + } + } + } + } + + @Test + fun `When closing change and recents are running letterbox surfaces are not destroyed`() { + runTestScenario { r -> + executeTransitionObserverTest(observerFactory = r.observerFactory) { + r.invokeShellInit() + + inputBuilder { + buildTransitionInfo() + r.createClosingChange(inputBuilder = this) + r.configureRecentsState(running = true) + } + + validateOutput { + r.destroyEventDetected(expected = false) } } } @@ -197,6 +218,7 @@ class LetterboxTransitionObserverTest : ShellTestCase() { private val transitions: Transitions private val letterboxController: LetterboxController private val letterboxObserver: LetterboxTransitionObserver + private val transitionStateHolder: TransitionStateHolder val observerFactory: () -> LetterboxTransitionObserver @@ -205,8 +227,16 @@ class LetterboxTransitionObserverTest : ShellTestCase() { shellInit = ShellInit(executor) transitions = mock<Transitions>() letterboxController = mock<LetterboxController>() + transitionStateHolder = + TransitionStateHolder(shellInit, mock<RecentsTransitionHandler>()) + spyOn(transitionStateHolder) letterboxObserver = - LetterboxTransitionObserver(shellInit, transitions, letterboxController) + LetterboxTransitionObserver( + shellInit, + transitions, + letterboxController, + transitionStateHolder + ) observerFactory = { letterboxObserver } } @@ -218,6 +248,10 @@ class LetterboxTransitionObserverTest : ShellTestCase() { verify(transitions, expected.asMode()).registerObserver(observer()) } + fun configureRecentsState(running: Boolean) { + doReturn(running).`when`(transitionStateHolder).isRecentsTransitionRunning() + } + fun creationEventDetected( expected: Boolean, displayId: Int = DISPLAY_ID, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt index 2ea0379e3bf7..41a594a3347a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt @@ -23,6 +23,7 @@ import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT import android.graphics.Rect import android.os.Binder +import android.os.UserManager import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner @@ -96,11 +97,12 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { @Mock lateinit var taskStackListener: TaskStackListenerImpl @Mock lateinit var persistentRepository: DesktopPersistentRepository @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer + @Mock lateinit var userManager: UserManager private lateinit var mockitoSession: StaticMockitoSession private lateinit var handler: DesktopActivityOrientationChangeHandler private lateinit var shellInit: ShellInit - private lateinit var taskRepository: DesktopRepository + private lateinit var userRepositories: DesktopUserRepositories private lateinit var testScope: CoroutineScope // Mock running tasks are registered here so we can get the list from mock shell task organizer. private val runningTasks = mutableListOf<RunningTaskInfo>() @@ -117,13 +119,14 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) shellInit = spy(ShellInit(testExecutor)) - taskRepository = - DesktopRepository( + userRepositories = + DesktopUserRepositories( context, shellInit, persistentRepository, repositoryInitializer, - testScope + testScope, + userManager ) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } @@ -132,7 +135,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { ) handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer, - taskStackListener, resizeTransitionHandler, taskRepository) + taskStackListener, resizeTransitionHandler, userRepositories) shellInit.init() } @@ -156,7 +159,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { clearInvocations(shellInit) handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer, - taskStackListener, resizeTransitionHandler, taskRepository) + taskStackListener, resizeTransitionHandler, userRepositories) verify(shellInit, never()).addInitCallback(any(), any<DesktopActivityOrientationChangeHandler>()) @@ -180,7 +183,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { activityInfo.screenOrientation = SCREEN_ORIENTATION_PORTRAIT task.topActivityInfo = activityInfo whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) - taskRepository.addTask(DEFAULT_DISPLAY, task.taskId, isVisible = true) + userRepositories.current.addTask(DEFAULT_DISPLAY, task.taskId, isVisible = true) runningTasks.add(task) taskStackListener.onActivityRequestedOrientationChanged(task.taskId, @@ -203,7 +206,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { @Test fun handleActivityOrientationChange_notInDesktopMode_doNothing() { val task = setUpFreeformTask(isResizeable = false) - taskRepository.updateTask(task.displayId, task.taskId, isVisible = false) + userRepositories.current.updateTask(task.displayId, task.taskId, isVisible = false) taskStackListener.onActivityRequestedOrientationChanged(task.taskId, SCREEN_ORIENTATION_LANDSCAPE) @@ -268,7 +271,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { task.topActivityInfo = activityInfo task.isResizeable = isResizeable whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) - taskRepository.addTask(displayId, task.taskId, isVisible = true) + userRepositories.current.addTask(displayId, task.taskId, isVisible = true) runningTasks.add(task) return task } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt index b57c55c4c45a..db4c7465ae48 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt @@ -76,18 +76,19 @@ class DesktopImmersiveControllerTest : ShellTestCase() { @JvmField @Rule val animatorTestRule = AnimatorTestRule(this) @Mock private lateinit var mockTransitions: Transitions - private lateinit var desktopRepository: DesktopRepository + private lateinit var userRepositories: DesktopUserRepositories @Mock private lateinit var mockDisplayController: DisplayController @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer @Mock private lateinit var mockDisplayLayout: DisplayLayout private val transactionSupplier = { StubTransaction() } private lateinit var controller: DesktopImmersiveController + private lateinit var desktopRepository: DesktopRepository @Before fun setUp() { - desktopRepository = DesktopRepository( - context, ShellInit(TestShellExecutor()), mock(), mock(), mock() + userRepositories = DesktopUserRepositories( + context, ShellInit(TestShellExecutor()), mock(), mock(), mock(), mock() ) whenever(mockDisplayController.getDisplayLayout(DEFAULT_DISPLAY)) .thenReturn(mockDisplayLayout) @@ -97,12 +98,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller = DesktopImmersiveController( shellInit = mock(), transitions = mockTransitions, - desktopRepository = desktopRepository, + desktopUserRepositories = userRepositories, displayController = mockDisplayController, shellTaskOrganizer = mockShellTaskOrganizer, shellCommandHandler = mock(), transactionSupplier = transactionSupplier, ) + desktopRepository = userRepositories.current } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt index 62717a32d99f..2d5544527436 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt @@ -60,6 +60,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.kotlin.any @@ -84,7 +85,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Mock lateinit var transitions: Transitions @Mock - lateinit var desktopRepository: DesktopRepository + lateinit var userRepositories: DesktopUserRepositories @Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler @Mock @@ -103,16 +104,21 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { lateinit var shellInit: ShellInit @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + @Mock + private lateinit var desktopRepository: DesktopRepository private lateinit var mixedHandler: DesktopMixedTransitionHandler + @Before fun setUp() { + whenever(userRepositories.current).thenReturn(desktopRepository) + whenever(userRepositories.getProfile(Mockito.anyInt())).thenReturn(desktopRepository) mixedHandler = DesktopMixedTransitionHandler( context, transitions, - desktopRepository, + userRepositories, freeformTaskTransitionHandler, closeDesktopTaskTransitionHandler, desktopImmersiveController, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt index 605069533ad9..dc7fb5f36952 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt @@ -136,8 +136,13 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler( context, - Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController), - inputManager, shellTaskOrganizer, focusTransitionObserver, testExecutor + Optional.of(desktopModeWindowDecorViewModel), + Optional.of(desktopTasksController), + inputManager, + shellTaskOrganizer, + focusTransitionObserver, + testExecutor, + displayController ) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index 7f790d574a7e..9059d7d5342c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -30,7 +30,6 @@ import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.persistence.Desktop import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository -import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.sysui.ShellInit import com.google.common.truth.Truth.assertThat import junit.framework.Assert.fail @@ -70,7 +69,6 @@ class DesktopRepositoryTest : ShellTestCase() { @Mock private lateinit var testExecutor: ShellExecutor @Mock private lateinit var persistentRepository: DesktopPersistentRepository - @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer @Before fun setUp() { @@ -80,11 +78,9 @@ class DesktopRepositoryTest : ShellTestCase() { repo = DesktopRepository( - context, - shellInit, persistentRepository, - repositoryInitializer, - datastoreScope + datastoreScope, + DEFAULT_USER_ID ) whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn( Desktop.getDefaultInstance() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt index 8e323acc4e66..b4daa6637f83 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt @@ -22,6 +22,7 @@ import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask @@ -29,6 +30,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -47,131 +49,163 @@ class DesktopTaskChangeListenerTest : ShellTestCase() { private lateinit var desktopTaskChangeListener: DesktopTaskChangeListener + private val desktopUserRepositories = mock<DesktopUserRepositories>() private val desktopRepository = mock<DesktopRepository>() @Before fun setUp() { - desktopTaskChangeListener = DesktopTaskChangeListener(desktopRepository) + desktopTaskChangeListener = DesktopTaskChangeListener(desktopUserRepositories) + + whenever(desktopUserRepositories.current).thenReturn(desktopRepository) + whenever(desktopUserRepositories.getProfile(anyInt())).thenReturn(desktopRepository) } @Test fun onTaskOpening_fullscreenTask_notActiveDesktopTask_noop() { val task = createFullscreenTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(false) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(false) desktopTaskChangeListener.onTaskOpening(task) - verify(desktopRepository, never()).addTask(task.displayId, task.taskId, task.isVisible) - verify(desktopRepository, never()).removeFreeformTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current, never()) + .addTask(task.displayId, task.taskId, task.isVisible) + verify(desktopUserRepositories.current, never()) + .removeFreeformTask(task.displayId, task.taskId) } @Test fun onTaskOpening_freeformTask_activeDesktopTask_removesTaskFromRepo() { val task = createFullscreenTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskOpening(task) - verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId) } @Test fun onTaskOpening_freeformTask_visibleDesktopTask_addsTaskToRepository() { val task = createFreeformTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(false) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(false) desktopTaskChangeListener.onTaskOpening(task) - verify(desktopRepository).addTask(task.displayId, task.taskId, task.isVisible) + verify(desktopUserRepositories.current) + .addTask(task.displayId, task.taskId, task.isVisible) } @Test fun onTaskOpening_freeformTask_nonVisibleDesktopTask_addsTaskToRepository() { val task = createFreeformTask().apply { isVisible = false } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskOpening(task) - verify(desktopRepository).addTask(task.displayId, task.taskId, task.isVisible) + verify(desktopUserRepositories.current) + .addTask(task.displayId, task.taskId, task.isVisible) } @Test fun onTaskChanging_freeformTaskOutsideDesktop_removesTaskFromRepo() { val task = createFullscreenTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskChanging(task) - verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current) + .removeFreeformTask(task.displayId, task.taskId) } @Test fun onTaskChanging_visibleTaskInDesktop_updatesTaskVisibility() { val task = createFreeformTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskChanging(task) - verify(desktopRepository).updateTask(task.displayId, task.taskId, task.isVisible) + verify(desktopUserRepositories.current) + .updateTask(task.displayId, task.taskId, task.isVisible) } @Test fun onTaskChanging_nonVisibleTask_updatesTaskVisibility() { val task = createFreeformTask().apply { isVisible = false } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskChanging(task) - verify(desktopRepository).updateTask(task.displayId, task.taskId, task.isVisible) + verify(desktopUserRepositories.current) + .updateTask(task.displayId, task.taskId, task.isVisible) } @Test fun onTaskMovingToFront_freeformTaskOutsideDesktop_removesTaskFromRepo() { val task = createFullscreenTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskMovingToFront(task) - verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current) + .removeFreeformTask(task.displayId, task.taskId) } @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun onTaskClosing_backNavEnabled_nonClosingTask_minimizesTaskInRepo() { val task = createFreeformTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) - whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(false) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) + whenever(desktopUserRepositories.current.isClosingTask(task.taskId)) + .thenReturn(false) desktopTaskChangeListener.onTaskClosing(task) - verify(desktopRepository).updateTask(task.displayId, task.taskId, isVisible = false) - verify(desktopRepository).minimizeTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current) + .updateTask(task.displayId, task.taskId, isVisible = false) + verify(desktopUserRepositories.current) + .minimizeTask(task.displayId, task.taskId) } @Test @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun onTaskClosing_backNavDisabled_closingTask_removesTaskInRepo() { val task = createFreeformTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) - whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) + whenever(desktopUserRepositories.current.isClosingTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskClosing(task) - verify(desktopRepository, never()).minimizeTask(task.displayId, task.taskId) - verify(desktopRepository).removeClosingTask(task.taskId) - verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current, never()) + .minimizeTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current) + .removeClosingTask(task.taskId) + verify(desktopUserRepositories.current) + .removeFreeformTask(task.displayId, task.taskId) } @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun onTaskClosing_backNavEnabled_closingTask_removesTaskFromRepo() { val task = createFreeformTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) - whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) + whenever(desktopUserRepositories.current.isClosingTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskClosing(task) - verify(desktopRepository).removeClosingTask(task.taskId) - verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current).removeClosingTask(task.taskId) + verify(desktopUserRepositories.current) + .removeFreeformTask(task.displayId, task.taskId) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 5c0027220ec9..c10434aa6d6f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -44,6 +44,7 @@ import android.os.Binder import android.os.Bundle import android.os.Handler import android.os.IBinder +import android.os.UserManager import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule @@ -93,6 +94,7 @@ import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.MultiInstanceHelper import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger @@ -233,9 +235,11 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock private lateinit var resources: Resources @Mock lateinit var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener + @Mock private lateinit var userManager: UserManager private lateinit var controller: DesktopTasksController private lateinit var shellInit: ShellInit private lateinit var taskRepository: DesktopRepository + private lateinit var userRepositories: DesktopUserRepositories private lateinit var desktopTasksLimiter: DesktopTasksLimiter private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener private lateinit var testScope: CoroutineScope @@ -267,12 +271,18 @@ class DesktopTasksControllerTest : ShellTestCase() { testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) shellInit = spy(ShellInit(testExecutor)) - taskRepository = - DesktopRepository(context, shellInit, persistentRepository, repositoryInitializer, testScope) + userRepositories = + DesktopUserRepositories( + context, + shellInit, + persistentRepository, + repositoryInitializer, + testScope, + userManager) desktopTasksLimiter = DesktopTasksLimiter( transitions, - taskRepository, + userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT, mockInteractionJankMonitor, @@ -315,6 +325,8 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener assumeTrue(ENABLE_SHELL_TRANSITIONS) + + taskRepository = userRepositories.current } private fun createController(): DesktopTasksController { @@ -338,7 +350,7 @@ class DesktopTasksControllerTest : ShellTestCase() { toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, mMockDesktopImmersiveController, - taskRepository, + userRepositories, recentsTransitionHandler, multiInstanceHelper, shellExecutor, @@ -377,7 +389,14 @@ class DesktopTasksControllerTest : ShellTestCase() { val task1 = setUpFreeformTask() val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java) - controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task1, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH + ) + ) verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture()) verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( @@ -399,21 +418,29 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFullScreenTask_returnFalse() { + fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfMaximizedTask_returnFalse() { val stableBounds = Rect().apply { displayLayout.getStableBounds(this) } val task1 = setUpFreeformTask(bounds = stableBounds, active = true) val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java) - controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task1, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.RESTORE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, + InputMethod.TOUCH + ) + ) verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture()) verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - ResizeTrigger.MAXIMIZE_BUTTON, - InputMethod.TOUCH, - task1, - 0, - 0, - displayController + eq(ResizeTrigger.MAXIMIZE_BUTTON), + eq(InputMethod.TOUCH), + eq(task1), + anyOrNull(), + anyOrNull(), + eq(displayController), + anyOrNull() ) assertThat(argumentCaptor.value).isFalse() } @@ -3359,7 +3386,14 @@ class DesktopTasksControllerTest : ShellTestCase() { val bounds = Rect(0, 0, 100, 100) val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds) - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH + ) + ) // Assert bounds set to stable bounds val wct = getLatestToggleResizeDesktopTaskWct() @@ -3584,7 +3618,14 @@ class DesktopTasksControllerTest : ShellTestCase() { // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750) - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH + ) + ) // Assert bounds set to stable bounds val wct = getLatestToggleResizeDesktopTaskWct() @@ -3604,7 +3645,15 @@ class DesktopTasksControllerTest : ShellTestCase() { val bounds = Rect(0, 0, 100, 100) val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds) - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH + ) + ) + assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isEqualTo(bounds) verify(desktopModeEventLogger, never()).logTaskResizingEnded( any(), any(), any(), any(), @@ -3618,11 +3667,25 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize) // Maximize - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH + ) + ) task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS) // Restore - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.RESTORE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, + InputMethod.TOUCH + ) + ) // Assert bounds set to last bounds before maximize val wct = getLatestToggleResizeDesktopTaskWct() @@ -3645,12 +3708,26 @@ class DesktopTasksControllerTest : ShellTestCase() { } // Maximize - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH + ) + ) task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left, boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom) // Restore - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.RESTORE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, + InputMethod.TOUCH + ) + ) // Assert bounds set to last bounds before maximize val wct = getLatestToggleResizeDesktopTaskWct() @@ -3673,12 +3750,26 @@ class DesktopTasksControllerTest : ShellTestCase() { } // Maximize - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH + ) + ) task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left, STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom) // Restore - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.RESTORE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, + InputMethod.TOUCH + ) + ) // Assert bounds set to last bounds before maximize val wct = getLatestToggleResizeDesktopTaskWct() @@ -3699,11 +3790,25 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize) // Maximize - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH + ) + ) task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS) // Restore - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.RESTORE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, + InputMethod.TOUCH + ) + ) // Assert last bounds before maximize removed after use assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 797b12505ef2..0712d58166bb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -20,6 +20,7 @@ import android.app.ActivityManager.RunningTaskInfo import android.graphics.Rect import android.os.Binder import android.os.Handler +import android.os.UserManager import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule @@ -73,7 +74,6 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.verify import org.mockito.quality.Strictness - /** * Test class for {@link DesktopTasksLimiter} * @@ -95,9 +95,11 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Mock lateinit var testExecutor: ShellExecutor @Mock lateinit var persistentRepository: DesktopPersistentRepository @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer + @Mock lateinit var userManager: UserManager private lateinit var mockitoSession: StaticMockitoSession private lateinit var desktopTasksLimiter: DesktopTasksLimiter + private lateinit var userRepositories: DesktopUserRepositories private lateinit var desktopTaskRepo: DesktopRepository private lateinit var shellInit: ShellInit private lateinit var testScope: CoroutineScope @@ -111,16 +113,18 @@ class DesktopTasksLimiterTest : ShellTestCase() { Dispatchers.setMain(StandardTestDispatcher()) testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) - desktopTaskRepo = - DesktopRepository( + userRepositories = + DesktopUserRepositories( context, shellInit, persistentRepository, repositoryInitializer, - testScope + testScope, + userManager ) + desktopTaskRepo = userRepositories.current desktopTasksLimiter = - DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT, + DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT, interactionJankMonitor, mContext, handler) } @@ -133,7 +137,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() { assertFailsWith<IllegalArgumentException> { - DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, 0, + DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, 0, interactionJankMonitor, mContext, handler) } } @@ -141,7 +145,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() { assertFailsWith<IllegalArgumentException> { - DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, -5, + DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, -5, interactionJankMonitor, mContext, handler) } } @@ -411,7 +415,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getTaskToMinimize_tasksAboveLimit_otherLimit_returnsBackTask() { desktopTasksLimiter = - DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT2, + DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT2, interactionJankMonitor, mContext, handler) val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt index 7f1c1db3207a..238483da537c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -50,6 +50,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.ArgumentMatchers.isA import org.mockito.Mockito @@ -75,6 +76,7 @@ class DesktopTasksTransitionObserverTest { private val transitions = mock<Transitions>() private val context = mock<Context>() private val shellTaskOrganizer = mock<ShellTaskOrganizer>() + private val userRepositories = mock<DesktopUserRepositories>() private val taskRepository = mock<DesktopRepository>() private val mixedHandler = mock<DesktopMixedTransitionHandler>() @@ -86,9 +88,12 @@ class DesktopTasksTransitionObserverTest { whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) shellInit = spy(ShellInit(testExecutor)) + whenever(userRepositories.current).thenReturn(taskRepository) + whenever(userRepositories.getProfile(anyInt())).thenReturn(taskRepository) + transitionObserver = DesktopTasksTransitionObserver( - context, taskRepository, transitions, shellTaskOrganizer, mixedHandler, shellInit + context, userRepositories, transitions, shellTaskOrganizer, mixedHandler, shellInit ) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt index 226e974d2875..b9d7bbf567b7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt @@ -25,10 +25,11 @@ import android.view.WindowManager.TRANSIT_OPEN import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTaskBuilder import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSystemModalTask +import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions @@ -49,6 +50,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { private val animExecutor = mock<ShellExecutor>() private val shellInit = mock<ShellInit>() private val transitions = mock<Transitions>() + private val desktopUserRepositories = mock<DesktopUserRepositories>() private val desktopRepository = mock<DesktopRepository>() private val startT = mock<SurfaceControl.Transaction>() private val finishT = mock<SurfaceControl.Transaction>() @@ -58,6 +60,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { @Before fun setUp() { // Simulate having one Desktop task so that we see Desktop Mode as active + whenever(desktopUserRepositories.current).thenReturn(desktopRepository) whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1) transitionHandler = createTransitionHandler() } @@ -69,7 +72,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { animExecutor, shellInit, transitions, - desktopRepository, + desktopUserRepositories, ) @Test @@ -79,7 +82,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { @Test fun startAnimation_desktopNotActive_doesNotAnimate() { - whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1) + whenever(desktopUserRepositories.current.getVisibleTaskCount(anyInt())).thenReturn(1) val info = TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN, createSystemModalTask()) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt index 8495580f42a5..4f7e80cf8330 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt @@ -112,7 +112,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { datastoreRepository.addOrUpdateDesktop( visibleTasks = visibleTasks, minimizedTasks = minimizedTasks, - freeformTasksInZOrder = freeformTasksInZOrder) + freeformTasksInZOrder = freeformTasksInZOrder, + userId = DEFAULT_USER_ID) val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) assertThat(actualDesktop?.tasksByTaskIdMap).hasSize(2) @@ -135,7 +136,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { datastoreRepository.addOrUpdateDesktop( visibleTasks = visibleTasks, minimizedTasks = minimizedTasks, - freeformTasksInZOrder = freeformTasksInZOrder) + freeformTasksInZOrder = freeformTasksInZOrder, + userId = DEFAULT_USER_ID) val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) assertThat(actualDesktop?.tasksByTaskIdMap?.get(task.taskId)?.desktopTaskState) @@ -158,7 +160,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { datastoreRepository.addOrUpdateDesktop( visibleTasks = visibleTasks, minimizedTasks = minimizedTasks, - freeformTasksInZOrder = freeformTasksInZOrder) + freeformTasksInZOrder = freeformTasksInZOrder, + userId = DEFAULT_USER_ID) val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) assertThat(actualDesktop?.tasksByTaskIdMap).isEmpty() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt index 975342902814..1c88a290d677 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt @@ -16,14 +16,17 @@ package com.android.wm.shell.desktopmode.persistence +import android.os.UserManager import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY import androidx.test.filters.SmallTest +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.sysui.ShellInit import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope @@ -36,26 +39,30 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.inOrder import org.mockito.Mockito.spy -import org.mockito.kotlin.any import org.mockito.kotlin.mock -import org.mockito.kotlin.verify import org.mockito.kotlin.whenever + @SmallTest @RunWith(AndroidTestingRunner::class) @ExperimentalCoroutinesApi class DesktopRepositoryInitializerTest : ShellTestCase() { + @JvmField + @Rule + val setFlagsRule = SetFlagsRule() + private lateinit var repositoryInitializer: DesktopRepositoryInitializer private lateinit var shellInit: ShellInit private lateinit var datastoreScope: CoroutineScope - private lateinit var desktopRepository: DesktopRepository + private lateinit var desktopUserRepositories: DesktopUserRepositories private val persistentRepository = mock<DesktopPersistentRepository>() + private val userManager = mock<UserManager>() private val testExecutor = mock<ShellExecutor>() @Before @@ -65,55 +72,193 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) repositoryInitializer = DesktopRepositoryInitializerImpl(context, persistentRepository, datastoreScope) - desktopRepository = - DesktopRepository( - context, shellInit, persistentRepository, repositoryInitializer, datastoreScope) + desktopUserRepositories = + DesktopUserRepositories( + context, shellInit, persistentRepository, repositoryInitializer, datastoreScope, + userManager + ) } @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM) + fun initWithPersistence_multipleUsers_addedCorrectly() = + runTest(StandardTestDispatcher()) { + whenever(persistentRepository.getUserDesktopRepositoryMap()).thenReturn( + mapOf( + USER_ID_1 to desktopRepositoryState1, + USER_ID_2 to desktopRepositoryState2 + ) + ) + whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1)) + .thenReturn(desktopRepositoryState1) + whenever(persistentRepository.getDesktopRepositoryState(USER_ID_2)) + .thenReturn(desktopRepositoryState2) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)) + .thenReturn(desktop1) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)) + .thenReturn(desktop2) + whenever(persistentRepository.readDesktop(USER_ID_2, DESKTOP_ID_3)) + .thenReturn(desktop3) + + repositoryInitializer.initialize(desktopUserRepositories) + + // Desktop Repository currently returns all tasks across desktops for a specific user + // since the repository currently doesn't handle desktops. This test logic should be updated + // once the repository handles multiple desktops. + assertThat( + desktopUserRepositories.getProfile(USER_ID_1) + .getActiveTasks(DEFAULT_DISPLAY) + ) + .containsExactly(1, 3, 4, 5) + .inOrder() + assertThat( + desktopUserRepositories.getProfile(USER_ID_1) + .getExpandedTasksOrdered(DEFAULT_DISPLAY) + ) + .containsExactly(5, 1) + .inOrder() + assertThat( + desktopUserRepositories.getProfile(USER_ID_1) + .getMinimizedTasks(DEFAULT_DISPLAY) + ) + .containsExactly(3, 4) + .inOrder() + + assertThat( + desktopUserRepositories.getProfile(USER_ID_2) + .getActiveTasks(DEFAULT_DISPLAY) + ) + .containsExactly(7, 8) + .inOrder() + assertThat( + desktopUserRepositories.getProfile(USER_ID_2) + .getExpandedTasksOrdered(DEFAULT_DISPLAY) + ) + .contains(7) + assertThat( + desktopUserRepositories.getProfile(USER_ID_2) + .getMinimizedTasks(DEFAULT_DISPLAY) + ).containsExactly(8) + } + + @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) - fun initWithPersistence_multipleTasks_addedCorrectly() = + fun initWithPersistence_singleUser_addedCorrectly() = runTest(StandardTestDispatcher()) { - val freeformTasksInZOrder = listOf(1, 2, 3) - whenever(persistentRepository.readDesktop(any(), any())) - .thenReturn( - Desktop.newBuilder() - .setDesktopId(1) - .addAllZOrderedTasks(freeformTasksInZOrder) - .putTasksByTaskId( - 1, - DesktopTask.newBuilder() - .setTaskId(1) - .setDesktopTaskState(DesktopTaskState.VISIBLE) - .build()) - .putTasksByTaskId( - 2, - DesktopTask.newBuilder() - .setTaskId(2) - .setDesktopTaskState(DesktopTaskState.VISIBLE) - .build()) - .putTasksByTaskId( - 3, - DesktopTask.newBuilder() - .setTaskId(3) - .setDesktopTaskState(DesktopTaskState.MINIMIZED) - .build()) - .build()) - - repositoryInitializer.initialize(desktopRepository) - - verify(persistentRepository).readDesktop(any(), any()) - assertThat(desktopRepository.getActiveTasks(DEFAULT_DISPLAY)) - .containsExactly(1, 2, 3) + whenever(persistentRepository.getUserDesktopRepositoryMap()).thenReturn( + mapOf( + USER_ID_1 to desktopRepositoryState1, + ) + ) + whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1)) + .thenReturn(desktopRepositoryState1) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)) + .thenReturn(desktop1) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)) + .thenReturn(desktop2) + + repositoryInitializer.initialize(desktopUserRepositories) + + // Desktop Repository currently returns all tasks across desktops for a specific user + // since the repository currently doesn't handle desktops. This test logic should be updated + // once the repository handles multiple desktops. + assertThat( + desktopUserRepositories.getProfile(USER_ID_1) + .getActiveTasks(DEFAULT_DISPLAY) + ) + .containsExactly(1, 3, 4, 5) + .inOrder() + assertThat( + desktopUserRepositories.getProfile(USER_ID_1) + .getExpandedTasksOrdered(DEFAULT_DISPLAY) + ) + .containsExactly(5, 1) .inOrder() - assertThat(desktopRepository.getExpandedTasksOrdered(DEFAULT_DISPLAY)) - .containsExactly(1, 2) + assertThat( + desktopUserRepositories.getProfile(USER_ID_1) + .getMinimizedTasks(DEFAULT_DISPLAY) + ) + .containsExactly(3, 4) .inOrder() - assertThat(desktopRepository.getMinimizedTasks(DEFAULT_DISPLAY)).containsExactly(3) } @After fun tearDown() { datastoreScope.cancel() } + + private companion object { + const val USER_ID_1 = 5 + const val USER_ID_2 = 6 + const val DESKTOP_ID_1 = 2 + const val DESKTOP_ID_2 = 3 + const val DESKTOP_ID_3 = 4 + + val freeformTasksInZOrder1 = listOf(1, 3) + val desktop1: Desktop = Desktop.newBuilder() + .setDesktopId(DESKTOP_ID_1) + .addAllZOrderedTasks(freeformTasksInZOrder1) + .putTasksByTaskId( + 1, + DesktopTask.newBuilder() + .setTaskId(1) + .setDesktopTaskState(DesktopTaskState.VISIBLE) + .build() + ) + .putTasksByTaskId( + 3, + DesktopTask.newBuilder() + .setTaskId(3) + .setDesktopTaskState(DesktopTaskState.MINIMIZED) + .build() + ) + .build() + + val freeformTasksInZOrder2 = listOf(4, 5) + val desktop2: Desktop = Desktop.newBuilder() + .setDesktopId(DESKTOP_ID_2) + .addAllZOrderedTasks(freeformTasksInZOrder2) + .putTasksByTaskId( + 4, + DesktopTask.newBuilder() + .setTaskId(4) + .setDesktopTaskState(DesktopTaskState.MINIMIZED) + .build() + ) + .putTasksByTaskId( + 5, + DesktopTask.newBuilder() + .setTaskId(5) + .setDesktopTaskState(DesktopTaskState.VISIBLE) + .build() + ) + .build() + + val freeformTasksInZOrder3 = listOf(7, 8) + val desktop3: Desktop = Desktop.newBuilder() + .setDesktopId(DESKTOP_ID_3) + .addAllZOrderedTasks(freeformTasksInZOrder3) + .putTasksByTaskId( + 7, + DesktopTask.newBuilder() + .setTaskId(7) + .setDesktopTaskState(DesktopTaskState.VISIBLE) + .build() + ) + .putTasksByTaskId( + 8, + DesktopTask.newBuilder() + .setTaskId(8) + .setDesktopTaskState(DesktopTaskState.MINIMIZED) + .build() + ) + .build() + val desktopRepositoryState1: DesktopRepositoryState = DesktopRepositoryState.newBuilder() + .putDesktop(DESKTOP_ID_1, desktop1) + .putDesktop(DESKTOP_ID_2, desktop2) + .build() + val desktopRepositoryState2: DesktopRepositoryState = DesktopRepositoryState.newBuilder() + .putDesktop(DESKTOP_ID_3, desktop3) + .build() + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java index b504a88e71fc..794ba48e2581 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java @@ -27,6 +27,7 @@ import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_TRANSITION_HA import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -46,6 +47,7 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInit; @@ -81,6 +83,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { @Mock private SurfaceControl mMockSurfaceControl; @Mock + private DesktopUserRepositories mDesktopUserRepositories; + @Mock private DesktopRepository mDesktopRepository; @Mock private DesktopTasksController mDesktopTasksController; @@ -101,13 +105,14 @@ public final class FreeformTaskListenerTests extends ShellTestCase { .mockStatic(DesktopModeStatus.class) .startMocking(); doReturn(true).when(() -> DesktopModeStatus.canEnterDesktopMode(any())); - + when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository); + when(mDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository); mFreeformTaskListener = new FreeformTaskListener( mContext, mShellInit, mTaskOrganizer, - Optional.of(mDesktopRepository), + Optional.of(mDesktopUserRepositories), Optional.of(mDesktopTasksController), mLaunchAdjacentController, mWindowDecorViewModel, @@ -123,7 +128,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl); - verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible = true); + verify(mDesktopUserRepositories.getCurrent()) + .addTask(task.displayId, task.taskId, task.isVisible = true); } @Test @@ -135,7 +141,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl); - verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible); + verify(mDesktopUserRepositories.getCurrent()) + .addTask(task.displayId, task.taskId, task.isVisible); } @Test @@ -147,7 +154,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl); - verify(mDesktopRepository, never()).addTask(task.displayId, task.taskId, task.isVisible); + verify(mDesktopUserRepositories.getCurrent(), never()) + .addTask(task.displayId, task.taskId, task.isVisible); } @Test @@ -158,7 +166,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onFocusTaskChanged(task); - verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible); + verify(mDesktopUserRepositories.getCurrent()) + .addTask(task.displayId, task.taskId, task.isVisible); } @Test @@ -171,7 +180,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onFocusTaskChanged(fullscreenTask); - verify(mDesktopRepository, never()) + verify(mDesktopUserRepositories.getCurrent(), never()) .addTask(fullscreenTask.displayId, fullscreenTask.taskId, fullscreenTask.isVisible); } @@ -214,7 +223,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase { task.displayId = INVALID_DISPLAY; mFreeformTaskListener.onTaskVanished(task); - verify(mDesktopRepository).minimizeTask(task.displayId, task.taskId); + verify(mDesktopUserRepositories.getCurrent()).minimizeTask(task.displayId, task.taskId); } @Test @@ -227,14 +236,17 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl); - when(mDesktopRepository.isClosingTask(task.taskId)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent() + .isClosingTask(task.taskId)).thenReturn(true); task.isVisible = false; task.displayId = INVALID_DISPLAY; mFreeformTaskListener.onTaskVanished(task); - verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId); - verify(mDesktopRepository).removeClosingTask(task.taskId); - verify(mDesktopRepository).removeFreeformTask(task.displayId, task.taskId); + verify(mDesktopUserRepositories.getCurrent(), never()) + .minimizeTask(task.displayId, task.taskId); + verify(mDesktopUserRepositories.getCurrent()).removeClosingTask(task.taskId); + verify(mDesktopUserRepositories.getCurrent()) + .removeFreeformTask(task.displayId, task.taskId); } @Test @@ -246,9 +258,12 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskVanished(task); - verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId); - verify(mDesktopRepository, never()).removeClosingTask(task.taskId); - verify(mDesktopRepository, never()).removeFreeformTask(task.displayId, task.taskId); + verify(mDesktopUserRepositories.getCurrent(), never()) + .minimizeTask(task.displayId, task.taskId); + verify(mDesktopUserRepositories.getCurrent(), never()) + .removeClosingTask(task.taskId); + verify(mDesktopUserRepositories.getCurrent(), never()) + .removeFreeformTask(task.displayId, task.taskId); } @Test @@ -274,7 +289,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskInfoChanged(task); verify(mTaskChangeListener, never()).onTaskChanging(any()); - verify(mDesktopRepository).updateTask(task.displayId, task.taskId, task.isVisible); + verify(mDesktopUserRepositories.getCurrent()) + .updateTask(task.displayId, task.taskId, task.isVisible); } @Test @@ -289,7 +305,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskInfoChanged(task); verify(mTaskChangeListener).onNonTransitionTaskChanging(any()); - verify(mDesktopRepository, never()) + verify(mDesktopUserRepositories.getCurrent(), never()) .updateTask(task.displayId, task.taskId, task.isVisible); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 289fd2d838fd..2eb2c3b8e2f7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -65,7 +65,7 @@ import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.SizeSpecSource; -import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -103,7 +103,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper; @Mock private PipUiEventLogger mMockPipUiEventLogger; @Mock private Optional<SplitScreenController> mMockOptionalSplitScreen; - @Mock private Optional<DesktopRepository> mMockOptionalDesktopRepository; + @Mock private Optional<DesktopUserRepositories> mMockOptionalDesktopUserRepositories; @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; @Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder; @@ -136,7 +136,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { mMockPipSurfaceTransactionHelper, mMockPipTransitionController, mMockPipParamsChangedForwarder, mMockOptionalSplitScreen, Optional.empty() /* pipPerfHintControllerOptional */, - mMockOptionalDesktopRepository, mRootTaskDisplayAreaOrganizer, + mMockOptionalDesktopUserRepositories, mRootTaskDisplayAreaOrganizer, mMockDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor); mMainExecutor.flushAll(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java index 57b6d7f0ac2f..3fe8c109807a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java @@ -45,7 +45,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; -import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; @@ -83,7 +83,7 @@ public class PipSchedulerTest { @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory; @Mock private SurfaceControl.Transaction mMockTransaction; @Mock private PipAlphaAnimator mMockAlphaAnimator; - @Mock private Optional<DesktopRepository> mMockOptionalDesktopRepository; + @Mock private Optional<DesktopUserRepositories> mMockOptionalDesktopUserRepositories; @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor; @@ -102,7 +102,7 @@ public class PipSchedulerTest { .thenReturn(mMockTransaction); mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor, - mMockPipTransitionState, mMockOptionalDesktopRepository, + mMockPipTransitionState, mMockOptionalDesktopUserRepositories, mRootTaskDisplayAreaOrganizer); mPipScheduler.setPipTransitionController(mMockPipTransitionController); mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 68c8aab8849d..95f371f7000a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -26,6 +26,8 @@ import static com.android.launcher3.Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -57,6 +59,7 @@ import android.content.pm.PackageManager; import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; +import android.os.UserManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; @@ -74,6 +77,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.ShellSharedConstants; @@ -113,8 +117,6 @@ public class RecentTasksControllerTest extends ShellTestCase { @Mock private ShellCommandHandler mShellCommandHandler; @Mock - private DesktopRepository mDesktopRepository; - @Mock private ActivityTaskManager mActivityTaskManager; @Mock private DisplayInsetsController mDisplayInsetsController; @@ -122,6 +124,10 @@ public class RecentTasksControllerTest extends ShellTestCase { private IRecentTasksListener mRecentTasksListener; @Mock private TaskStackTransitionObserver mTaskStackTransitionObserver; + @Mock + private DesktopUserRepositories mDesktopUserRepositories; + @Mock + private DesktopRepository mDesktopRepository; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -142,6 +148,8 @@ public class RecentTasksControllerTest extends ShellTestCase { .when(() -> DesktopModeStatus.canEnterDesktopMode(any())); mMainExecutor = new TestShellExecutor(); + when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository); + when(mDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository); when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); when(mContext.getSystemService(KeyguardManager.class)) .thenReturn(mock(KeyguardManager.class)); @@ -150,7 +158,7 @@ public class RecentTasksControllerTest extends ShellTestCase { mDisplayInsetsController, mMainExecutor)); mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit, mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager, - Optional.of(mDesktopRepository), mTaskStackTransitionObserver, + Optional.of(mDesktopUserRepositories), mTaskStackTransitionObserver, mMainExecutor); mRecentTasksController = spy(mRecentTasksControllerReal); mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler, @@ -182,6 +190,12 @@ public class RecentTasksControllerTest extends ShellTestCase { } @Test + public void instantiateController_initializesRepository() { + verify(mDesktopUserRepositories, times(1)).getCurrent(); + verify(mDesktopRepository, times(1)).addActiveTaskListener(any()); + } + + @Test public void testInvalidateExternalInterface_unregistersListener() { // Note: We have to use the real instance of the controller here since that is the instance // that is passed to ShellController internally, and the instance that the listener will be @@ -323,8 +337,8 @@ public class RecentTasksControllerTest extends ShellTestCase { RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); - when(mDesktopRepository.isActiveTask(1)).thenReturn(true); - when(mDesktopRepository.isActiveTask(3)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true); ArrayList<GroupedTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -362,8 +376,8 @@ public class RecentTasksControllerTest extends ShellTestCase { new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_2_50_50); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds); - when(mDesktopRepository.isActiveTask(3)).thenReturn(true); - when(mDesktopRepository.isActiveTask(5)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(5)).thenReturn(true); ArrayList<GroupedTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -402,8 +416,8 @@ public class RecentTasksControllerTest extends ShellTestCase { RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); - when(mDesktopRepository.isActiveTask(1)).thenReturn(true); - when(mDesktopRepository.isActiveTask(3)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true); ArrayList<GroupedTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -431,7 +445,9 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3, t4, t5); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); + when(mDesktopRepository.isActiveTask(2)).thenReturn(false); when(mDesktopRepository.isActiveTask(3)).thenReturn(true); + when(mDesktopRepository.isActiveTask(4)).thenReturn(false); when(mDesktopRepository.isActiveTask(5)).thenReturn(true); when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true); @@ -470,8 +486,8 @@ public class RecentTasksControllerTest extends ShellTestCase { t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450); setRawList(t1, t2); - when(mDesktopRepository.isActiveTask(1)).thenReturn(true); - when(mDesktopRepository.isActiveTask(2)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(2)).thenReturn(true); ArrayList<GroupedTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java index f0f5fe159069..894d238b7e15 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java @@ -65,6 +65,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -100,7 +101,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { @Mock private ShellCommandHandler mShellCommandHandler; @Mock - private DesktopRepository mDesktopRepository; + private DesktopUserRepositories mDesktopUserRepositories; @Mock private ActivityTaskManager mActivityTaskManager; @Mock @@ -112,6 +113,8 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { @Mock private Transitions mTransitions; + @Mock private DesktopRepository mDesktopRepository; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -131,6 +134,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { ExtendedMockito.doReturn(true) .when(() -> DesktopModeStatus.canEnterDesktopMode(any())); + when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository); mMainExecutor = new TestShellExecutor(); when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); when(mContext.getSystemService(KeyguardManager.class)) @@ -140,7 +144,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { mDisplayInsetsController, mMainExecutor)); mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit, mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager, - Optional.of(mDesktopRepository), mTaskStackTransitionObserver, + Optional.of(mDesktopUserRepositories), mTaskStackTransitionObserver, mMainExecutor); mRecentTasksController = spy(mRecentTasksControllerReal); mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt index 0e15668a05a7..a328b5b2bb6b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt @@ -77,6 +77,30 @@ class TransitionObserverTestContext : TransitionObserverTestStep { validateObj.validate() } + fun validateOnMerged( + validate: + TransitionObserverOnTransitionMergedValidation.() -> Unit + ) { + val validateObj = TransitionObserverOnTransitionMergedValidation() + transitionObserver.onTransitionMerged( + validateObj.playing, + validateObj.merged + ) + validateObj.validate() + } + + fun validateOnFinished( + validate: + TransitionObserverOnTransitionFinishedValidation.() -> Unit + ) { + val validateObj = TransitionObserverOnTransitionFinishedValidation() + transitionObserver.onTransitionFinished( + transitionReadyInput.transition, + validateObj.aborted + ) + validateObj.validate() + } + fun invokeObservable() { transitionObserver.onTransitionReady( transitionReadyInput.transition, @@ -162,6 +186,28 @@ class TransitionObserverInputBuilder : TransitionObserverTestStep { class TransitionObserverResultValidation : TransitionObserverTestStep /** + * Phase responsible for the execution of validation methods after the + * [TransitionObservable#onTransitionMerged] has been executed. + */ +class TransitionObserverOnTransitionMergedValidation : TransitionObserverTestStep { + val merged = mock<IBinder>() + val playing = mock<IBinder>() + + init { + spyOn(merged) + spyOn(playing) + } +} + +/** + * Phase responsible for the execution of validation methods after the + * [TransitionObservable#onTransitionFinished] has been executed. + */ +class TransitionObserverOnTransitionFinishedValidation : TransitionObserverTestStep { + var aborted: Boolean = false +} + +/** * Allows to run a test about a specific [TransitionObserver] passing the specific * implementation and input value as parameters for the [TransitionObserver#onTransitionReady] * method. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt index 1215c52209a5..44035588887f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt @@ -29,12 +29,13 @@ import com.android.wm.shell.MockToken import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.TestShellExecutor -import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -55,17 +56,18 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() { @Rule val setFlagsRule: SetFlagsRule = SetFlagsRule() - private lateinit var desktopRepository: DesktopRepository + private lateinit var userRepositories: DesktopUserRepositories private lateinit var menu: DesktopHeaderManageWindowsMenu @Before fun setUp() { - desktopRepository = DesktopRepository( + userRepositories = DesktopUserRepositories( context = context, shellInit = ShellInit(TestShellExecutor()), persistentRepository = mock(), repositoryInitializer = mock(), - mainCoroutineScope = mock() + mainCoroutineScope = mock(), + userManager = mock(), ) } @@ -75,15 +77,15 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() { } @Test + @Ignore("Test is failing internally") @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun testShow_forImmersiveTask_usesSystemViewContainer() { val task = createFreeformTask() - desktopRepository.setTaskInFullImmersiveState( + userRepositories.getProfile(DEFAULT_USER_ID).setTaskInFullImmersiveState( displayId = task.displayId, taskId = task.taskId, immersive = true ) - menu = createMenu(task) assertThat(menu.menuViewContainer).isInstanceOf(AdditionalSystemViewContainer::class.java) @@ -96,7 +98,7 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() { displayController = mock(), rootTdaOrganizer = mock(), context = context, - desktopRepository = desktopRepository, + desktopUserRepositories = userRepositories, surfaceControlBuilderSupplier = { SurfaceControl.Builder() }, surfaceControlTransactionSupplier = { SurfaceControl.Transaction() }, snapshotList = emptyList(), @@ -109,4 +111,8 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() { .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(WINDOWING_MODE_FREEFORM) .build() + + private companion object { + const val DEFAULT_USER_ID = 10 + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 153be07bd204..a4e3af47edaa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -59,6 +59,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.window.flags.Flags import com.android.wm.shell.R +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.desktopmode.DesktopImmersiveController import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger @@ -398,11 +399,12 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest maxOrRestoreListenerCaptor.value.invoke() verify(mockDesktopTasksController).toggleDesktopTaskSize( - eq(decor.mTaskInfo), - eq(ResizeTrigger.MAXIMIZE_MENU), - eq(InputMethod.UNKNOWN_INPUT_METHOD), - any(), - any() + decor.mTaskInfo, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.MAXIMIZE_MENU_TO_MAXIMIZE, + InputMethod.UNKNOWN_INPUT_METHOD + ) ) } @@ -1061,11 +1063,12 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest verify(mockDesktopTasksController) .toggleDesktopTaskSize( - eq(decor.mTaskInfo), - eq(ResizeTrigger.MAXIMIZE_BUTTON), - eq(InputMethod.UNKNOWN_INPUT_METHOD), - any(), - any(), + decor.mTaskInfo, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.UNKNOWN_INPUT_METHOD + ) ) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt index 080f496593cf..afd46078074c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt @@ -60,6 +60,7 @@ import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTasksLimiter +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository import com.android.wm.shell.desktopmode.education.AppHandleEducationController import com.android.wm.shell.desktopmode.education.AppToWebEducationController @@ -108,7 +109,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { protected val mockTaskOrganizer = mock<ShellTaskOrganizer>() protected val mockDisplayController = mock<DisplayController>() protected val mockSplitScreenController = mock<SplitScreenController>() - protected val mockDesktopRepository = mock<DesktopRepository>() + protected val mockDesktopUserRepositories = mock<DesktopUserRepositories>() protected val mockDisplayLayout = mock<DisplayLayout>() protected val displayInsetsController = mock<DisplayInsetsController>() protected val mockSyncQueue = mock<SyncTransactionQueue>() @@ -142,6 +143,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { protected val mockAppToWebEducationController = mock<AppToWebEducationController>() protected val mockFocusTransitionObserver = mock<FocusTransitionObserver>() protected val mockCaptionHandleRepository = mock<WindowDecorCaptionHandleRepository>() + protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>() protected val motionEvent = mock<MotionEvent>() val displayController = mock<DisplayController>() val displayLayout = mock<DisplayLayout>() @@ -168,6 +170,9 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { windowDecorByTaskIdSpy.clear() spyContext.addMockSystemService(InputManager::class.java, mockInputManager) desktopModeEventLogger = mock<DesktopModeEventLogger>() + whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository) + whenever(mockDesktopUserRepositories.getProfile(anyInt())) + .thenReturn(mockDesktopRepository) desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel( spyContext, testShellExecutor, @@ -178,7 +183,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { mockShellCommandHandler, mockWindowManager, mockTaskOrganizer, - mockDesktopRepository, + mockDesktopUserRepositories, mockDisplayController, mockShellController, displayInsetsController, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 03c7c9857d8f..61f3755ca772 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -109,6 +109,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.CaptionState; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -165,7 +166,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; @Mock - private DesktopRepository mMockDesktopRepository; + private DesktopUserRepositories mMockDesktopUserRepositories; @Mock private Choreographer mMockChoreographer; @Mock @@ -214,6 +215,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private WindowDecorCaptionHandleRepository mMockCaptionHandleRepository; @Mock private DesktopModeEventLogger mDesktopModeEventLogger; + @Mock + private DesktopRepository mDesktopRepository; @Captor private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener; @Captor @@ -270,6 +273,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false); when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(), any())).thenReturn(mMockAppHeaderViewHolder); + when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository); + when(mMockDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository); } @After @@ -1469,8 +1474,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, true /* relayout */); - when(mMockDesktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) - .thenReturn(true); + when(mMockDesktopUserRepositories.getCurrent() + .isTaskInFullImmersiveState(taskInfo.taskId)).thenReturn(true); createHandleMenu(decoration); @@ -1491,7 +1496,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Test @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION}) + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION_INTEGRATION}) public void notifyCaptionStateChanged_flagDisabled_doNoNotify() { when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true); final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); @@ -1705,8 +1710,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { boolean relayout) { final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext, mContext, mMockDisplayController, mMockSplitScreenController, - mMockDesktopRepository, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, - mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue, + mMockDesktopUserRepositories, mMockShellTaskOrganizer, taskInfo, + mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue, mMockAppHeaderViewHolderFactory, mMockRootTaskDisplayAreaOrganizer, mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new, mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt new file mode 100644 index 000000000000..2f223ded5ce1 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt @@ -0,0 +1,221 @@ +/* + * 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.wm.shell.windowdecor.common.viewhost + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.View +import android.view.WindowManager +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertThrows +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify + +/** + * Tests for [DefaultWindowDecorViewHost]. + * + * Build/Install/Run: atest WMShellUnitTests:DefaultWindowDecorViewHostTest + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +class DefaultWindowDecorViewHostTest : ShellTestCase() { + + @Test + fun updateView_layoutInViewHost() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view) + } + + @Test + fun updateView_alreadyLaidOut_relayouts() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + val otherParams = WindowManager.LayoutParams(200, 200) + windowDecorViewHost.updateView( + view = view, + attrs = otherParams, + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view) + assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width) + .isEqualTo(otherParams.width) + } + + @Test + fun updateView_replacingView_throws() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + val otherView = View(context) + assertThrows(Exception::class.java) { + windowDecorViewHost.updateView( + view = otherView, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateView_clearsPendingAsyncJob() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val asyncView = View(context) + val syncView = View(context) + val asyncAttrs = WindowManager.LayoutParams(100, 100) + val syncAttrs = WindowManager.LayoutParams(200, 200) + + windowDecorViewHost.updateViewAsync( + view = asyncView, + attrs = asyncAttrs, + configuration = context.resources.configuration, + ) + + // No view host yet, since the coroutine hasn't run. + assertThat(windowDecorViewHost.viewHost).isNull() + + windowDecorViewHost.updateView( + view = syncView, + attrs = syncAttrs, + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + // Would run coroutine if it hadn't been cancelled. + advanceUntilIdle() + + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isNotNull() + // View host view/attrs should match the ones from the sync call, plus, since the + // sync/async were made with different views, if the job hadn't been cancelled there + // would've been an exception thrown as replacing views isn't allowed. + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(syncView) + assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width) + .isEqualTo(syncAttrs.width) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateViewAsync() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + val attrs = WindowManager.LayoutParams(100, 100) + + windowDecorViewHost.updateViewAsync( + view = view, + attrs = attrs, + configuration = context.resources.configuration, + ) + + assertThat(windowDecorViewHost.viewHost).isNull() + + advanceUntilIdle() + + assertThat(windowDecorViewHost.viewHost).isNotNull() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateViewAsync_clearsPendingAsyncJob() = runTest { + val windowDecorViewHost = createDefaultViewHost() + + val view = View(context) + windowDecorViewHost.updateViewAsync( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + ) + val otherView = View(context) + windowDecorViewHost.updateViewAsync( + view = otherView, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + ) + + advanceUntilIdle() + + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(otherView) + } + + @Test + fun release() = runTest { + val windowDecorViewHost = createDefaultViewHost() + + val view = View(context) + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + val t = mock(SurfaceControl.Transaction::class.java) + windowDecorViewHost.release(t) + + verify(windowDecorViewHost.viewHost!!).release() + verify(t).remove(windowDecorViewHost.surfaceControl) + } + + private fun CoroutineScope.createDefaultViewHost() = + DefaultWindowDecorViewHost( + context = context, + mainScope = this, + display = context.display, + surfaceControlViewHostFactory = { c, d, wwm, s -> + spy(SurfaceControlViewHost(c, d, wwm, s)) + }, + ) +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt index d29002199f9d..193c2c25d26d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt @@ -25,7 +25,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger -import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -52,7 +52,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { private val syncQueueMock: SyncTransactionQueue = mock() private val transitionsMock: Transitions = mock() private val shellTaskOrganizerMock: ShellTaskOrganizer = mock() - private val desktopRepository: DesktopRepository = mock() + private val userRepositories: DesktopUserRepositories = mock() private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val toggleResizeDesktopTaskTransitionHandlerMock: ToggleResizeDesktopTaskTransitionHandler = @@ -75,7 +75,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { shellTaskOrganizerMock, toggleResizeDesktopTaskTransitionHandlerMock, returnToDragStartAnimatorMock, - desktopRepository, + userRepositories, desktopModeEventLogger, ) whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt index 3b39f1e4a25a..95e2151be96c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt @@ -39,6 +39,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeT import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler import com.android.wm.shell.transition.Transitions @@ -93,10 +94,11 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { private val transition: IBinder = mock() private val info: TransitionInfo = mock() private val finishCallback: Transitions.TransitionFinishCallback = mock() - private val desktopRepository: DesktopRepository = mock() + private val userRepositories: DesktopUserRepositories = mock() private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager = mock() private val motionEvent: MotionEvent = mock() + private val desktopRepository: DesktopRepository = mock() private lateinit var tilingDecoration: DesktopTilingWindowDecoration private val split_divider_width = 10 @@ -116,10 +118,11 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { shellTaskOrganizer, toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, - desktopRepository, + userRepositories, desktopModeEventLogger, ) whenever(context.createContextAsUser(any(), any())).thenReturn(context) + whenever(userRepositories.current).thenReturn(desktopRepository) } @Test @@ -275,8 +278,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { } whenever(context.resources).thenReturn(resources) whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) - whenever(desktopRepository.isVisibleTask(eq(task1.taskId))).thenReturn(true) - whenever(desktopRepository.isVisibleTask(eq(task2.taskId))).thenReturn(true) + whenever(userRepositories.current.isVisibleTask(eq(task1.taskId))).thenReturn(true) + whenever(userRepositories.current.isVisibleTask(eq(task2.taskId))).thenReturn(true) tilingDecoration.onAppTiled( task1, @@ -308,7 +311,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { whenever(context.resources).thenReturn(resources) whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) whenever(desktopWindowDecoration.getLeash()).thenReturn(surfaceControlMock) - whenever(desktopRepository.isVisibleTask(any())).thenReturn(true) + whenever(userRepositories.current.isVisibleTask(any())).thenReturn(true) tilingDecoration.onAppTiled( task1, desktopWindowDecoration, @@ -341,7 +344,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { whenever(context.resources).thenReturn(resources) whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) whenever(desktopWindowDecoration.getLeash()).thenReturn(surfaceControlMock) - whenever(desktopRepository.isVisibleTask(any())).thenReturn(true) + whenever(userRepositories.current.isVisibleTask(any())).thenReturn(true) tilingDecoration.onAppTiled( task1, desktopWindowDecoration, @@ -614,7 +617,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { private fun createVisibleTask() = createFreeformTask().also { - whenever(desktopRepository.isVisibleTask(eq(it.taskId))).thenReturn(true) + whenever(userRepositories.current.isVisibleTask(eq(it.taskId))).thenReturn(true) } companion object { diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index e5fb75575ac3..7b45070af312 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -48,7 +48,14 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, minikinPaint.localeListId = paint->getMinikinLocaleListId(); minikinPaint.fontStyle = resolvedFace->fStyle; minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings(); - minikinPaint.fontVariationSettings = paint->getFontVariationOverride(); + if (!resolvedFace->fIsVariationInstance) { + // This is an optimization for direct private API use typically done by System UI. + // In the public API surface, if Typeface is already configured for variation instance + // (Target SDK <= 35) the font variation settings of Paint is not set. + // On the other hand, if Typeface is not configured so (Target SDK >= 36), the font + // variation settings are configured dynamically. + minikinPaint.fontVariationSettings = paint->getFontVariationOverride(); + } minikinPaint.verticalText = paint->isVerticalText(); const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant(); diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java index 1ecba31ce07f..3efb5f961dc8 100644 --- a/media/java/android/media/MediaCas.java +++ b/media/java/android/media/MediaCas.java @@ -1010,17 +1010,17 @@ public final class MediaCas implements AutoCloseable { * scenario, when both resource holder and resource challenger have same processId and same * priority. * - * @param resourceHolderRetain Set to {@code true} to allow the resource holder to retain - * ownership, or false to allow the resource challenger to acquire the resource. - * If not explicitly set, resourceHolderRetain is set to {@code false}. + *@param enabled Set to {@code true} to allow the resource holder to retain ownership, + * or false to allow the resource challenger to acquire the resource. + * If not explicitly set, enabled is set to {@code false}. * @hide */ @FlaggedApi(FLAG_SET_RESOURCE_HOLDER_RETAIN) @SystemApi @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) - public void setResourceHolderRetain(boolean resourceHolderRetain) { + public void setResourceOwnershipRetention(boolean enabled) { if (mTunerResourceManager != null) { - mTunerResourceManager.setResourceHolderRetain(mClientId, resourceHolderRetain); + mTunerResourceManager.setResourceOwnershipRetention(mClientId, enabled); } } diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 09022782e6c3..d433ec876af9 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -21,6 +21,7 @@ import static android.media.audio.Flags.FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE; import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER; import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES; +import static com.android.media.flags.Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2; import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES; import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES; @@ -421,6 +422,51 @@ public final class MediaRoute2Info implements Parcelable { */ public static final int TYPE_GROUP = 2000; + /** @hide */ + @IntDef( + prefix = {"ROUTING_TYPE_"}, + value = { + FLAG_ROUTING_TYPE_SYSTEM_AUDIO, + FLAG_ROUTING_TYPE_SYSTEM_VIDEO, + FLAG_ROUTING_TYPE_REMOTE + }, + flag = true) + @Retention(RetentionPolicy.SOURCE) + public @interface RoutingType {} + + /** + * Indicates that a route supports routing of the system audio. + * + * <p>Providers that support this type of routing require the {@link + * android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission. + */ + @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2) + public static final int FLAG_ROUTING_TYPE_SYSTEM_AUDIO = 1; + + /** + * Indicates that a route supports routing of the system video. + * + * @hide + */ + // TODO: b/380431086 - Enable this API once we add support for system video routing. + @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2) + public static final int FLAG_ROUTING_TYPE_SYSTEM_VIDEO = 1 << 1; + + /** + * Indicates that a route supports routing playback to remote routes through control commands. + * + * <p>This type of routing does not affect affect this system's audio or video, but instead + * relies on the device that corresponds to this route to fetch and play the media. It also + * requires the media app to take care of initializing and controlling playback. + */ + @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2) + public static final int FLAG_ROUTING_TYPE_REMOTE = 1 << 2; + + private static final int FLAG_ROUTING_TYPE_ALL = + FLAG_ROUTING_TYPE_SYSTEM_AUDIO + | FLAG_ROUTING_TYPE_SYSTEM_VIDEO + | FLAG_ROUTING_TYPE_REMOTE; + /** * Route feature: Live audio. * <p> @@ -553,6 +599,7 @@ public final class MediaRoute2Info implements Parcelable { private final List<String> mFeatures; @Type private final int mType; + @RoutingType private final int mRoutingTypeFlags; private final boolean mIsSystem; private final Uri mIconUri; private final CharSequence mDescription; @@ -576,6 +623,7 @@ public final class MediaRoute2Info implements Parcelable { mName = builder.mName; mFeatures = builder.mFeatures; mType = builder.mType; + mRoutingTypeFlags = builder.mRoutingTypeFlags; mIsSystem = builder.mIsSystem; mIconUri = builder.mIconUri; mDescription = builder.mDescription; @@ -600,6 +648,7 @@ public final class MediaRoute2Info implements Parcelable { mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mFeatures = in.createStringArrayList(); mType = in.readInt(); + mRoutingTypeFlags = validateRoutingTypeFlags(in.readInt()); mIsSystem = in.readBoolean(); mIconUri = in.readParcelable(null, android.net.Uri.class); mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); @@ -660,6 +709,13 @@ public final class MediaRoute2Info implements Parcelable { return mType; } + /** Returns the flags that indicate the routing types supported by this route. */ + @RoutingType + @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2) + public int getSupportedRoutingTypes() { + return mRoutingTypeFlags; + } + /** * Returns whether the route is a system route or not. * <p> @@ -904,6 +960,7 @@ public final class MediaRoute2Info implements Parcelable { pw.println(indent + "mName=" + mName); pw.println(indent + "mFeatures=" + mFeatures); pw.println(indent + "mType=" + getDeviceTypeString(mType)); + pw.println(indent + "mRoutingTypeFlags=" + getRoutingTypeFlagsString(mRoutingTypeFlags)); pw.println(indent + "mIsSystem=" + mIsSystem); pw.println(indent + "mIconUri=" + mIconUri); pw.println(indent + "mDescription=" + mDescription); @@ -941,6 +998,7 @@ public final class MediaRoute2Info implements Parcelable { && Objects.equals(mName, other.mName) && Objects.equals(mFeatures, other.mFeatures) && (mType == other.mType) + && (mRoutingTypeFlags == other.mRoutingTypeFlags) && (mIsSystem == other.mIsSystem) && Objects.equals(mIconUri, other.mIconUri) && Objects.equals(mDescription, other.mDescription) @@ -966,6 +1024,7 @@ public final class MediaRoute2Info implements Parcelable { mName, mFeatures, mType, + mRoutingTypeFlags, mIsSystem, mIconUri, mDescription, @@ -994,6 +1053,8 @@ public final class MediaRoute2Info implements Parcelable { .append(getName()) .append(", type=") .append(getDeviceTypeString(getType())) + .append(", routingTypes=") + .append(getRoutingTypeFlagsString(getSupportedRoutingTypes())) .append(", isSystem=") .append(isSystemRoute()) .append(", features=") @@ -1035,6 +1096,7 @@ public final class MediaRoute2Info implements Parcelable { TextUtils.writeToParcel(mName, dest, flags); dest.writeStringList(mFeatures); dest.writeInt(mType); + dest.writeInt(mRoutingTypeFlags); dest.writeBoolean(mIsSystem); dest.writeParcelable(mIconUri, flags); TextUtils.writeToParcel(mDescription, dest, flags); @@ -1143,6 +1205,34 @@ public final class MediaRoute2Info implements Parcelable { } } + /** Returns a human-readable representation of the given {@code routingTypeFlags}. */ + private static String getRoutingTypeFlagsString(@RoutingType int routingTypeFlags) { + List<String> typeStrings = new ArrayList<>(); + if ((routingTypeFlags & FLAG_ROUTING_TYPE_SYSTEM_AUDIO) != 0) { + typeStrings.add("SYSTEM_AUDIO"); + } + if ((routingTypeFlags & FLAG_ROUTING_TYPE_SYSTEM_VIDEO) != 0) { + typeStrings.add("SYSTEM_VIDEO"); + } + if ((routingTypeFlags & FLAG_ROUTING_TYPE_REMOTE) != 0) { + typeStrings.add("REMOTE"); + } + return String.join(/* delimiter= */ "|", typeStrings); + } + + /** + * Throws an {@link IllegalArgumentException} if the provided {@code routingTypeFlags} are not + * valid. Otherwise, returns the provided value. + */ + private static int validateRoutingTypeFlags(@RoutingType int routingTypeFlags) { + if (routingTypeFlags == 0 || (routingTypeFlags & ~FLAG_ROUTING_TYPE_ALL) != 0) { + throw new IllegalArgumentException( + "Invalid routing type flags: " + Integer.toHexString(routingTypeFlags)); + } else { + return routingTypeFlags; + } + } + /** * Builder for {@link MediaRoute2Info media route info}. */ @@ -1153,6 +1243,7 @@ public final class MediaRoute2Info implements Parcelable { @Type private int mType = TYPE_UNKNOWN; + @RoutingType private int mRoutingTypeFlags = FLAG_ROUTING_TYPE_REMOTE; private boolean mIsSystem; private Uri mIconUri; private CharSequence mDescription; @@ -1224,6 +1315,7 @@ public final class MediaRoute2Info implements Parcelable { mName = routeInfo.mName; mFeatures = new ArrayList<>(routeInfo.mFeatures); mType = routeInfo.mType; + mRoutingTypeFlags = routeInfo.mRoutingTypeFlags; mIsSystem = routeInfo.mIsSystem; mIconUri = routeInfo.mIconUri; mDescription = routeInfo.mDescription; @@ -1301,6 +1393,18 @@ public final class MediaRoute2Info implements Parcelable { } /** + * Sets the routing types that this route supports. + * + * @see MediaRoute2Info#getSupportedRoutingTypes() + */ + @NonNull + @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2) + public Builder setSupportedRoutingTypes(@RoutingType int routingTypeFlags) { + mRoutingTypeFlags = validateRoutingTypeFlags(routingTypeFlags); + return this; + } + + /** * Sets whether the route is a system route or not. * @hide */ diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl index b7e75b7e6649..dc3fbf60c0b3 100644 --- a/media/java/android/media/quality/IMediaQualityManager.aidl +++ b/media/java/android/media/quality/IMediaQualityManager.aidl @@ -30,42 +30,42 @@ import android.media.quality.SoundProfile; * @hide */ interface IMediaQualityManager { - PictureProfile createPictureProfile(in PictureProfile pp); - void updatePictureProfile(in String id, in PictureProfile pp); - void removePictureProfile(in String id); - PictureProfile getPictureProfile(in int type, in String name); - List<PictureProfile> getPictureProfilesByPackage(in String packageName); - List<PictureProfile> getAvailablePictureProfiles(); - List<String> getPictureProfilePackageNames(); - List<String> getPictureProfileAllowList(); - void setPictureProfileAllowList(in List<String> packages); - PictureProfileHandle getPictureProfileHandle(in String id); + PictureProfile createPictureProfile(in PictureProfile pp, int userId); + void updatePictureProfile(in String id, in PictureProfile pp, int userId); + void removePictureProfile(in String id, int userId); + PictureProfile getPictureProfile(in int type, in String name, int userId); + List<PictureProfile> getPictureProfilesByPackage(in String packageName, int userId); + List<PictureProfile> getAvailablePictureProfiles(int userId); + List<String> getPictureProfilePackageNames(int userId); + List<String> getPictureProfileAllowList(int userId); + void setPictureProfileAllowList(in List<String> packages, int userId); + PictureProfileHandle getPictureProfileHandle(in String id, int userId); - SoundProfile createSoundProfile(in SoundProfile pp); - void updateSoundProfile(in String id, in SoundProfile pp); - void removeSoundProfile(in String id); - SoundProfile getSoundProfile(in int type, in String name); - List<SoundProfile> getSoundProfilesByPackage(in String packageName); - List<SoundProfile> getAvailableSoundProfiles(); - List<String> getSoundProfilePackageNames(); - List<String> getSoundProfileAllowList(); - void setSoundProfileAllowList(in List<String> packages); + SoundProfile createSoundProfile(in SoundProfile pp, int userId); + void updateSoundProfile(in String id, in SoundProfile pp, int userId); + void removeSoundProfile(in String id, int userId); + SoundProfile getSoundProfile(in int type, in String name, int userId); + List<SoundProfile> getSoundProfilesByPackage(in String packageName, int userId); + List<SoundProfile> getAvailableSoundProfiles(int userId); + List<String> getSoundProfilePackageNames(int userId); + List<String> getSoundProfileAllowList(int userId); + void setSoundProfileAllowList(in List<String> packages, int userId); void registerPictureProfileCallback(in IPictureProfileCallback cb); void registerSoundProfileCallback(in ISoundProfileCallback cb); void registerAmbientBacklightCallback(in IAmbientBacklightCallback cb); - List<ParamCapability> getParamCapabilities(in List<String> names); + List<ParamCapability> getParamCapabilities(in List<String> names, int userId); - boolean isSupported(); - void setAutoPictureQualityEnabled(in boolean enabled); - boolean isAutoPictureQualityEnabled(); - void setSuperResolutionEnabled(in boolean enabled); - boolean isSuperResolutionEnabled(); - void setAutoSoundQualityEnabled(in boolean enabled); - boolean isAutoSoundQualityEnabled(); + boolean isSupported(int userId); + void setAutoPictureQualityEnabled(in boolean enabled, int userId); + boolean isAutoPictureQualityEnabled(int userId); + void setSuperResolutionEnabled(in boolean enabled, int userId); + boolean isSuperResolutionEnabled(int userId); + void setAutoSoundQualityEnabled(in boolean enabled, int userId); + boolean isAutoSoundQualityEnabled(int userId); - void setAmbientBacklightSettings(in AmbientBacklightSettings settings); - void setAmbientBacklightEnabled(in boolean enabled); - boolean isAmbientBacklightEnabled(); + void setAmbientBacklightSettings(in AmbientBacklightSettings settings, int userId); + void setAmbientBacklightEnabled(in boolean enabled, int userId); + boolean isAmbientBacklightEnabled(int userId); } diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index 50055971d66d..d4de99aadb14 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -47,6 +47,7 @@ public final class MediaQualityManager { private final IMediaQualityManager mService; private final Context mContext; + private final int mUserId; private final Object mLock = new Object(); // @GuardedBy("mLock") private final List<PictureProfileCallbackRecord> mPpCallbackRecords = new ArrayList<>(); @@ -61,6 +62,7 @@ public final class MediaQualityManager { */ public MediaQualityManager(Context context, IMediaQualityManager service) { mContext = context; + mUserId = context.getUserId(); mService = service; IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() { @Override @@ -219,7 +221,7 @@ public final class MediaQualityManager { public PictureProfile getPictureProfile( @PictureProfile.ProfileType int type, @NonNull String name) { try { - return mService.getPictureProfile(type, name); + return mService.getPictureProfile(type, name, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -236,7 +238,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public List<PictureProfile> getPictureProfilesByPackage(@NonNull String packageName) { try { - return mService.getPictureProfilesByPackage(packageName); + return mService.getPictureProfilesByPackage(packageName, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -248,7 +250,7 @@ public final class MediaQualityManager { @NonNull public List<PictureProfile> getAvailablePictureProfiles() { try { - return mService.getAvailablePictureProfiles(); + return mService.getAvailablePictureProfiles(mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -265,7 +267,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public List<String> getPictureProfilePackageNames() { try { - return mService.getPictureProfilePackageNames(); + return mService.getPictureProfilePackageNames(mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -277,7 +279,7 @@ public final class MediaQualityManager { */ public PictureProfileHandle getPictureProfileHandle(String id) { try { - return mService.getPictureProfileHandle(id); + return mService.getPictureProfileHandle(id, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -291,7 +293,7 @@ public final class MediaQualityManager { */ public void createPictureProfile(@NonNull PictureProfile pp) { try { - mService.createPictureProfile(pp); + mService.createPictureProfile(pp, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -303,7 +305,7 @@ public final class MediaQualityManager { */ public void updatePictureProfile(@NonNull String profileId, @NonNull PictureProfile pp) { try { - mService.updatePictureProfile(profileId, pp); + mService.updatePictureProfile(profileId, pp, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -315,7 +317,7 @@ public final class MediaQualityManager { */ public void removePictureProfile(@NonNull String profileId) { try { - mService.removePictureProfile(profileId); + mService.removePictureProfile(profileId, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -362,7 +364,7 @@ public final class MediaQualityManager { public SoundProfile getSoundProfile( @SoundProfile.ProfileType int type, @NonNull String name) { try { - return mService.getSoundProfile(type, name); + return mService.getSoundProfile(type, name, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -379,7 +381,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public List<SoundProfile> getSoundProfilesByPackage(@NonNull String packageName) { try { - return mService.getSoundProfilesByPackage(packageName); + return mService.getSoundProfilesByPackage(packageName, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -391,7 +393,7 @@ public final class MediaQualityManager { @NonNull public List<SoundProfile> getAvailableSoundProfiles() { try { - return mService.getAvailableSoundProfiles(); + return mService.getAvailableSoundProfiles(mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -409,7 +411,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public List<String> getSoundProfilePackageNames() { try { - return mService.getSoundProfilePackageNames(); + return mService.getSoundProfilePackageNames(mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -424,7 +426,7 @@ public final class MediaQualityManager { */ public void createSoundProfile(@NonNull SoundProfile sp) { try { - mService.createSoundProfile(sp); + mService.createSoundProfile(sp, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -436,7 +438,7 @@ public final class MediaQualityManager { */ public void updateSoundProfile(@NonNull String profileId, @NonNull SoundProfile sp) { try { - mService.updateSoundProfile(profileId, sp); + mService.updateSoundProfile(profileId, sp, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -448,7 +450,7 @@ public final class MediaQualityManager { */ public void removeSoundProfile(@NonNull String profileId) { try { - mService.removeSoundProfile(profileId); + mService.removeSoundProfile(profileId, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -460,7 +462,7 @@ public final class MediaQualityManager { @NonNull public List<ParamCapability> getParamCapabilities(@NonNull List<String> names) { try { - return mService.getParamCapabilities(names); + return mService.getParamCapabilities(names, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -478,7 +480,7 @@ public final class MediaQualityManager { @NonNull public List<String> getPictureProfileAllowList() { try { - return mService.getPictureProfileAllowList(); + return mService.getPictureProfileAllowList(mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -492,7 +494,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setPictureProfileAllowList(@NonNull List<String> packageNames) { try { - mService.setPictureProfileAllowList(packageNames); + mService.setPictureProfileAllowList(packageNames, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -510,7 +512,7 @@ public final class MediaQualityManager { @NonNull public List<String> getSoundProfileAllowList() { try { - return mService.getSoundProfileAllowList(); + return mService.getSoundProfileAllowList(mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -524,7 +526,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public void setSoundProfileAllowList(@NonNull List<String> packageNames) { try { - mService.setSoundProfileAllowList(packageNames); + mService.setSoundProfileAllowList(packageNames, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -536,7 +538,7 @@ public final class MediaQualityManager { */ public boolean isSupported() { try { - return mService.isSupported(); + return mService.isSupported(mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -554,7 +556,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setAutoPictureQualityEnabled(boolean enabled) { try { - mService.setAutoPictureQualityEnabled(enabled); + mService.setAutoPictureQualityEnabled(enabled, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -565,7 +567,7 @@ public final class MediaQualityManager { */ public boolean isAutoPictureQualityEnabled() { try { - return mService.isAutoPictureQualityEnabled(); + return mService.isAutoPictureQualityEnabled(mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -582,7 +584,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setSuperResolutionEnabled(boolean enabled) { try { - mService.setSuperResolutionEnabled(enabled); + mService.setSuperResolutionEnabled(enabled, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -593,7 +595,7 @@ public final class MediaQualityManager { */ public boolean isSuperResolutionEnabled() { try { - return mService.isSuperResolutionEnabled(); + return mService.isSuperResolutionEnabled(mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -611,7 +613,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public void setAutoSoundQualityEnabled(boolean enabled) { try { - mService.setAutoSoundQualityEnabled(enabled); + mService.setAutoSoundQualityEnabled(enabled, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -622,7 +624,7 @@ public final class MediaQualityManager { */ public boolean isAutoSoundQualityEnabled() { try { - return mService.isAutoSoundQualityEnabled(); + return mService.isAutoSoundQualityEnabled(mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -668,7 +670,7 @@ public final class MediaQualityManager { @NonNull AmbientBacklightSettings settings) { Preconditions.checkNotNull(settings); try { - mService.setAmbientBacklightSettings(settings); + mService.setAmbientBacklightSettings(settings, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -679,7 +681,7 @@ public final class MediaQualityManager { */ public boolean isAmbientBacklightEnabled() { try { - return mService.isAmbientBacklightEnabled(); + return mService.isAmbientBacklightEnabled(mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -692,7 +694,7 @@ public final class MediaQualityManager { */ public void setAmbientBacklightEnabled(boolean enabled) { try { - mService.setAmbientBacklightEnabled(enabled); + mService.setAmbientBacklightEnabled(enabled, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig index 572db97595ea..3451dfc559ee 100644 --- a/media/java/android/media/tv/flags/media_tv.aconfig +++ b/media/java/android/media/tv/flags/media_tv.aconfig @@ -93,7 +93,7 @@ flag { name: "set_resource_holder_retain" is_exported: true namespace: "media_tv" - description: "Feature flag to add setResourceHolderRetain api to MediaCas and Tuner JAVA." + description: "Feature flag to add setResourceOwnershipRetention api to MediaCas and Tuner JAVA." bug: "372973197" } diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index b1adb77f9543..7f7a23969411 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -757,14 +757,14 @@ public class Tuner implements AutoCloseable { * scenario, when both resource holder and resource challenger have same processId and same * priority. * - * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or - * false to allow the resource challenger to acquire the resource. If not explicitly set, - * resourceHolderRetain is set to false. + * @param enabled Set to {@code true} to allow the resource holder to retain ownership, + * or false to allow the resource challenger to acquire the resource. + * If not explicitly set, enabled is set to {@code false}. */ @FlaggedApi(FLAG_SET_RESOURCE_HOLDER_RETAIN) @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) - public void setResourceHolderRetain(boolean resourceHolderRetain) { - mTunerResourceManager.setResourceHolderRetain(mClientId, resourceHolderRetain); + public void setResourceOwnershipRetention(boolean enabled) { + mTunerResourceManager.setResourceOwnershipRetention(mClientId, enabled); } /** diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java index be65ad98c35b..2ed642eecb81 100644 --- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java +++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; +import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemService; import android.annotation.TestApi; @@ -227,15 +228,16 @@ public class TunerResourceManager { * scenario, when both resource holder and resource challenger have same processId and same * priority. * - * @param clientId The client id used to set ownership of resource to owner in case of resource + * @param clientId The client id used to set ownership of resource in case of resource * challenger situation. - * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or - * false to allow the resource challenger to acquire the resource. If not explicitly set, - * resourceHolderRetain is set to false. + * @param enabled Set to {@code true} to allow the resource holder to retain ownership, + * or false to allow the resource challenger to acquire the resource. + * If not explicitly set, enabled is set to {@code false}. */ - public void setResourceHolderRetain(int clientId, boolean resourceHolderRetain) { + @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) + public void setResourceOwnershipRetention(int clientId, boolean enabled) { try { - mService.setResourceHolderRetain(clientId, resourceHolderRetain); + mService.setResourceOwnershipRetention(clientId, enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl index c57be1b09b66..50f9fe556cee 100644 --- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl +++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl @@ -156,12 +156,13 @@ interface ITunerResourceManager { * scenario, when both Resource Holder and Resource Challenger have same processId and same * priority. * - * @param clientId The resourceHolderRetain of the client is updated using client ID. - * @param resourceHolderRetain set to true to allow the Resource Holder to retain ownership, or - * false to allow the Resource Challenger to acquire the resource. If not explicitly set, - * resourceHolderRetain is set to false. + * @param clientId The client id used to set ownership of resource in case of resource + * challenger situation. + * @param enabled Set to {@code true} to allow the Resource Holder to retain ownership, + * or false to allow the Resource Challenger to acquire the resource. + * If not explicitly set, enabled is set to {@code false}. */ - void setResourceHolderRetain(int clientId, boolean resourceHolderRetain); + void setResourceOwnershipRetention(int clientId, boolean enabled); /* * This API is used by the Tuner framework to request a frontend from the TunerHAL. diff --git a/native/android/OWNERS b/native/android/OWNERS index 9a3527da9623..f0db2ea236ea 100644 --- a/native/android/OWNERS +++ b/native/android/OWNERS @@ -31,3 +31,4 @@ per-file input.cpp = file:/INPUT_OWNERS # PerformanceHint per-file performance_hint.cpp = file:/ADPF_OWNERS +per-file thermal.cpp = file:/ADPF_OWNERS diff --git a/packages/SettingsProvider/res/xml/bookmarks.xml b/packages/SettingsProvider/res/xml/bookmarks.xml index cb38b53c1cbc..645b275e2af5 100644 --- a/packages/SettingsProvider/res/xml/bookmarks.xml +++ b/packages/SettingsProvider/res/xml/bookmarks.xml @@ -20,10 +20,14 @@ Typical shortcuts (not necessarily defined here): 'b': Browser - 'p': Contacts + 'c': Contacts 'e': Email - 'c': Calendar + 'g': GMail + 'k': Calendar 'm': Maps + 'p': Music + 's': SMS + 't': Talk 'u': Calculator 'y': YouTube --> @@ -36,17 +40,23 @@ shortcut="b" /> <bookmark category="android.intent.category.APP_CONTACTS" - shortcut="p" /> + shortcut="c" /> <bookmark category="android.intent.category.APP_EMAIL" shortcut="e" /> <bookmark category="android.intent.category.APP_CALENDAR" - shortcut="c" /> + shortcut="k" /> <bookmark category="android.intent.category.APP_MAPS" shortcut="m" /> <bookmark + category="android.intent.category.APP_MUSIC" + shortcut="p" /> + <bookmark + role="android.app.role.SMS" + shortcut="s" /> + <bookmark category="android.intent.category.APP_CALCULATOR" shortcut="u" /> </bookmarks> diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index e03afecc4240..0ec5571a7b8f 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -990,9 +990,6 @@ <uses-permission android:name="android.permission.health.READ_SKIN_TEMPERATURE" android:featureFlag="android.permission.flags.replace_body_sensor_permission_enabled"/> - <!-- Permissions required for CTS test - BugreportManagerTest --> - <uses-permission android:name="android.permission.CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT" /> - <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" 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 a1f0c146c507..38f09988e7a7 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -867,9 +867,6 @@ constructor( ) { // Raise closing task to "above" layer so it isn't covered. t.setLayer(target.leash, aboveLayers - i) - } else if (TransitionUtil.isOpeningType(change.mode)) { - // Put into the "below" layer space. - t.setLayer(target.leash, belowLayers - i) } } else if (TransitionInfo.isIndependent(change, info)) { // Root tasks @@ -1150,7 +1147,7 @@ constructor( // If a [controller.windowAnimatorState] exists, treat this like a takeover. takeOverAnimationInternal( window, - startWindowState = null, + startWindowStates = null, startTransaction = null, callback, ) @@ -1165,23 +1162,22 @@ constructor( callback: IRemoteAnimationFinishedCallback?, ) { val window = setUpAnimation(apps, callback) ?: return - val startWindowState = startWindowStates[apps!!.indexOf(window)] - takeOverAnimationInternal(window, startWindowState, startTransaction, callback) + takeOverAnimationInternal(window, startWindowStates, startTransaction, callback) } private fun takeOverAnimationInternal( window: RemoteAnimationTarget, - startWindowState: WindowAnimationState?, + startWindowStates: Array<WindowAnimationState>?, startTransaction: SurfaceControl.Transaction?, callback: IRemoteAnimationFinishedCallback?, ) { val useSpring = - !controller.isLaunching && startWindowState != null && startTransaction != null + !controller.isLaunching && startWindowStates != null && startTransaction != null startAnimation( window, navigationBar = null, useSpring, - startWindowState, + startWindowStates, startTransaction, callback, ) @@ -1291,7 +1287,7 @@ constructor( window: RemoteAnimationTarget, navigationBar: RemoteAnimationTarget? = null, useSpring: Boolean = false, - startingWindowState: WindowAnimationState? = null, + startingWindowStates: Array<WindowAnimationState>? = null, startTransaction: SurfaceControl.Transaction? = null, iCallback: IRemoteAnimationFinishedCallback? = null, ) { @@ -1337,7 +1333,6 @@ constructor( val isExpandingFullyAbove = transitionAnimator.isExpandingFullyAbove(controller.transitionContainer, endState) - val windowState = startingWindowState ?: controller.windowAnimatorState // We animate the opening window and delegate the view expansion to [this.controller]. val delegate = this.controller @@ -1360,6 +1355,18 @@ constructor( } } + // The states are sorted matching the changes inside the transition info. + // Using this info, the RemoteAnimationTargets are created, with their + // prefixOrderIndex fields in reverse order to that of changes. To extract + // the right state, we need to invert again. + val windowState = + if (startingWindowStates != null) { + startingWindowStates[ + startingWindowStates.size - window.prefixOrderIndex] + } else { + controller.windowAnimatorState + } + // TODO(b/323863002): use the timestamp and velocity to update the initial // position. val bounds = windowState?.bounds @@ -1448,6 +1455,12 @@ constructor( delegate.onTransitionAnimationProgress(state, progress, linearProgress) } } + val windowState = + if (startingWindowStates != null) { + startingWindowStates[startingWindowStates.size - window.prefixOrderIndex] + } else { + controller.windowAnimatorState + } val velocityPxPerS = if (longLivedReturnAnimationsEnabled() && windowState?.velocityPxPerMs != null) { val xVelocityPxPerS = windowState.velocityPxPerMs.x * 1000 @@ -1466,7 +1479,6 @@ constructor( fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow, drawHole = !controller.isBelowAnimatingWindow, startVelocity = velocityPxPerS, - startFrameTime = windowState?.timestamp ?: -1, ) } 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 4e889e946a5f..e2bc4095e1b5 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt @@ -27,8 +27,6 @@ import android.graphics.drawable.GradientDrawable import android.util.FloatProperty import android.util.Log import android.util.MathUtils -import android.util.TimeUtils -import android.view.Choreographer import android.view.View import android.view.ViewGroup import android.view.ViewGroupOverlay @@ -368,7 +366,6 @@ class TransitionAnimator( @get:VisibleForTesting val springY: SpringAnimation, @get:VisibleForTesting val springScale: SpringAnimation, private val springState: SpringState, - private val startFrameTime: Long, private val onAnimationStart: Runnable, ) : Animation { @get:VisibleForTesting @@ -377,42 +374,6 @@ class TransitionAnimator( override fun start() { onAnimationStart.run() - - // If no start frame time is provided, we start the springs normally. - if (startFrameTime < 0) { - startSprings() - return - } - - // This function is not guaranteed to be called inside a frame. We try to access the - // frame time immediately, but if we're not inside a frame this will throw an exception. - // We must then post a callback to be run at the beginning of the next frame. - try { - initAndStartSprings(Choreographer.getInstance().frameTime) - } catch (_: IllegalStateException) { - Choreographer.getInstance().postFrameCallback { frameTimeNanos -> - initAndStartSprings(frameTimeNanos / TimeUtils.NANOS_PER_MS) - } - } - } - - private fun initAndStartSprings(frameTime: Long) { - // Initialize the spring as if it had started at the time that its start state - // was created. - springX.doAnimationFrame(startFrameTime) - springY.doAnimationFrame(startFrameTime) - springScale.doAnimationFrame(startFrameTime) - // Move the spring time forward to the current frame, so it updates its internal state - // following the initial momentum over the elapsed time. - springX.doAnimationFrame(frameTime) - springY.doAnimationFrame(frameTime) - springScale.doAnimationFrame(frameTime) - // Actually start the spring. We do this after the previous calls because the framework - // doesn't like it when you call doAnimationFrame() after start() with an earlier time. - startSprings() - } - - private fun startSprings() { springX.start() springY.start() springScale.start() @@ -510,9 +471,7 @@ class TransitionAnimator( * is true. * * If [startVelocity] (expressed in pixels per second) is not null, a multi-spring animation - * using it for the initial momentum will be used instead of the default interpolators. In this - * case, [startFrameTime] (if non-negative) represents the frame time at which the springs - * should be started. + * using it for the initial momentum will be used instead of the default interpolators. */ fun startAnimation( controller: Controller, @@ -521,7 +480,6 @@ class TransitionAnimator( fadeWindowBackgroundLayer: Boolean = true, drawHole: Boolean = false, startVelocity: PointF? = null, - startFrameTime: Long = -1, ): Animation { if (!controller.isLaunching) assertReturnAnimations() if (startVelocity != null) assertLongLivedReturnAnimations() @@ -544,7 +502,6 @@ class TransitionAnimator( fadeWindowBackgroundLayer, drawHole, startVelocity, - startFrameTime, ) .apply { start() } } @@ -558,7 +515,6 @@ class TransitionAnimator( fadeWindowBackgroundLayer: Boolean = true, drawHole: Boolean = false, startVelocity: PointF? = null, - startFrameTime: Long = -1, ): Animation { val transitionContainer = controller.transitionContainer val transitionContainerOverlay = transitionContainer.overlay @@ -581,7 +537,6 @@ class TransitionAnimator( startState, endState, startVelocity, - startFrameTime, windowBackgroundLayer, transitionContainer, transitionContainerOverlay, @@ -767,7 +722,6 @@ class TransitionAnimator( startState: State, endState: State, startVelocity: PointF, - startFrameTime: Long, windowBackgroundLayer: GradientDrawable, transitionContainer: View, transitionContainerOverlay: ViewGroupOverlay, @@ -958,7 +912,7 @@ class TransitionAnimator( } } - return MultiSpringAnimation(springX, springY, springScale, springState, startFrameTime) { + return MultiSpringAnimation(springX, springY, springScale, springState) { onAnimationStart( controller, isExpandingFullyAbove, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt new file mode 100644 index 000000000000..e3310780afd7 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt @@ -0,0 +1,234 @@ +/* + * 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.communal.ui.compose + +import android.content.res.Configuration +import androidx.compose.foundation.OverscrollEffect +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.gestures.ScrollableDefaults +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.rememberOverscrollEffect +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.coerceAtMost +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.times + +/** + * Renders a responsive [LazyHorizontalGrid] with dynamic columns and rows. Each cell will maintain + * the specified aspect ratio, but is otherwise resizeable in order to best fill the available + * space. + */ +@Composable +fun ResponsiveLazyHorizontalGrid( + cellAspectRatio: Float, + modifier: Modifier = Modifier, + state: LazyGridState = rememberLazyGridState(), + minContentPadding: PaddingValues = PaddingValues(0.dp), + minHorizontalArrangement: Dp = 0.dp, + minVerticalArrangement: Dp = 0.dp, + flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), + userScrollEnabled: Boolean = true, + overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(), + content: LazyGridScope.(sizeInfo: SizeInfo) -> Unit, +) { + check(cellAspectRatio > 0f) { "Aspect ratio must be greater than 0, but was $cellAspectRatio" } + check(minHorizontalArrangement.value >= 0f && minVerticalArrangement.value >= 0f) { + "Horizontal and vertical arrangements must be non-negative, but were " + + "$minHorizontalArrangement and $minVerticalArrangement, respectively." + } + BoxWithConstraints(modifier) { + val gridSize = rememberGridSize(maxWidth = maxWidth, maxHeight = maxHeight) + val layoutDirection = LocalLayoutDirection.current + + val minStartPadding = minContentPadding.calculateStartPadding(layoutDirection) + val minEndPadding = minContentPadding.calculateEndPadding(layoutDirection) + val minTopPadding = minContentPadding.calculateTopPadding() + val minBottomPadding = minContentPadding.calculateBottomPadding() + val minHorizontalPadding = minStartPadding + minEndPadding + val minVerticalPadding = minTopPadding + minBottomPadding + + // Determine the maximum allowed cell width and height based on the available width and + // height, and the desired number of columns and rows. + val maxCellWidth = + calculateCellSize( + availableSpace = maxWidth, + padding = minHorizontalPadding, + numCells = gridSize.width, + cellSpacing = minHorizontalArrangement, + ) + val maxCellHeight = + calculateCellSize( + availableSpace = maxHeight, + padding = minVerticalPadding, + numCells = gridSize.height, + cellSpacing = minVerticalArrangement, + ) + + // Constrain the max size to the desired aspect ratio. + val finalSize = + calculateClosestSize( + maxWidth = maxCellWidth, + maxHeight = maxCellHeight, + aspectRatio = cellAspectRatio, + ) + + // Determine how much space in each dimension we've used up, and how much we have left as + // extra space. Distribute the extra space evenly along the content padding. + val usedWidth = + calculateUsedSpace( + cellSize = finalSize.width, + numCells = gridSize.width, + padding = minHorizontalPadding, + cellSpacing = minHorizontalArrangement, + ) + .coerceAtMost(maxWidth) + val usedHeight = + calculateUsedSpace( + cellSize = finalSize.height, + numCells = gridSize.height, + padding = minVerticalPadding, + cellSpacing = minVerticalArrangement, + ) + .coerceAtMost(maxHeight) + val extraWidth = maxWidth - usedWidth + val extraHeight = maxHeight - usedHeight + + val finalContentPadding = + PaddingValues( + start = minStartPadding + extraWidth / 2, + end = minEndPadding + extraWidth / 2, + top = minTopPadding + extraHeight / 2, + bottom = minBottomPadding + extraHeight / 2, + ) + + LazyHorizontalGrid( + rows = GridCells.Fixed(gridSize.height), + modifier = Modifier.fillMaxSize(), + state = state, + contentPadding = finalContentPadding, + horizontalArrangement = Arrangement.spacedBy(minHorizontalArrangement), + verticalArrangement = Arrangement.spacedBy(minVerticalArrangement), + flingBehavior = flingBehavior, + userScrollEnabled = userScrollEnabled, + overscrollEffect = overscrollEffect, + ) { + content( + SizeInfo( + cellSize = finalSize, + contentPadding = finalContentPadding, + horizontalArrangement = minHorizontalArrangement, + verticalArrangement = minVerticalArrangement, + maxHeight = maxHeight, + ) + ) + } + } +} + +private fun calculateCellSize(availableSpace: Dp, padding: Dp, numCells: Int, cellSpacing: Dp): Dp = + (availableSpace - padding - cellSpacing * (numCells - 1)) / numCells + +private fun calculateUsedSpace(cellSize: Dp, numCells: Int, padding: Dp, cellSpacing: Dp): Dp = + cellSize * numCells + padding + (numCells - 1) * cellSpacing + +private fun calculateClosestSize(maxWidth: Dp, maxHeight: Dp, aspectRatio: Float): DpSize { + return if (maxWidth / maxHeight > aspectRatio) { + // Target is too wide, shrink width + DpSize(maxHeight * aspectRatio, maxHeight) + } else { + // Target is too tall, shrink height + DpSize(maxWidth, maxWidth / aspectRatio) + } +} + +/** + * Provides size info of the responsive grid, since the size is dynamic. + * + * @property cellSize The size of each cell in the grid. + * @property contentPadding The final content padding of the grid. + * @property horizontalArrangement The space between columns in the grid. + * @property verticalArrangement The space between rows in the grid. + * @property availableHeight The maximum height an item in the grid may occupy. + */ +data class SizeInfo( + val cellSize: DpSize, + val contentPadding: PaddingValues, + val horizontalArrangement: Dp, + val verticalArrangement: Dp, + private val maxHeight: Dp, +) { + val availableHeight: Dp + get() = + maxHeight - + contentPadding.calculateBottomPadding() - + contentPadding.calculateTopPadding() +} + +@Composable +private fun rememberGridSize(maxWidth: Dp, maxHeight: Dp): IntSize { + val configuration = LocalConfiguration.current + val orientation = configuration.orientation + + return remember(orientation, maxWidth, maxHeight) { + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + IntSize( + width = calculateNumCellsWidth(maxWidth), + height = calculateNumCellsHeight(maxHeight), + ) + } else { + // In landscape we invert the rows/columns to ensure we match the same area as portrait. + // This keeps the number of elements in the grid consistent when changing orientation. + IntSize( + width = calculateNumCellsHeight(maxWidth), + height = calculateNumCellsWidth(maxHeight), + ) + } + } +} + +private fun calculateNumCellsWidth(width: Dp) = + // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes + when { + width >= 840.dp -> 3 + width >= 600.dp -> 2 + else -> 1 + } + +private fun calculateNumCellsHeight(height: Dp) = + // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes + when { + height >= 900.dp -> 3 + height >= 480.dp -> 2 + else -> 1 + } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index a266e7eb44a1..c3dc84d0a12c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -39,6 +39,7 @@ import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout +import com.android.compose.animation.scene.SceneTransitions import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.observableTransitionState @@ -78,6 +79,7 @@ fun SceneContainer( sceneByKey: Map<SceneKey, Scene>, overlayByKey: Map<OverlayKey, Overlay>, initialSceneKey: SceneKey, + sceneTransitions: SceneTransitions, dataSourceDelegator: SceneDataSourceDelegator, qsSceneAdapter: Provider<QSSceneAdapter>, modifier: Modifier = Modifier, @@ -87,7 +89,7 @@ fun SceneContainer( MutableSceneTransitionLayoutState( initialScene = initialSceneKey, canChangeScene = { toScene -> viewModel.canChangeScene(toScene) }, - transitions = SceneContainerTransitions, + transitions = sceneTransitions, ) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index eb2a01632095..e819bfd18578 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -53,10 +53,10 @@ import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transformation.CustomPropertyTransformation import com.android.compose.animation.scene.transformation.InterpolatedPropertyTransformation import com.android.compose.animation.scene.transformation.PropertyTransformation -import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.drawInContainer +import com.android.compose.ui.util.IntIndexedMap import com.android.compose.ui.util.lerp import kotlin.math.roundToInt import kotlinx.coroutines.launch @@ -70,6 +70,14 @@ internal class Element(val key: ElementKey) { val stateByContent = SnapshotStateMap<ContentKey, State>() /** + * A sorted map of nesting depth (key) to content key (value). For shared elements it is used to + * determine which content this element should be rendered by. The nesting depth refers to the + * number of STLs nested within each other, starting at 0 for the parent STL and increasing by + * one for each nested [NestedSceneTransitionLayout]. + */ + val renderAuthority = IntIndexedMap<ContentKey>() + + /** * The last transition that was used when computing the state (size, position and alpha) of this * element in any content, or `null` if it was last laid out when idle. */ @@ -232,9 +240,8 @@ internal class ElementNode( private val element: Element get() = _element!! - private var _stateInContent: Element.State? = null private val stateInContent: Element.State - get() = _stateInContent!! + get() = element.stateByContent.getValue(content.key) override val traverseKey: Any = ElementTraverseKey @@ -248,9 +255,13 @@ internal class ElementNode( val element = layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it } _element = element - _stateInContent = - element.stateByContent[content.key] - ?: Element.State(content.key).also { element.stateByContent[content.key] = it } + addToRenderAuthority(element) + if (!element.stateByContent.contains(content.key)) { + val elementState = Element.State(content.key) + element.stateByContent[content.key] = elementState + + layoutImpl.ancestorContentKeys.forEach { element.stateByContent[it] = elementState } + } } private fun addNodeToContentState() { @@ -272,8 +283,20 @@ internal class ElementNode( removeNodeFromContentState() maybePruneMaps(layoutImpl, element, stateInContent) + removeFromRenderAuthority() _element = null - _stateInContent = null + } + + private fun addToRenderAuthority(element: Element) { + val nestingDepth = layoutImpl.ancestorContentKeys.size + element.renderAuthority[nestingDepth] = content.key + } + + private fun removeFromRenderAuthority() { + val nestingDepth = layoutImpl.ancestorContentKeys.size + if (element.renderAuthority[nestingDepth] == content.key) { + element.renderAuthority.remove(nestingDepth) + } } private fun removeNodeFromContentState() { @@ -346,15 +369,17 @@ internal class ElementNode( val elementState = elementState(layoutImpl, element, currentTransitionStates) if (elementState == null) { // If the element is not part of any transition, place it normally in its idle scene. + // This is the case if for example a transition between two overlays is ongoing where + // sharedElement isn't part of either but the element is still rendered as part of + // the underlying scene that is currently not being transitioned. val currentState = currentTransitionStates.last() - val placeInThisContent = + val shouldPlaceInThisContent = elementContentWhenIdle( layoutImpl, currentState, isInContent = { it in element.stateByContent }, ) == content.key - - return if (placeInThisContent) { + return if (shouldPlaceInThisContent) { placeNormally(measurable, constraints) } else { doNotPlace(measurable, constraints) @@ -536,7 +561,9 @@ internal class ElementNode( stateInContent.clearLastPlacementValues() traverseDescendants(ElementTraverseKey) { node -> - (node as ElementNode)._stateInContent?.clearLastPlacementValues() + if ((node as ElementNode)._element != null) { + node.stateInContent.clearLastPlacementValues() + } TraversableNode.Companion.TraverseDescendantsAction.ContinueTraversal } } @@ -569,22 +596,30 @@ internal class ElementNode( element: Element, stateInContent: Element.State, ) { - // If element is not composed in this content anymore, remove the content values. This - // works because [onAttach] is called before [onDetach], so if an element is moved from - // the UI tree we will first add the new code location then remove the old one. - if ( - stateInContent.nodes.isEmpty() && - element.stateByContent[stateInContent.content] == stateInContent - ) { - element.stateByContent.remove(stateInContent.content) - - // If the element is not composed in any content, remove it from the elements map. + fun pruneForContent(contentKey: ContentKey) { + // If element is not composed in this content anymore, remove the content values. + // This works because [onAttach] is called before [onDetach], so if an element is + // moved from the UI tree we will first add the new code location then remove the + // old one. if ( - element.stateByContent.isEmpty() && layoutImpl.elements[element.key] == element + stateInContent.nodes.isEmpty() && + element.stateByContent[contentKey] == stateInContent ) { - layoutImpl.elements.remove(element.key) + element.stateByContent.remove(contentKey) + + // If the element is not composed in any content, remove it from the elements + // map. + if ( + element.stateByContent.isEmpty() && + layoutImpl.elements[element.key] == element + ) { + layoutImpl.elements.remove(element.key) + } } } + + pruneForContent(stateInContent.content) + layoutImpl.ancestorContentKeys.forEach { content -> pruneForContent(content) } } } } @@ -890,12 +925,13 @@ private fun shouldPlaceElement( val transition = when (elementState) { is TransitionState.Idle -> { - return content == - elementContentWhenIdle( - layoutImpl, - elementState, - isInContent = { it in element.stateByContent }, - ) + return element.shouldBeRenderedBy(content) && + content == + elementContentWhenIdle( + layoutImpl, + elementState, + isInContent = { it in element.stateByContent }, + ) } is TransitionState.Transition -> elementState } @@ -925,76 +961,7 @@ private fun shouldPlaceElement( return true } - return shouldPlaceOrComposeSharedElement( - layoutImpl, - content, - element.key, - transition, - isInContent = { it in element.stateByContent }, - ) -} - -internal inline fun shouldPlaceOrComposeSharedElement( - layoutImpl: SceneTransitionLayoutImpl, - content: ContentKey, - element: ElementKey, - transition: TransitionState.Transition, - isInContent: (ContentKey) -> Boolean, -): Boolean { - val overscrollContent = transition.currentOverscrollSpec?.content - if (overscrollContent != null) { - return when (transition) { - // If we are overscrolling between scenes, only place/compose the element in the - // overscrolling scene. - is TransitionState.Transition.ChangeScene -> content == overscrollContent - - // If we are overscrolling an overlay, place/compose the element if [content] is the - // overscrolling content or if [content] is the current scene and the overscrolling - // overlay does not contain the element. - is TransitionState.Transition.ReplaceOverlay, - is TransitionState.Transition.ShowOrHideOverlay -> - content == overscrollContent || - (content == transition.currentScene && !isInContent(overscrollContent)) - } - } - - val scenePicker = element.contentPicker - val pickedScene = - scenePicker.contentDuringTransition( - element = element, - transition = transition, - fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex, - toContentZIndex = layoutImpl.content(transition.toContent).zIndex, - ) - - return pickedScene == content -} - -private fun isSharedElementEnabled( - element: ElementKey, - transition: TransitionState.Transition, -): Boolean { - return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true -} - -internal fun sharedElementTransformation( - element: ElementKey, - transition: TransitionState.Transition, -): TransformationWithRange<SharedElementTransformation>? { - val transformationSpec = transition.transformationSpec - val sharedInFromContent = - transformationSpec.transformations(element, transition.fromContent).shared - val sharedInToContent = transformationSpec.transformations(element, transition.toContent).shared - - // The sharedElement() transformation must either be null or be the same in both contents. - if (sharedInFromContent != sharedInToContent) { - error( - "Different sharedElement() transformations matched $element " + - "(from=$sharedInFromContent to=$sharedInToContent)" - ) - } - - return sharedInFromContent + return shouldPlaceSharedElement(layoutImpl, content, element.key, transition) } /** diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt index 509a16c5a704..17510c732e65 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -196,18 +196,54 @@ private fun shouldComposeMovableElement( is TransitionState.Transition -> { // During transitions, always compose movable elements in the scene picked by their // content picker. - val contents = element.contentPicker.contents - shouldPlaceOrComposeSharedElement( + shouldComposeMoveableElement( layoutImpl, content, element, elementState, - isInContent = { contents.contains(it) }, + element.contentPicker.contents, ) } } } +private fun shouldComposeMoveableElement( + layoutImpl: SceneTransitionLayoutImpl, + content: ContentKey, + elementKey: ElementKey, + transition: TransitionState.Transition, + containingContents: Set<ContentKey>, +): Boolean { + val overscrollContent = transition.currentOverscrollSpec?.content + if (overscrollContent != null) { + return when (transition) { + // If we are overscrolling between scenes, only place/compose the element in the + // overscrolling scene. + is TransitionState.Transition.ChangeScene -> content == overscrollContent + + // If we are overscrolling an overlay, place/compose the element if [content] is the + // overscrolling content or if [content] is the current scene and the overscrolling + // overlay does not contain the element. + is TransitionState.Transition.ReplaceOverlay, + is TransitionState.Transition.ShowOrHideOverlay -> + content == overscrollContent || + (content == transition.currentScene && + !containingContents.contains(overscrollContent)) + } + } + + val scenePicker = elementKey.contentPicker + val pickedScene = + scenePicker.contentDuringTransition( + element = elementKey, + transition = transition, + fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex, + toContentZIndex = layoutImpl.content(transition.toContent).zIndex, + ) + + return pickedScene == content +} + private fun movableElementState( element: MovableElementKey, transitionStates: List<TransitionState>, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index 59ac68bd0299..501fbb034c51 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -132,11 +132,7 @@ internal class MultiPointerDraggableNode( var onFirstPointerDown: () -> Unit, swipeDetector: SwipeDetector = DefaultSwipeDetector, private val dispatcher: NestedScrollDispatcher, -) : - DelegatingNode(), - PointerInputModifierNode, - CompositionLocalConsumerModifierNode, - SpaceVectorConverter { +) : DelegatingNode(), PointerInputModifierNode, CompositionLocalConsumerModifierNode { private val pointerTracker = delegate(SuspendingPointerInputModifierNode { pointerTracker() }) private val pointerInput = delegate(SuspendingPointerInputModifierNode { pointerInput() }) private val velocityTracker = VelocityTracker() @@ -151,13 +147,13 @@ internal class MultiPointerDraggableNode( private var converter = SpaceVectorConverter(orientation) - override fun Offset.toFloat(): Float = with(converter) { this@toFloat.toFloat() } + fun Offset.toFloat(): Float = with(converter) { this@toFloat.toFloat() } - override fun Velocity.toFloat(): Float = with(converter) { this@toFloat.toFloat() } + fun Velocity.toFloat(): Float = with(converter) { this@toFloat.toFloat() } - override fun Float.toOffset(): Offset = with(converter) { this@toOffset.toOffset() } + fun Float.toOffset(): Offset = with(converter) { this@toOffset.toOffset() } - override fun Float.toVelocity(): Velocity = with(converter) { this@toVelocity.toVelocity() } + fun Float.toVelocity(): Velocity = with(converter) { this@toVelocity.toVelocity() } var orientation: Orientation = orientation set(value) { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index d3ddb5003469..759100b15a56 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.pointer.PointerType +import androidx.compose.ui.layout.LookaheadScope import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Density @@ -68,7 +69,7 @@ fun SceneTransitionLayout( swipeDetector, transitionInterceptionThreshold, onLayoutImpl = null, - builder, + builder = builder, ) } @@ -261,8 +262,21 @@ interface BaseContentScope : ElementStateScope { * lists keep a constant size during transitions even if its elements are growing/shrinking. */ fun Modifier.noResizeDuringTransitions(): Modifier + + /** + * A [NestedSceneTransitionLayout] will share its elements with its ancestor STLs therefore + * enabling sharedElement transitions between them. + */ + // TODO(b/380070506): Add more parameters when default params are supported in Kotlin 2.0.21 + @Composable + fun NestedSceneTransitionLayout( + state: SceneTransitionLayoutState, + modifier: Modifier, + builder: SceneTransitionLayoutScope.() -> Unit, + ) } +@Deprecated("Use ContentScope instead", ReplaceWith("ContentScope")) typealias SceneScope = ContentScope @Stable @@ -677,6 +691,9 @@ internal fun SceneTransitionLayoutForTesting( swipeDetector: SwipeDetector = DefaultSwipeDetector, transitionInterceptionThreshold: Float = 0f, onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null, + sharedElementMap: MutableMap<ElementKey, Element> = remember { mutableMapOf() }, + ancestorContentKeys: List<ContentKey> = emptyList(), + lookaheadScope: LookaheadScope? = null, builder: SceneTransitionLayoutScope.() -> Unit, ) { val density = LocalDensity.current @@ -691,6 +708,9 @@ internal fun SceneTransitionLayoutForTesting( transitionInterceptionThreshold = transitionInterceptionThreshold, builder = builder, animationScope = animationScope, + elements = sharedElementMap, + ancestorContentKeys = ancestorContentKeys, + lookaheadScope = lookaheadScope, ) .also { onLayoutImpl?.invoke(it) } } @@ -706,6 +726,24 @@ internal fun SceneTransitionLayoutForTesting( " that was used when creating it, which is not supported" ) } + if (layoutImpl.elements != sharedElementMap) { + error( + "This SceneTransitionLayout was bound to a different elements map that was used " + + "when creating it, which is not supported" + ) + } + if (layoutImpl.ancestorContentKeys != ancestorContentKeys) { + error( + "This SceneTransitionLayout was bound to a different ancestorContents that was " + + "used when creating it, which is not supported" + ) + } + if (lookaheadScope != null && layoutImpl.lookaheadScope != lookaheadScope) { + error( + "This SceneTransitionLayout was bound to a different lookaheadScope that was " + + "used when creating it, which is not supported" + ) + } layoutImpl.density = density layoutImpl.layoutDirection = layoutDirection diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index b916b0b45e41..bdc1461f06c9 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -70,7 +70,39 @@ internal class SceneTransitionLayoutImpl( * animations. */ internal val animationScope: CoroutineScope, + + /** + * The map of [Element]s. + * + * Important: [Element]s from this map should never be accessed during composition because the + * Elements are added when the associated Modifier.element() node is attached to the Modifier + * tree, i.e. after composition. + */ + internal val elements: MutableMap<ElementKey, Element> = mutableMapOf(), + + /** + * When this STL is a [NestedSceneTransitionLayout], this is a list of [ContentKey]s of where + * this STL is composed in within its ancestors. + * + * The root STL holds an emptyList. With each nesting level the parent is supposed to add + * exactly one scene to the list, therefore the size of this list is equal to the nesting depth + * of this STL. + * + * This is used to know in which content of the ancestors a sharedElement appears in. + */ + internal val ancestorContentKeys: List<ContentKey> = emptyList(), + lookaheadScope: LookaheadScope? = null, ) { + + /** + * The [LookaheadScope] of this layout, that can be used to compute offsets relative to the + * layout. For [NestedSceneTransitionLayout]s this scope is the scope of the root STL, such that + * offset computations can be shared among all children. + */ + private var _lookaheadScope: LookaheadScope? = lookaheadScope + internal val lookaheadScope: LookaheadScope + get() = _lookaheadScope!! + /** * The map of [Scene]s. * @@ -89,15 +121,6 @@ internal class SceneTransitionLayoutImpl( get() = _overlays ?: SnapshotStateMap<OverlayKey, Overlay>().also { _overlays = it } /** - * The map of [Element]s. - * - * Important: [Element]s from this map should never be accessed during composition because the - * Elements are added when the associated Modifier.element() node is attached to the Modifier - * tree, i.e. after composition. - */ - internal val elements = mutableMapOf<ElementKey, Element>() - - /** * The map of contents of movable elements. * * Note that given that this map is mutated directly during a composition, it has to be a @@ -138,13 +161,6 @@ internal class SceneTransitionLayoutImpl( _userActionDistanceScope = it } - /** - * The [LookaheadScope] of this layout, that can be used to compute offsets relative to the - * layout. - */ - internal lateinit var lookaheadScope: LookaheadScope - private set - internal var lastSize: IntSize = IntSize.Zero init { @@ -347,7 +363,12 @@ internal class SceneTransitionLayoutImpl( .then(LayoutElement(layoutImpl = this)) ) { LookaheadScope { - lookaheadScope = this + if (_lookaheadScope == null) { + // We can't init this in a SideEffect as other NestedSTLs are already calling + // this during composition. However, when composition is canceled + // SceneTransitionLayoutImpl is discarded as well. So it's fine to do this here. + _lookaheadScope = this + } BackHandler() Scenes() diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt new file mode 100644 index 000000000000..599a152a23bd --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt @@ -0,0 +1,113 @@ +/* + * 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.compose.animation.scene + +import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.animation.scene.transformation.SharedElementTransformation +import com.android.compose.animation.scene.transformation.TransformationWithRange + +/** + * Whether this element should be rendered by the given [content]. This method returns true only for + * exactly one content at any given time. + */ +internal fun Element.shouldBeRenderedBy(content: ContentKey): Boolean { + // The current strategy is that always the content with the lowest nestingDepth has authority. + // This content is supposed to render the shared element because this is also the level at which + // the transition is running. If the [renderAuthority.size] is 1 it means that that this element + // is currently composed only in one nesting level, which means that the render authority + // is determined by "classic" shared element code. + return renderAuthority.size == 1 || renderAuthority.first() == content +} + +/** + * Whether this element is currently composed in multiple [SceneTransitionLayout]s. + * + * Note: Shared elements across [NestedSceneTransitionLayout]s side-by-side are not supported. + */ +internal fun Element.isPresentInMultipleStls(): Boolean { + return renderAuthority.size > 1 +} + +internal fun shouldPlaceSharedElement( + layoutImpl: SceneTransitionLayoutImpl, + content: ContentKey, + elementKey: ElementKey, + transition: TransitionState.Transition, +): Boolean { + val element = layoutImpl.elements.getValue(elementKey) + if (element.isPresentInMultipleStls()) { + // If the element is present in multiple STLs we require the highest STL to render it and + // we don't want contentPicker to potentially return false for the highest STL. + return element.shouldBeRenderedBy(content) + } + + val overscrollContent = transition.currentOverscrollSpec?.content + if (overscrollContent != null) { + return when (transition) { + // If we are overscrolling between scenes, only place/compose the element in the + // overscrolling scene. + is TransitionState.Transition.ChangeScene -> content == overscrollContent + + // If we are overscrolling an overlay, place/compose the element if [content] is the + // overscrolling content or if [content] is the current scene and the overscrolling + // overlay does not contain the element. + is TransitionState.Transition.ReplaceOverlay, + is TransitionState.Transition.ShowOrHideOverlay -> + content == overscrollContent || + (content == transition.currentScene && + overscrollContent !in element.stateByContent) + } + } + + val scenePicker = elementKey.contentPicker + val pickedScene = + scenePicker.contentDuringTransition( + element = elementKey, + transition = transition, + fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex, + toContentZIndex = layoutImpl.content(transition.toContent).zIndex, + ) + + return pickedScene == content +} + +internal fun isSharedElementEnabled( + element: ElementKey, + transition: TransitionState.Transition, +): Boolean { + return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true +} + +internal fun sharedElementTransformation( + element: ElementKey, + transition: TransitionState.Transition, +): TransformationWithRange<SharedElementTransformation>? { + val transformationSpec = transition.transformationSpec + val sharedInFromContent = + transformationSpec.transformations(element, transition.fromContent).shared + val sharedInToContent = transformationSpec.transformations(element, transition.toContent).shared + + // The sharedElement() transformation must either be null or be the same in both contents. + if (sharedInFromContent != sharedInToContent) { + error( + "Different sharedElement() transformations matched $element " + + "(from=$sharedInFromContent to=$sharedInToContent)" + ) + } + + return sharedInFromContent +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt index 255a16c6de6b..8c4cd8c93b87 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.layout.approachLayout @@ -41,7 +42,9 @@ import com.android.compose.animation.scene.MovableElement import com.android.compose.animation.scene.MovableElementContentScope import com.android.compose.animation.scene.MovableElementKey import com.android.compose.animation.scene.NestedScrollBehavior +import com.android.compose.animation.scene.SceneTransitionLayoutForTesting import com.android.compose.animation.scene.SceneTransitionLayoutImpl +import com.android.compose.animation.scene.SceneTransitionLayoutScope import com.android.compose.animation.scene.SceneTransitionLayoutState import com.android.compose.animation.scene.SharedValueType import com.android.compose.animation.scene.UserAction @@ -175,4 +178,24 @@ internal class ContentScopeImpl( override fun Modifier.noResizeDuringTransitions(): Modifier { return noResizeDuringTransitions(layoutState = layoutImpl.state) } + + @Composable + override fun NestedSceneTransitionLayout( + state: SceneTransitionLayoutState, + modifier: Modifier, + builder: SceneTransitionLayoutScope.() -> Unit, + ) { + SceneTransitionLayoutForTesting( + state, + modifier, + onLayoutImpl = null, + builder = builder, + sharedElementMap = layoutImpl.elements, + ancestorContentKeys = + remember(layoutImpl.ancestorContentKeys, contentKey) { + layoutImpl.ancestorContentKeys + contentKey + }, + lookaheadScope = layoutImpl.lookaheadScope, + ) + } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/IntIndexedMap.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/IntIndexedMap.kt new file mode 100644 index 000000000000..1b5341b8048a --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/IntIndexedMap.kt @@ -0,0 +1,73 @@ +/* + * 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.compose.ui.util + +/** + * This is a custom implementation that resembles a SortedMap<Int, T> but is based on a simple + * ArrayList to avoid the allocation overhead and boxing. + * + * It can only hold positive keys and 0 and it is only efficient for small keys (0 - ~100), but + * therefore provides fast operations for small keys. + */ +internal class IntIndexedMap<T> { + private val arrayList = ArrayList<T?>() + private var _size = 0 + val size + get() = _size + + /** Returns the value at [key] or null if the key is not present. */ + operator fun get(key: Int): T? { + if (key < 0 || key >= arrayList.size) return null + return arrayList[key] + } + + /** + * Sets the value at [key] to [value]. If [key] is larger than the current size of the map, this + * operation may take up to O(key) time and space. Therefore this data structure is only + * efficient for small [key] sizes. + */ + operator fun set(key: Int, value: T?) { + if (key < 0) + throw UnsupportedOperationException("This map can only hold positive keys and 0.") + if (key < arrayList.size) { + if (arrayList[key] != null && value == null) _size-- + if (arrayList[key] == null && value != null) _size++ + arrayList[key] = value + } else { + if (value == null) return + while (key > arrayList.size) { + arrayList.add(null) + } + _size++ + arrayList.add(value) + } + } + + /** Remove value at [key] */ + fun remove(key: Int) { + if (key >= arrayList.size) return + this[key] = null + } + + /** Get the [value] with the smallest [key] of the map. */ + fun first(): T { + for (i in 0 until arrayList.size) { + return arrayList[i] ?: continue + } + throw NoSuchElementException("The map is empty.") + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt index f08a18046537..ca50e778d131 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt @@ -18,6 +18,7 @@ package com.android.compose.ui.util import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.Velocity interface SpaceVectorConverter { @@ -25,9 +26,13 @@ interface SpaceVectorConverter { fun Velocity.toFloat(): Float + fun IntOffset.toInt(): Int + fun Float.toOffset(): Offset fun Float.toVelocity(): Velocity + + fun Int.toIntOffset(): IntOffset } fun SpaceVectorConverter(orientation: Orientation) = @@ -36,24 +41,30 @@ fun SpaceVectorConverter(orientation: Orientation) = Orientation.Vertical -> VerticalConverter } -private val HorizontalConverter = - object : SpaceVectorConverter { - override fun Offset.toFloat() = x +private data object HorizontalConverter : SpaceVectorConverter { + override fun Offset.toFloat() = x - override fun Velocity.toFloat() = x + override fun Velocity.toFloat() = x - override fun Float.toOffset() = Offset(this, 0f) + override fun IntOffset.toInt() = x - override fun Float.toVelocity() = Velocity(this, 0f) - } + override fun Float.toOffset() = Offset(this, 0f) -private val VerticalConverter = - object : SpaceVectorConverter { - override fun Offset.toFloat() = y + override fun Float.toVelocity() = Velocity(this, 0f) - override fun Velocity.toFloat() = y + override fun Int.toIntOffset() = IntOffset(this, 0) +} - override fun Float.toOffset() = Offset(0f, this) +private data object VerticalConverter : SpaceVectorConverter { + override fun Offset.toFloat() = y - override fun Float.toVelocity() = Velocity(0f, this) - } + override fun Velocity.toFloat() = y + + override fun IntOffset.toInt() = y + + override fun Float.toOffset() = Offset(0f, this) + + override fun Float.toVelocity() = Velocity(0f, this) + + override fun Int.toIntOffset() = IntOffset(0, this) +} diff --git a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml index 174ad30a8f1d..2b76d7ba267e 100644 --- a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml +++ b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml @@ -18,7 +18,8 @@ package="com.android.compose.animation.scene.tests" > <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> - <application> + <application + android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> <uses-library android:name="android.test.runner" /> </application> diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt new file mode 100644 index 000000000000..c6ef8cff1a66 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2023 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.compose.animation.scene.transformation + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.assertIsNotDisplayed +import androidx.compose.ui.test.assertPositionInRootIsEqualTo +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.AutoTransitionTestAssertionScope +import com.android.compose.animation.scene.ContentScope +import com.android.compose.animation.scene.Default4FrameLinearTransition +import com.android.compose.animation.scene.Edge +import com.android.compose.animation.scene.MutableSceneTransitionLayoutState +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TestElements +import com.android.compose.animation.scene.TestScenes +import com.android.compose.animation.scene.inScene +import com.android.compose.animation.scene.testTransition +import com.android.compose.animation.scene.transitions +import com.android.compose.test.assertSizeIsEqualTo +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class NestedSharedElementTest { + @get:Rule val rule = createComposeRule() + + private object Scenes { + val NestedSceneA = SceneKey("NestedSceneA") + val NestedSceneB = SceneKey("NestedSceneB") + val NestedNestedSceneA = SceneKey("NestedNestedSceneA") + val NestedNestedSceneB = SceneKey("NestedNestedSceneB") + } + + private val elementVariant1 = SharedElement(0.dp, 0.dp, 100.dp, 100.dp, Color.Red) + private val elementVariant2 = SharedElement(40.dp, 80.dp, 60.dp, 20.dp, Color.Blue) + private val elementVariant3 = SharedElement(80.dp, 40.dp, 140.dp, 180.dp, Color.Yellow) + private val elementVariant4 = SharedElement(120.dp, 240.dp, 20.dp, 140.dp, Color.Green) + + private class SharedElement( + val x: Dp, + val y: Dp, + val width: Dp, + val height: Dp, + val color: Color = Color.Black, + val alpha: Float = 0.8f, + ) + + @Composable + private fun ContentScope.SharedElement(element: SharedElement) { + Box(Modifier.fillMaxSize()) { + Box( + Modifier.offset(element.x, element.y) + .element(TestElements.Foo) + .size(element.width, element.height) + .background(element.color) + .alpha(element.alpha) + ) + } + } + + private val contentWithSharedElement: @Composable ContentScope.() -> Unit = { + SharedElement(elementVariant1) + } + + private val nestedState: MutableSceneTransitionLayoutState = + rule.runOnUiThread { + MutableSceneTransitionLayoutState( + Scenes.NestedSceneA, + transitions { + from( + from = Scenes.NestedSceneA, + to = Scenes.NestedSceneB, + builder = Default4FrameLinearTransition, + ) + }, + ) + } + + private val nestedNestedState: MutableSceneTransitionLayoutState = + rule.runOnUiThread { + MutableSceneTransitionLayoutState( + Scenes.NestedNestedSceneA, + transitions { + from( + from = Scenes.NestedNestedSceneA, + to = Scenes.NestedNestedSceneB, + builder = Default4FrameLinearTransition, + ) + }, + ) + } + + private val nestedStlWithSharedElement: @Composable ContentScope.() -> Unit = { + NestedSceneTransitionLayout(nestedState, modifier = Modifier) { + scene(Scenes.NestedSceneA) { SharedElement(elementVariant2) } + scene(Scenes.NestedSceneB) { SharedElement(elementVariant3) } + } + } + + private val nestedNestedStlWithSharedElement: @Composable ContentScope.() -> Unit = { + NestedSceneTransitionLayout(nestedState, modifier = Modifier) { + scene(Scenes.NestedSceneA) { + NestedSceneTransitionLayout(state = nestedNestedState, modifier = Modifier) { + scene(Scenes.NestedNestedSceneA) { SharedElement(elementVariant4) } + scene(Scenes.NestedNestedSceneB) { SharedElement(elementVariant3) } + } + } + scene(Scenes.NestedSceneB) { SharedElement(elementVariant2) } + } + } + + @Test + fun nestedSharedElementTransition_fromNestedSTLtoParentSTL() { + rule.testTransition( + fromSceneContent = nestedStlWithSharedElement, + toSceneContent = contentWithSharedElement, + ) { + before { onElement(TestElements.Foo).assertElementVariant(elementVariant2) } + atAllFrames(4) { + onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed() + + onElement(TestElements.Foo, TestScenes.SceneB) + .assertBetweenElementVariants(elementVariant2, elementVariant1, this) + } + after { onElement(TestElements.Foo).assertElementVariant(elementVariant1) } + } + } + + @Test + fun nestedSharedElementTransition_fromParentSTLtoNestedSTL() { + rule.testTransition( + fromSceneContent = contentWithSharedElement, + toSceneContent = nestedStlWithSharedElement, + ) { + before { onElement(TestElements.Foo).assertElementVariant(elementVariant1) } + atAllFrames(4) { + onElement(TestElements.Foo, TestScenes.SceneB).assertIsNotDisplayed() + + onElement(TestElements.Foo, TestScenes.SceneA) + .assertBetweenElementVariants(elementVariant1, elementVariant2, this) + } + after { onElement(TestElements.Foo).assertElementVariant(elementVariant2) } + } + } + + @Test + fun nestedSharedElementTransition_fromParentSTLtoNestedNestedSTL() { + rule.testTransition( + fromSceneContent = contentWithSharedElement, + toSceneContent = nestedNestedStlWithSharedElement, + ) { + before { onElement(TestElements.Foo).assertElementVariant(elementVariant1) } + atAllFrames(4) { + onElement(TestElements.Foo, TestScenes.SceneB).assertIsNotDisplayed() + + onElement(TestElements.Foo, TestScenes.SceneA) + .assertBetweenElementVariants(elementVariant1, elementVariant4, this) + } + after { onElement(TestElements.Foo).assertElementVariant(elementVariant4) } + } + } + + @Test + fun nestedSharedElementTransition_fromNestedNestedSTLtoNestedSTL() { + rule.testTransition( + fromSceneContent = nestedNestedStlWithSharedElement, + toSceneContent = { Box(modifier = Modifier.fillMaxSize()) }, + changeState = { nestedState.setTargetScene(Scenes.NestedSceneB, this) }, + ) { + before { onElement(TestElements.Foo).assertElementVariant(elementVariant4) } + atAllFrames(4) { + onElement(TestElements.Foo, Scenes.NestedSceneA).assertIsNotDisplayed() + onElement(TestElements.Foo, Scenes.NestedNestedSceneA).assertIsNotDisplayed() + + onElement(TestElements.Foo, Scenes.NestedSceneB) + .assertBetweenElementVariants(elementVariant4, elementVariant2, this) + } + after { onElement(TestElements.Foo).assertElementVariant(elementVariant2) } + } + } + + @Test + fun nestedSharedElement_sharedElementTransitionIsDisabled() { + rule.testTransition( + fromSceneContent = contentWithSharedElement, + toSceneContent = nestedStlWithSharedElement, + transition = { + spec = tween(16 * 4, easing = LinearEasing) + + // Disable the shared element animation. + sharedElement(TestElements.Foo, enabled = false) + + // In SceneA, Foo leaves to the left edge. + translate(TestElements.Foo.inScene(TestScenes.SceneA), Edge.Left, false) + + // We can't reference the element inside the NestedSTL as of today + }, + ) { + before { onElement(TestElements.Foo).assertElementVariant(elementVariant1) } + atAllFrames(4) { + onElement(TestElements.Foo, scene = TestScenes.SceneA) + .assertPositionInRootIsEqualTo( + interpolate(elementVariant1.x, 0.dp), + elementVariant1.y, + ) + .assertSizeIsEqualTo(elementVariant1.width, elementVariant1.height) + } + after { onElement(TestElements.Foo).assertElementVariant(elementVariant2) } + } + } + + @Test + fun nestedSharedElementTransition_transitionInsideNestedStl() { + rule.testTransition( + layoutModifier = Modifier.fillMaxSize(), + fromSceneContent = nestedStlWithSharedElement, + toSceneContent = contentWithSharedElement, + changeState = { nestedState.setTargetScene(Scenes.NestedSceneB, animationScope = this) }, + ) { + before { onElement(TestElements.Foo).assertElementVariant(elementVariant2) } + atAllFrames(4) { + onElement(TestElements.Foo, Scenes.NestedSceneA).assertIsNotDisplayed() + + onElement(TestElements.Foo, scene = Scenes.NestedSceneB) + .assertBetweenElementVariants(elementVariant2, elementVariant3, this) + } + after { + onElement(TestElements.Foo, Scenes.NestedSceneA).assertIsNotDisplayed() + onElement(TestElements.Foo).assertElementVariant(elementVariant3) + } + } + } + + private fun SemanticsNodeInteraction.assertElementVariant(variant: SharedElement) { + assertPositionInRootIsEqualTo(variant.x, variant.y) + assertSizeIsEqualTo(variant.width, variant.height) + } + + private fun SemanticsNodeInteraction.assertBetweenElementVariants( + from: SharedElement, + to: SharedElement, + assertScope: AutoTransitionTestAssertionScope, + ) { + assertPositionInRootIsEqualTo( + assertScope.interpolate(from.x, to.x), + assertScope.interpolate(from.y, to.y), + ) + assertSizeIsEqualTo( + assertScope.interpolate(from.width, to.width), + assertScope.interpolate(from.height, to.height), + ) + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt index 2e3a934c2701..47c10f5ab3a3 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt @@ -62,35 +62,14 @@ class SharedElementTest { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) onElement(TestElements.Foo).assertSizeIsEqualTo(20.dp, 80.dp) } - at(0) { - // Shared elements are by default placed and drawn only in the scene with highest - // zIndex. + atAllFrames(4) { onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed() - - onElement(TestElements.Foo, TestScenes.SceneB) - .assertPositionInRootIsEqualTo(10.dp, 50.dp) - .assertSizeIsEqualTo(20.dp, 80.dp) - } - at(16) { - onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed() - - onElement(TestElements.Foo, TestScenes.SceneB) - .assertPositionInRootIsEqualTo(20.dp, 55.dp) - .assertSizeIsEqualTo(17.5.dp, 70.dp) - } - at(32) { - onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed() - - onElement(TestElements.Foo, TestScenes.SceneB) - .assertPositionInRootIsEqualTo(30.dp, 60.dp) - .assertSizeIsEqualTo(15.dp, 60.dp) - } - at(48) { - onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed() - onElement(TestElements.Foo, TestScenes.SceneB) - .assertPositionInRootIsEqualTo(40.dp, 65.dp) - .assertSizeIsEqualTo(12.5.dp, 50.dp) + .assertPositionInRootIsEqualTo( + interpolate(10.dp, 50.dp), + interpolate(50.dp, 70.dp), + ) + .assertSizeIsEqualTo(interpolate(20.dp, 10.dp), interpolate(80.dp, 40.dp)) } after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 70.dp) @@ -132,29 +111,11 @@ class SharedElementTest { }, ) { before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) } - at(0) { - onElement(TestElements.Foo, scene = TestScenes.SceneA) - .assertPositionInRootIsEqualTo(10.dp, 50.dp) - onElement(TestElements.Foo, scene = TestScenes.SceneB) - .assertPositionInRootIsEqualTo(50.dp, 100.dp) - } - at(16) { - onElement(TestElements.Foo, scene = TestScenes.SceneA) - .assertPositionInRootIsEqualTo(7.5.dp, 50.dp) - onElement(TestElements.Foo, scene = TestScenes.SceneB) - .assertPositionInRootIsEqualTo(50.dp, 90.dp) - } - at(32) { - onElement(TestElements.Foo, scene = TestScenes.SceneA) - .assertPositionInRootIsEqualTo(5.dp, 50.dp) - onElement(TestElements.Foo, scene = TestScenes.SceneB) - .assertPositionInRootIsEqualTo(50.dp, 80.dp) - } - at(48) { + atAllFrames(4) { onElement(TestElements.Foo, scene = TestScenes.SceneA) - .assertPositionInRootIsEqualTo(2.5.dp, 50.dp) + .assertPositionInRootIsEqualTo(interpolate(10.dp, 0.dp), 50.dp) onElement(TestElements.Foo, scene = TestScenes.SceneB) - .assertPositionInRootIsEqualTo(50.dp, 70.dp) + .assertPositionInRootIsEqualTo(50.dp, interpolate(100.dp, 60.dp)) } after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 60.dp) } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/IntIndexMapTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/IntIndexMapTest.kt new file mode 100644 index 000000000000..d7a9b9007be0 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/IntIndexMapTest.kt @@ -0,0 +1,92 @@ +/* + * 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.compose.ui.util + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class IntIndexMapTest { + + @Test + fun testSetGetFirstAndSize() { + val map = IntIndexedMap<String>() + + // Write first element at 10 + map[10] = "1" + assertThat(map[10]).isEqualTo("1") + assertThat(map.size).isEqualTo(1) + assertThat(map.first()).isEqualTo("1") + + // Write same element to same index + map[10] = "1" + assertThat(map[10]).isEqualTo("1") + assertThat(map.size).isEqualTo(1) + + // Writing into larger index + map[12] = "2" + assertThat(map[12]).isEqualTo("2") + assertThat(map.size).isEqualTo(2) + assertThat(map.first()).isEqualTo("1") + + // Overwriting existing index + map[10] = "3" + assertThat(map[10]).isEqualTo("3") + assertThat(map.size).isEqualTo(2) + assertThat(map.first()).isEqualTo("3") + + // Writing into smaller index + map[0] = "4" + assertThat(map[0]).isEqualTo("4") + assert(map.size == 3) + assertThat(map.first()).isEqualTo("4") + + // Writing null into non-null index + map[0] = null + assertThat(map[0]).isEqualTo(null) + assertThat(map.size).isEqualTo(2) + assertThat(map.first()).isEqualTo("3") + + // Writing null into smaller null index + map[1] = null + assertThat(map[1]).isEqualTo(null) + assertThat(map.size).isEqualTo(2) + + // Writing null into larger null index + map[15] = null + assertThat(map[15]).isEqualTo(null) + assertThat(map.size).isEqualTo(2) + + // Remove existing element + map.remove(12) + assertThat(map[12]).isEqualTo(null) + assertThat(map.size).isEqualTo(1) + + // Remove non-existing element + map.remove(17) + assertThat(map[17]).isEqualTo(null) + assertThat(map.size).isEqualTo(1) + + // Remove all elements + assertThat(map.first()).isEqualTo("3") + map.remove(10) + map.remove(10) + map.remove(0) + assertThat(map.size).isEqualTo(0) + assertThat(map[10]).isEqualTo(null) + assertThat(map.size).isEqualTo(0) + } +} diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt index 0d2fcfc0b790..124b61e45ed6 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt @@ -16,6 +16,8 @@ package com.android.compose.animation.scene +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable @@ -27,6 +29,9 @@ import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.lerp +import androidx.compose.ui.util.lerp import kotlinx.coroutines.CoroutineScope import platform.test.motion.MotionTestRule import platform.test.motion.RecordedMotion @@ -62,6 +67,16 @@ interface TransitionTestBuilder { fun at(timestamp: Long, builder: TransitionTestAssertionScope.() -> Unit) /** + * Run the same assertion for all frames of a transition. + * + * @param totalFrames needs to be the exact number of frames of the transition that is run, + * otherwise the passed progress will be incorrect. That is the duration in ms divided by 16. + * @param builder is passed a progress Float which can be used to calculate values for the + * specific frame. Or use [AutoTransitionTestAssertionScope.interpolate]. + */ + fun atAllFrames(totalFrames: Int, builder: AutoTransitionTestAssertionScope.(Float) -> Unit) + + /** * Assert on the state of the layout after the transition finished. * * This should be called maximum once, after [before] or [at] is called. @@ -82,6 +97,16 @@ interface TransitionTestAssertionScope { fun onElement(element: ElementKey, scene: SceneKey? = null): SemanticsNodeInteraction } +interface AutoTransitionTestAssertionScope : TransitionTestAssertionScope { + + /** Linear interpolate [from] and [to] with the current progress of the transition. */ + fun <T> interpolate(from: T, to: T): T +} + +val Default4FrameLinearTransition: TransitionBuilder.() -> Unit = { + spec = tween(16 * 4, easing = LinearEasing) +} + /** * Test the transition between [fromSceneContent] and [toSceneContent] at different points in time. * @@ -90,10 +115,13 @@ interface TransitionTestAssertionScope { fun ComposeContentTestRule.testTransition( fromSceneContent: @Composable ContentScope.() -> Unit, toSceneContent: @Composable ContentScope.() -> Unit, - transition: TransitionBuilder.() -> Unit, + transition: TransitionBuilder.() -> Unit = Default4FrameLinearTransition, layoutModifier: Modifier = Modifier, fromScene: SceneKey = TestScenes.SceneA, toScene: SceneKey = TestScenes.SceneB, + changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit = { state -> + state.setTargetScene(toScene, animationScope = this) + }, builder: TransitionTestBuilder.() -> Unit, ) { testTransition( @@ -104,7 +132,7 @@ fun ComposeContentTestRule.testTransition( transitions { from(fromScene, to = toScene, builder = transition) }, ) }, - to = toScene, + changeState = changeState, transitionLayout = { state -> SceneTransitionLayout(state, layoutModifier) { scene(fromScene, content = fromSceneContent) @@ -293,13 +321,30 @@ fun ComposeContentTestRule.testTransition( ) { val test = transitionTest(builder) val assertionScope = - object : TransitionTestAssertionScope { + object : AutoTransitionTestAssertionScope { + var progress = 0f + override fun onElement( element: ElementKey, scene: SceneKey?, ): SemanticsNodeInteraction { return onNode(isElement(element, scene)) } + + override fun <T> interpolate(from: T, to: T): T { + @Suppress("UNCHECKED_CAST") + return when { + from is Float && to is Float -> lerp(from, to, progress) + from is Int && to is Int -> lerp(from, to, progress) + from is Long && to is Long -> lerp(from, to, progress) + from is Dp && to is Dp -> lerp(from, to, progress) + else -> + throw UnsupportedOperationException( + "Interpolation not supported for this type" + ) + } + as T + } } lateinit var coroutineScope: CoroutineScope @@ -321,14 +366,28 @@ fun ComposeContentTestRule.testTransition( mainClock.advanceTimeByFrame() waitForIdle() + var currentTime = 0L // Test the assertions at specific points in time. test.timestamps.forEach { tsAssertion -> if (tsAssertion.timestampDelta > 0L) { mainClock.advanceTimeBy(tsAssertion.timestampDelta) waitForIdle() + currentTime += tsAssertion.timestampDelta.toInt() } - tsAssertion.assertion(assertionScope) + assertionScope.progress = tsAssertion.progress + try { + tsAssertion.assertion(assertionScope, tsAssertion.progress) + } catch (assertionError: AssertionError) { + if (assertionScope.progress > 0) { + throw AssertionError( + "Transition assertion failed at ${currentTime}ms " + + "at progress: ${assertionScope.progress}f", + assertionError, + ) + } + throw assertionError + } } // Go to the end state and test it. @@ -371,7 +430,25 @@ private fun transitionTest(builder: TransitionTestBuilder.() -> Unit): Transitio val delta = timestamp - currentTimestamp currentTimestamp = timestamp - timestamps.add(TimestampAssertion(delta, builder)) + timestamps.add(TimestampAssertion(delta, { builder() }, 0f)) + } + + override fun atAllFrames( + totalFrames: Int, + builder: AutoTransitionTestAssertionScope.(Float) -> Unit, + ) { + check(after == null) { "atFrames(...) {} must be called before after {}" } + check(currentTimestamp == 0L) { + "atFrames(...) can't be called multiple times or after at(...)" + } + + for (frame in 0 until totalFrames) { + val timestamp = frame * 16L + val delta = timestamp - currentTimestamp + val progress = frame.toFloat() / totalFrames + currentTimestamp = timestamp + timestamps.add(TimestampAssertion(delta, builder, progress)) + } } override fun after(builder: TransitionTestAssertionScope.() -> Unit) { @@ -396,5 +473,6 @@ private class TransitionTest( private class TimestampAssertion( val timestampDelta: Long, - val assertion: TransitionTestAssertionScope.() -> Unit, + val assertion: AutoTransitionTestAssertionScope.(Float) -> Unit, + val progress: Float, ) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt index 34c4dfb4bc54..48af2d9f5542 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt @@ -25,6 +25,7 @@ import android.database.ContentObserver import android.graphics.Color import android.graphics.drawable.Drawable import android.net.Uri +import android.os.Bundle import android.util.Log import androidx.annotation.DrawableRes import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract @@ -51,10 +52,7 @@ interface CustomizationProviderClient { * selected affordances on the slot will move the selected affordance to the newest location in * the slot. */ - suspend fun insertSelection( - slotId: String, - affordanceId: String, - ) + suspend fun insertSelection(slotId: String, affordanceId: String) /** Returns all available slots supported by the device. */ suspend fun querySlots(): List<Slot> @@ -63,6 +61,11 @@ interface CustomizationProviderClient { suspend fun queryFlags(): List<Flag> /** + * Returns [Bundle] where the keys are from [CustomizationProviderContract.RuntimeValuesTable] + */ + suspend fun queryRuntimeValues(): Bundle + + /** * Returns [Flow] for observing the collection of slots. * * @see [querySlots] @@ -77,6 +80,13 @@ interface CustomizationProviderClient { fun observeFlags(): Flow<List<Flag>> /** + * Returns [Flow] for observing the variables from the System UI. + * + * @see [queryRuntimeValues] + */ + fun observeRuntimeValues(): Flow<Bundle> + + /** * Returns all available affordances supported by the device, regardless of current slot * placement. */ @@ -100,15 +110,10 @@ interface CustomizationProviderClient { fun observeSelections(): Flow<List<Selection>> /** Unselects an affordance with the given ID from the slot with the given ID. */ - suspend fun deleteSelection( - slotId: String, - affordanceId: String, - ) + suspend fun deleteSelection(slotId: String, affordanceId: String) /** Unselects all affordances from the slot with the given ID. */ - suspend fun deleteAllSelections( - slotId: String, - ) + suspend fun deleteAllSelections(slotId: String) /** Returns a [Drawable] with the given ID, loaded from the system UI package. */ suspend fun getAffordanceIcon( @@ -200,10 +205,7 @@ class CustomizationProviderClientImpl( private val backgroundDispatcher: CoroutineDispatcher, ) : CustomizationProviderClient { - override suspend fun insertSelection( - slotId: String, - affordanceId: String, - ) { + override suspend fun insertSelection(slotId: String, affordanceId: String) { withContext(backgroundDispatcher) { context.contentResolver.insert( Contract.LockScreenQuickAffordances.SelectionTable.URI, @@ -211,9 +213,9 @@ class CustomizationProviderClientImpl( put(Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID, slotId) put( Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID, - affordanceId + affordanceId, ) - } + }, ) } } @@ -221,13 +223,7 @@ class CustomizationProviderClientImpl( override suspend fun querySlots(): List<CustomizationProviderClient.Slot> { return withContext(backgroundDispatcher) { context.contentResolver - .query( - Contract.LockScreenQuickAffordances.SlotTable.URI, - null, - null, - null, - null, - ) + .query(Contract.LockScreenQuickAffordances.SlotTable.URI, null, null, null, null) ?.use { cursor -> buildList { val idColumnIndex = @@ -252,42 +248,55 @@ class CustomizationProviderClientImpl( } } } - } - ?: emptyList() + } ?: emptyList() } override suspend fun queryFlags(): List<CustomizationProviderClient.Flag> { return withContext(backgroundDispatcher) { - context.contentResolver - .query( - Contract.FlagsTable.URI, - null, - null, - null, - null, - ) - ?.use { cursor -> - buildList { + context.contentResolver.query(Contract.FlagsTable.URI, null, null, null, null)?.use { + cursor -> + buildList { + val nameColumnIndex = cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME) + val valueColumnIndex = cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE) + if (nameColumnIndex == -1 || valueColumnIndex == -1) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + CustomizationProviderClient.Flag( + name = cursor.getString(nameColumnIndex), + value = cursor.getInt(valueColumnIndex) == 1, + ) + ) + } + } + } + } ?: emptyList() + } + + override suspend fun queryRuntimeValues(): Bundle { + return withContext(backgroundDispatcher) { + Bundle().apply { + context.contentResolver + .query(Contract.RuntimeValuesTable.URI, null, null, null, null) + ?.use { cursor -> val nameColumnIndex = cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME) val valueColumnIndex = cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE) - if (nameColumnIndex == -1 || valueColumnIndex == -1) { - return@buildList - } - - while (cursor.moveToNext()) { - add( - CustomizationProviderClient.Flag( - name = cursor.getString(nameColumnIndex), - value = cursor.getInt(valueColumnIndex) == 1, - ) - ) + if (nameColumnIndex >= 0 && valueColumnIndex >= 0) { + while (cursor.moveToNext()) { + when (val name = cursor.getString(nameColumnIndex)) { + Contract.RuntimeValuesTable.KEY_IS_SHADE_LAYOUT_WIDE -> { + putBoolean(name, cursor.getInt(valueColumnIndex) == 1) + } + } + } } } - } + } } - ?: emptyList() } override fun observeSlots(): Flow<List<CustomizationProviderClient.Slot>> { @@ -298,6 +307,10 @@ class CustomizationProviderClientImpl( return observeUri(Contract.FlagsTable.URI).map { queryFlags() } } + override fun observeRuntimeValues(): Flow<Bundle> { + return observeUri(Contract.RuntimeValuesTable.URI).map { queryRuntimeValues() } + } + override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> { return withContext(backgroundDispatcher) { context.contentResolver @@ -375,22 +388,17 @@ class CustomizationProviderClientImpl( enablementActionIntent = cursor .getString(enablementActionIntentColumnIndex) - ?.toIntent( - affordanceId = affordanceId, - ), + ?.toIntent(affordanceId = affordanceId), configureIntent = cursor .getString(configureIntentColumnIndex) - ?.toIntent( - affordanceId = affordanceId, - ), + ?.toIntent(affordanceId = affordanceId), ) ) } } } - } - ?: emptyList() + } ?: emptyList() } override fun observeAffordances(): Flow<List<CustomizationProviderClient.Affordance>> { @@ -444,8 +452,7 @@ class CustomizationProviderClientImpl( } } } - } - ?: emptyList() + } ?: emptyList() } override fun observeSelections(): Flow<List<CustomizationProviderClient.Selection>> { @@ -454,34 +461,24 @@ class CustomizationProviderClientImpl( } } - override suspend fun deleteSelection( - slotId: String, - affordanceId: String, - ) { + override suspend fun deleteSelection(slotId: String, affordanceId: String) { withContext(backgroundDispatcher) { context.contentResolver.delete( Contract.LockScreenQuickAffordances.SelectionTable.URI, "${Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID} = ? AND" + " ${Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID}" + " = ?", - arrayOf( - slotId, - affordanceId, - ), + arrayOf(slotId, affordanceId), ) } } - override suspend fun deleteAllSelections( - slotId: String, - ) { + override suspend fun deleteAllSelections(slotId: String) { withContext(backgroundDispatcher) { context.contentResolver.delete( Contract.LockScreenQuickAffordances.SelectionTable.URI, Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID, - arrayOf( - slotId, - ), + arrayOf(slotId), ) } } @@ -499,9 +496,7 @@ class CustomizationProviderClientImpl( } } - private fun observeUri( - uri: Uri, - ): Flow<Unit> { + private fun observeUri(uri: Uri): Flow<Unit> { return callbackFlow { val observer = object : ContentObserver(null) { @@ -522,9 +517,7 @@ class CustomizationProviderClientImpl( .flowOn(backgroundDispatcher) } - private fun String.toIntent( - affordanceId: String, - ): Intent? { + private fun String.toIntent(affordanceId: String): Intent? { return try { Intent.parseUri(this, Intent.URI_INTENT_SCHEME) } catch (e: URISyntaxException) { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt index 8721c7885265..cb167eddcea9 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt @@ -198,4 +198,27 @@ object CustomizationProviderContract { const val VALUE = "value" } } + + object RuntimeValuesTable { + const val TABLE_NAME = "runtime_values" + val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build() + + /** + * This key corresponds to an Int value, where `1` means `true` and `0` means `false`. + * + * Whether the shade layout should be wide (true) or narrow (false). + * + * In a wide layout, notifications and quick settings each take up only half the screen + * width (whether they are shown at the same time or not). In a narrow layout, they can each + * be as wide as the entire screen. + */ + const val KEY_IS_SHADE_LAYOUT_WIDE = "is_shade_layout_wide" + + object Columns { + /** String. Unique ID for the value. */ + const val NAME = "name" + /** Type depends on the key name. */ + const val VALUE = "value" + } + } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt index f5a955d450e3..47c5bda93c0e 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt @@ -19,6 +19,7 @@ package com.android.systemui.shared.customization.data.content import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable +import android.os.Bundle import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -64,11 +65,13 @@ class FakeCustomizationProviderClient( value = true, ) ), + runtimeValues: Bundle = Bundle(), ) : CustomizationProviderClient { private val slots = MutableStateFlow(slots) private val affordances = MutableStateFlow(affordances) private val flags = MutableStateFlow(flags) + private val runtimeValues = MutableStateFlow(runtimeValues) private val selections = MutableStateFlow<Map<String, List<String>>>(emptyMap()) @@ -93,6 +96,10 @@ class FakeCustomizationProviderClient( return flags.value } + override suspend fun queryRuntimeValues(): Bundle { + return runtimeValues.value + } + override fun observeSlots(): Flow<List<CustomizationProviderClient.Slot>> { return slots.asStateFlow() } @@ -101,6 +108,10 @@ class FakeCustomizationProviderClient( return flags.asStateFlow() } + override fun observeRuntimeValues(): Flow<Bundle> { + return runtimeValues.asStateFlow() + } + override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> { return affordances.value } @@ -139,10 +150,7 @@ class FakeCustomizationProviderClient( } } - fun setFlag( - name: String, - value: Boolean, - ) { + fun setFlag(name: String, value: Boolean) { flags.value = flags.value.toMutableList().apply { removeIf { it.name == name } @@ -150,6 +158,10 @@ class FakeCustomizationProviderClient( } } + fun setRuntimeValues(runtimeValues: Bundle) { + this.runtimeValues.value = runtimeValues + } + fun setSlotCapacity(slotId: String, capacity: Int) { slots.value = slots.value.toMutableList().apply { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt index f1401f18f1d9..73efea764e59 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt @@ -28,7 +28,6 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView -import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener import com.android.systemui.model.SysUiState import com.android.systemui.res.R import com.android.systemui.settings.UserTracker @@ -236,13 +235,13 @@ class FontScalingDialogDelegateTest : SysuiTestCase() { dialog.show() val slider = dialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)!! - val changeListener = slider.onSeekBarWithIconButtonsChangeListener + val seekBarListener = slider.getSeekBarChangeListener() val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!! // Default seekbar progress for font size is 1, simulate dragging to 0 without // releasing the finger. - changeListener.onStartTrackingTouch(seekBar) + seekBarListener.onStartTrackingTouch(seekBar) // Update seekbar progress. This will trigger onProgressChanged in the // OnSeekBarChangeListener and the seekbar could get updated progress value // in onStopTrackingTouch. @@ -261,16 +260,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() { assertThat(systemScale).isEqualTo(1.0f) // Simulate releasing the finger from the seekbar. - changeListener.onStopTrackingTouch(seekBar) - backgroundDelayableExecutor.runAllReady() - backgroundDelayableExecutor.advanceClockToNext() - backgroundDelayableExecutor.runAllReady() - - // SeekBar interaction is finalized. - changeListener.onUserInteractionFinalized( - seekBar, - OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER, - ) + seekBarListener.onStopTrackingTouch(seekBar) backgroundDelayableExecutor.runAllReady() backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 21c6583d4e84..aeea99be40dd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -365,6 +365,25 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void showUdfpsOverlay_invokedTwice_doesNotNotifyListenerSecondTime() throws RemoteException { + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mFingerprintManager).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN, + TEST_REQUEST_ID, mOpticalProps.sensorId); + + reset(mFingerprintManager); + + // Second attempt should do nothing + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + verify(mFingerprintManager, never()).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN, + TEST_REQUEST_ID, mOpticalProps.sensorId); + } + + @Test public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception { mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt index af8c357e6d36..b33a83cf202a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt @@ -68,6 +68,7 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.sceneDataSourceDelegator import com.android.systemui.scene.ui.composable.Scene import com.android.systemui.scene.ui.composable.SceneContainer +import com.android.systemui.scene.ui.composable.SceneContainerTransitions import com.android.systemui.testKosmos import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.awaitCancellation @@ -115,7 +116,13 @@ class BouncerPredictiveBackTest : SysuiTestCase() { private val Kosmos.initialSceneKey by Fixture { Scenes.Bouncer } private val Kosmos.sceneContainerConfig by Fixture { val navigationDistances = mapOf(Scenes.Lockscreen to 1, Scenes.Bouncer to 0) - SceneContainerConfig(sceneKeys, initialSceneKey, emptyList(), navigationDistances) + SceneContainerConfig( + sceneKeys, + initialSceneKey, + SceneContainerTransitions, + emptyList(), + navigationDistances, + ) } private val view = mock<View>() @@ -182,6 +189,7 @@ class BouncerPredictiveBackTest : SysuiTestCase() { Scenes.Bouncer to bouncerScene, ), initialSceneKey = Scenes.Bouncer, + sceneTransitions = SceneContainerTransitions, overlayByKey = emptyMap(), dataSourceDelegator = kosmos.sceneDataSourceDelegator, qsSceneAdapter = { kosmos.fakeQsSceneAdapter }, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java index cecb5251b6e2..01baadda7c87 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java @@ -19,7 +19,6 @@ package com.android.systemui.common.ui.view; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -129,16 +128,43 @@ public class SeekBarWithIconButtonsViewTest extends SysuiTestCase { } @Test - public void setProgress_onlyOnProgressChangedTriggeredWithFromUserFalse() { + public void setProgress_onProgressChangedAndOnUserInteractionFinalized() { reset(mOnSeekBarChangeListener); mIconDiscreteSliderLinearLayout.setProgress(1); + // If users are changing seekbar progress without touching the seekbar or clicking the + // buttons, trigger onUserInteractionFinalized. verify(mOnSeekBarChangeListener).onProgressChanged( eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false)); verify(mOnSeekBarChangeListener, never()).onStartTrackingTouch(/* seekBar= */ any()); verify(mOnSeekBarChangeListener, never()).onStopTrackingTouch(/* seekBar= */ any()); + verify(mOnSeekBarChangeListener).onUserInteractionFinalized( + /* seekBar= */ any(), + eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER)); + } + + @Test + public void setProgressToSeekBarByTouch_onUserInteractionFinalizedAfterTouchEnds() { + reset(mOnSeekBarChangeListener); + final SeekBarWithIconButtonsView.SeekBarChangeListener seekBarChangeListener = + mIconDiscreteSliderLinearLayout.getSeekBarChangeListener(); + final SeekBar seekBar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar); + + // Simulate changing seekbar progress by touch + seekBarChangeListener.onStartTrackingTouch(seekBar); + mIconDiscreteSliderLinearLayout.setProgress(1); + + verify(mOnSeekBarChangeListener).onProgressChanged( + eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false)); verify(mOnSeekBarChangeListener, never()).onUserInteractionFinalized( - /* seekBar= */any(), /* control= */ anyInt()); + /* seekBar= */ any(), + eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER)); + + // Notify onUserInteractionFinalized after touch ends + seekBarChangeListener.onStopTrackingTouch(seekBar); + verify(mOnSeekBarChangeListener).onUserInteractionFinalized( + /* seekBar= */ any(), + eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER)); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index b8745b3e95b2..2eb45902917d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -33,7 +33,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.media.controls.ui.controller.KeyguardMediaController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.render.MediaContainerController; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -57,7 +56,6 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private ConfigurationController mConfigurationController; @Mock private KeyguardMediaController mKeyguardMediaController; - @Mock private NotificationSectionsFeatureManager mSectionsFeatureManager; @Mock private MediaContainerController mMediaContainerController; @Mock private NotificationRoundnessManager mNotificationRoundnessManager; @Mock private SectionHeaderController mIncomingHeaderController; @@ -73,26 +71,10 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { @Before public void setUp() { - when(mSectionsFeatureManager.getNumberOfBuckets()).thenAnswer( - invocation -> { - int count = 2; - if (mSectionsFeatureManager.isFilteringEnabled()) { - count = 5; - } - if (mSectionsFeatureManager.isMediaControlsEnabled()) { - if (!mSectionsFeatureManager.isFilteringEnabled()) { - count = 5; - } else { - count += 1; - } - } - return count; - }); mSectionsManager = new NotificationSectionsManager( mConfigurationController, mKeyguardMediaController, - mSectionsFeatureManager, mMediaContainerController, mNotificationRoundnessManager, mIncomingHeaderController, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt index 32ef501cd9e3..09ea767d6efc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt @@ -90,15 +90,15 @@ class GlowBoxEffectTest : SysuiTestCase() { assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.EASE_IN) - animatorTestRule.advanceTimeBy(config.easeInDuration + 50L) + animatorTestRule.advanceAnimationDuration(config.easeInDuration + 50L) assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.MAIN) - animatorTestRule.advanceTimeBy(config.duration + 50L) + animatorTestRule.advanceAnimationDuration(config.duration + 50L) assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.EASE_OUT) - animatorTestRule.advanceTimeBy(config.easeOutDuration + 50L) + animatorTestRule.advanceAnimationDuration(config.easeOutDuration + 50L) assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt index 67a42196d2e2..c9be7e3d0392 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt @@ -117,9 +117,9 @@ class LoadingEffectTest : SysuiTestCase() { loadingEffect.play() // Execute all the animators by advancing each duration with some buffer. - animatorTestRule.advanceTimeBy(config.easeInDuration.toLong()) - animatorTestRule.advanceTimeBy(config.maxDuration.toLong()) - animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong()) + animatorTestRule.advanceAnimationDuration(config.easeInDuration.toLong()) + animatorTestRule.advanceAnimationDuration(config.maxDuration.toLong()) + animatorTestRule.advanceAnimationDuration(config.easeOutDuration.toLong()) animatorTestRule.advanceTimeBy(500) assertThat(states) @@ -206,12 +206,12 @@ class LoadingEffectTest : SysuiTestCase() { assertThat(isFinished).isFalse() loadingEffect.play() - animatorTestRule.advanceTimeBy(config.easeInDuration.toLong() + 500L) + animatorTestRule.advanceAnimationDuration(config.easeInDuration.toLong() + 500L) assertThat(isFinished).isFalse() loadingEffect.finish() - animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong() + 500L) + animatorTestRule.advanceAnimationDuration(config.easeOutDuration.toLong() + 500L) assertThat(isFinished).isTrue() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt index 266a60dfb6e9..665f55bfc2ec 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt @@ -193,7 +193,11 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { } private fun TestScope.advanceTime(timeMs: Long) { - animatorTestRule.advanceTimeBy(timeMs) + if (timeMs == ANIMATION_DURATION) { + animatorTestRule.advanceAnimationDuration(timeMs) + } else { + animatorTestRule.advanceTimeBy(timeMs) + } advanceTimeBy(timeMs) } diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 52704030108f..478050b0ed85 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -236,6 +236,9 @@ <!-- The size of a bluetooth indicator icon that displays next to the RSSI status icon. --> <dimen name="status_bar_connected_device_bt_indicator_size">17dp</dimen> + <!-- Height of a small notification in the status bar (2025 redesign version) --> + <dimen name="notification_2025_min_height">@*android:dimen/notification_2025_min_height</dimen> + <!-- Height of a small notification in the status bar--> <dimen name="notification_min_height">@*android:dimen/notification_min_height</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 2863e29c9a34..a9133e45e93f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -814,6 +814,11 @@ public class UdfpsController implements DozeReceiver, Dumpable { private void showUdfpsOverlay(@NonNull UdfpsControllerOverlay overlay) { mExecution.assertIsMainThread(); + if (mOverlay != null) { + Log.d(TAG, "showUdfpsOverlay | the overlay is already showing"); + return; + } + mOverlay = overlay; final int requestReason = overlay.getRequestReason(); if (requestReason == REASON_AUTH_KEYGUARD @@ -823,7 +828,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { return; } if (overlay.show(this, mOverlayParams)) { - Log.v(TAG, "showUdfpsOverlay | adding window reason=" + requestReason); + Log.d(TAG, "showUdfpsOverlay | adding window reason=" + requestReason); mOnFingerDown = false; mAttemptedToDismissKeyguard = false; mOrientationListener.enable(); @@ -832,7 +837,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { overlay.getRequestId(), mSensorProps.sensorId); } } else { - Log.v(TAG, "showUdfpsOverlay | the overlay is already showing"); + Log.d(TAG, "showUdfpsOverlay | the overlay is already showing"); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 2593cebb14d0..51eb13947d40 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -41,6 +41,7 @@ import android.view.WindowManager import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener import androidx.annotation.VisibleForTesting +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.animation.ActivityTransitionAnimator @@ -73,7 +74,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch private const val TAG = "UdfpsControllerOverlay" @@ -245,7 +245,7 @@ constructor( return true } - Log.v(TAG, "showUdfpsOverlay | the overlay is already showing") + Log.d(TAG, "showUdfpsOverlay | the overlay is already showing") return false } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java index 7fe00322ef27..82bce0b5338a 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java @@ -179,6 +179,14 @@ public class SeekBarWithIconButtonsView extends LinearLayout { } /** + * Only for testing. Get mSeekBarListener to the seekbar. + */ + @VisibleForTesting + public SeekBarChangeListener getSeekBarChangeListener() { + return mSeekBarListener; + } + + /** * Only for testing. Get {@link #mSeekbar} in the layout. */ @VisibleForTesting @@ -289,8 +297,10 @@ public class SeekBarWithIconButtonsView extends LinearLayout { void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control); } - private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener { + @VisibleForTesting + public class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener { private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener = null; + private boolean mSeekByTouch = false; @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { @@ -308,6 +318,14 @@ public class SeekBarWithIconButtonsView extends LinearLayout { seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON); } else { mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser); + if (!mSeekByTouch) { + // Accessibility users could change the progress of the seekbar without + // touching the seekbar or clicking the buttons. We will consider the + // interaction has finished in this case. + mOnSeekBarChangeListener.onUserInteractionFinalized( + seekBar, + OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER); + } } } updateIconViewIfNeeded(progress); @@ -315,6 +333,7 @@ public class SeekBarWithIconButtonsView extends LinearLayout { @Override public void onStartTrackingTouch(SeekBar seekBar) { + mSeekByTouch = true; if (mOnSeekBarChangeListener != null) { mOnSeekBarChangeListener.onStartTrackingTouch(seekBar); } @@ -322,6 +341,7 @@ public class SeekBarWithIconButtonsView extends LinearLayout { @Override public void onStopTrackingTouch(SeekBar seekBar) { + mSeekByTouch = false; if (mOnSeekBarChangeListener != null) { mOnSeekBarChangeListener.onStopTrackingTouch(seekBar); mOnSeekBarChangeListener.onUserInteractionFinalized( diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index 44dd34a89303..8ddd1ed04f33 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -34,6 +34,7 @@ import com.android.systemui.communal.shared.log.CommunalStatsLogProxyImpl import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelperImpl +import com.android.systemui.communal.ui.compose.sceneTransitions import com.android.systemui.communal.util.CommunalColors import com.android.systemui.communal.util.CommunalColorsImpl import com.android.systemui.communal.widgets.CommunalWidgetModule @@ -113,6 +114,7 @@ interface CommunalModule { SceneContainerConfig( sceneKeys = listOf(CommunalScenes.Blank, CommunalScenes.Communal), initialSceneKey = CommunalScenes.Blank, + transitions = sceneTransitions, navigationDistances = mapOf(CommunalScenes.Blank to 0, CommunalScenes.Communal to 1), ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt index a94df091230d..7a72732ea6bf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt @@ -36,6 +36,7 @@ import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCall import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager +import com.android.systemui.shade.domain.interactor.ShadeModeInteractor import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -44,6 +45,7 @@ class CustomizationProvider : ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer { @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor + @Inject lateinit var shadeModeInteractor: ShadeModeInteractor @Inject lateinit var previewManager: KeyguardRemotePreviewManager @Inject @Main lateinit var mainDispatcher: CoroutineDispatcher @@ -73,6 +75,11 @@ class CustomizationProvider : MATCH_CODE_ALL_SELECTIONS, ) addURI(Contract.AUTHORITY, Contract.FlagsTable.TABLE_NAME, MATCH_CODE_ALL_FLAGS) + addURI( + Contract.AUTHORITY, + Contract.RuntimeValuesTable.TABLE_NAME, + MATCH_CODE_ALL_RUNTIME_VALUES, + ) } override fun onCreate(): Boolean { @@ -94,7 +101,8 @@ class CustomizationProvider : MATCH_CODE_ALL_SLOTS, MATCH_CODE_ALL_AFFORDANCES, MATCH_CODE_ALL_FLAGS, - MATCH_CODE_ALL_SELECTIONS -> "vnd.android.cursor.dir/vnd." + MATCH_CODE_ALL_SELECTIONS, + MATCH_CODE_ALL_RUNTIME_VALUES -> "vnd.android.cursor.dir/vnd." else -> null } @@ -113,6 +121,7 @@ class CustomizationProvider : Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME ) MATCH_CODE_ALL_FLAGS -> Contract.FlagsTable.TABLE_NAME + MATCH_CODE_ALL_RUNTIME_VALUES -> Contract.RuntimeValuesTable.TABLE_NAME else -> null } @@ -146,6 +155,7 @@ class CustomizationProvider : MATCH_CODE_ALL_SLOTS -> querySlots() MATCH_CODE_ALL_SELECTIONS -> querySelections() MATCH_CODE_ALL_FLAGS -> queryFlags() + MATCH_CODE_ALL_RUNTIME_VALUES -> queryRuntimeValues() else -> null } } @@ -334,6 +344,23 @@ class CustomizationProvider : } } + private fun queryRuntimeValues(): Cursor { + return MatrixCursor( + arrayOf( + Contract.RuntimeValuesTable.Columns.NAME, + Contract.RuntimeValuesTable.Columns.VALUE, + ) + ) + .apply { + addRow( + arrayOf( + Contract.RuntimeValuesTable.KEY_IS_SHADE_LAYOUT_WIDE, + if (shadeModeInteractor.isShadeLayoutWide.value) 1 else 0, + ) + ) + } + } + private suspend fun deleteSelection(uri: Uri, selectionArgs: Array<out String>?): Int { if (selectionArgs == null) { throw IllegalArgumentException( @@ -370,5 +397,6 @@ class CustomizationProvider : private const val MATCH_CODE_ALL_AFFORDANCES = 2 private const val MATCH_CODE_ALL_SELECTIONS = 3 private const val MATCH_CODE_ALL_FLAGS = 4 + private const val MATCH_CODE_ALL_RUNTIME_VALUES = 5 } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/QsInCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/QsInCompose.kt new file mode 100644 index 000000000000..3067ccbb7cea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/flags/QsInCompose.kt @@ -0,0 +1,43 @@ +/* + * 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.flags + +import com.android.systemui.flags.RefactorFlagUtils +import com.android.systemui.shade.shared.flag.DualShade + +/** + * Object to help check if the new QS ui should be used. This is true if either [QSComposeFragment] + * or [DualShade] are enabled. + */ +object QsInCompose { + + /** + * This is not a real flag name, but a representation of the allowed flag names. Should not be + * used with test annotations. + */ + private val flagName = "${QSComposeFragment.FLAG_NAME}|${DualShade.FLAG_NAME}" + + @JvmStatic + inline val isEnabled: Boolean + get() = QSComposeFragment.isEnabled || DualShade.isEnabled + + @JvmStatic + fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, flagName) + + @JvmStatic fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, flagName) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt index 4806c3f83224..8f870d468997 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.tiles.impl.custom.domain.interactor import android.os.UserHandle import android.service.quicksettings.Tile -import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.systemui.common.coroutine.ConflatedCallbackFlow import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor @@ -28,7 +28,6 @@ import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePacka import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.di.QSTileScope import com.android.systemui.user.data.repository.UserRepository -import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,6 +44,7 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn +import com.android.app.tracing.coroutines.launchTraced as launch @QSTileScope @OptIn(ExperimentalCoroutinesApi::class) @@ -64,7 +64,7 @@ constructor( private val bindingFlow = mutableUserFlow .flatMapLatest { user -> - conflatedCallbackFlow { + ConflatedCallbackFlow.conflatedCallbackFlow { serviceInteractor.setUser(user) // Wait for the CustomTileInteractor to become initialized first, because @@ -79,7 +79,7 @@ constructor( defaultsRepository.requestNewDefaults( user, tileSpec.componentName, - true, + true ) } .launchIn(this) @@ -99,7 +99,7 @@ constructor( override fun tileData( user: UserHandle, - triggers: Flow<DataUpdateTrigger>, + triggers: Flow<DataUpdateTrigger> ): Flow<CustomTileDataModel> { tileScope.launch { mutableUserFlow.emit(user) } return bindingFlow.combine(triggers) { _, _ -> }.flatMapLatest { dataFlow(user) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index f9a1ad5d8424..ab3862b75ee8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -25,7 +25,6 @@ import com.android.systemui.Dumpable import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.QSHost import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon @@ -36,17 +35,14 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.io.PrintWriter import java.util.concurrent.CopyOnWriteArraySet -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collectIndexed import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.takeWhile -import kotlinx.coroutines.launch // TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout class QSTileViewModelAdapter @@ -55,7 +51,6 @@ constructor( @Application private val applicationScope: CoroutineScope, private val qsHost: QSHost, @Assisted private val qsTileViewModel: QSTileViewModel, - @UiBackground private val uiBgDispatcher: CoroutineDispatcher, ) : QSTile, Dumpable { private val context @@ -167,25 +162,19 @@ constructor( override fun setListening(client: Any?, listening: Boolean) { client ?: return if (listening) { - applicationScope.launch(uiBgDispatcher) { - val shouldStartMappingJob = - listeningClients.add(client) // new client - && listeningClients.size == 1 // first client - - if (shouldStartMappingJob) { - stateJob = - qsTileViewModel.state - .filterNotNull() - .map { mapState(context, it, qsTileViewModel.config) } - .onEach { legacyState -> - val changed = legacyState.copyTo(cachedState) - if (changed) { - callbacks.forEach { it.onStateChanged(legacyState) } - } + val clientWasNotAlreadyListening = listeningClients.add(client) + if (clientWasNotAlreadyListening && listeningClients.size == 1) { + stateJob = + qsTileViewModel.state + .filterNotNull() + .map { mapState(context, it, qsTileViewModel.config) } + .onEach { legacyState -> + val changed = legacyState.copyTo(cachedState) + if (changed) { + callbacks.forEach { it.onStateChanged(legacyState) } } - .flowOn(uiBgDispatcher) - .launchIn(applicationScope) - } + } + .launchIn(applicationScope) } } else { listeningClients.remove(client) diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt index e441a23d3725..e36e40d312b5 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt @@ -29,6 +29,7 @@ import com.android.systemui.scene.domain.startable.StatusBarStartable import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.ui.composable.SceneContainerTransitions import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.flag.DualShade @@ -98,6 +99,7 @@ interface KeyguardlessSceneContainerFrameworkModule { Scenes.Shade.takeUnless { DualShade.isEnabled }, ), initialSceneKey = Scenes.Gone, + transitions = SceneContainerTransitions, overlayKeys = listOfNotNull( Overlays.NotificationsShade.takeIf { DualShade.isEnabled }, diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 4beec1041a03..fe014524e3da 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -29,6 +29,7 @@ import com.android.systemui.scene.domain.startable.StatusBarStartable import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.ui.composable.SceneContainerTransitions import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.flag.DualShade @@ -106,6 +107,7 @@ interface SceneContainerFrameworkModule { Scenes.Shade.takeUnless { DualShade.isEnabled }, ), initialSceneKey = Scenes.Lockscreen, + transitions = SceneContainerTransitions, overlayKeys = listOfNotNull( Overlays.NotificationsShade.takeIf { DualShade.isEnabled }, diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt index 16ed59f4e6f2..c1646b8f2060 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt @@ -20,6 +20,7 @@ import com.android.systemui.scene.domain.SceneDomainModule import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.ui.composable.SceneContainerTransitions import dagger.Module import dagger.Provides @@ -35,7 +36,7 @@ import dagger.Provides // List SceneResolver modules for supported SceneFamilies HomeSceneFamilyResolverModule::class, - ], + ] ) object ShadelessSceneContainerFrameworkModule { @@ -46,20 +47,12 @@ object ShadelessSceneContainerFrameworkModule { return SceneContainerConfig( // Note that this list is in z-order. The first one is the bottom-most and the // last one is top-most. - sceneKeys = - listOf( - Scenes.Gone, - Scenes.Lockscreen, - Scenes.Bouncer, - ), + sceneKeys = listOf(Scenes.Gone, Scenes.Lockscreen, Scenes.Bouncer), initialSceneKey = Scenes.Lockscreen, + transitions = SceneContainerTransitions, overlayKeys = emptyList(), navigationDistances = - mapOf( - Scenes.Gone to 0, - Scenes.Lockscreen to 0, - Scenes.Bouncer to 1, - ) + mapOf(Scenes.Gone to 0, Scenes.Lockscreen to 0, Scenes.Bouncer to 1), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt index 2311e47abfae..ce7be8311c68 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt @@ -18,6 +18,7 @@ package com.android.systemui.scene.shared.model import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.SceneTransitions /** Models the configuration of the scene container. */ data class SceneContainerConfig( @@ -38,6 +39,9 @@ data class SceneContainerConfig( */ val initialSceneKey: SceneKey, + /** Transition definitions to be used when animating between scene transitions. */ + val transitions: SceneTransitions, + /** * The keys to all overlays in the container, sorted by z-order such that the last one renders * on top of all previous ones. Overlay keys within the same container must not repeat but it's @@ -61,7 +65,7 @@ data class SceneContainerConfig( * Note that this is not the z-order of rendering; that's determined by the order of declaration * of scenes in the [sceneKeys] list. */ - val navigationDistances: Map<SceneKey, Int> + val navigationDistances: Map<SceneKey, Int>, ) { init { check(sceneKeys.isNotEmpty()) { "A container must have at least one scene key." } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index 1e3a233bfc7e..1c15c74d5631 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -196,6 +196,7 @@ object SceneWindowRootViewBinder { sceneByKey = sceneByKey, overlayByKey = overlayByKey, initialSceneKey = containerConfig.initialSceneKey, + sceneTransitions = containerConfig.transitions, dataSourceDelegator = dataSourceDelegator, qsSceneAdapter = qsSceneAdapter, ) diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 8c004c4d3adf..6844f053cd21 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -48,7 +48,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel; import com.android.systemui.compose.ComposeInitializer; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.qs.flags.QSComposeFragment; +import com.android.systemui.qs.flags.QsInCompose; import com.android.systemui.res.R; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; @@ -96,7 +96,7 @@ public class BrightnessDialog extends Activity { super.onCreate(savedInstanceState); setWindowAttributes(); View view; - if (!QSComposeFragment.isEnabled()) { + if (!QsInCompose.isEnabled()) { setContentView(R.layout.brightness_mirror_container); view = findViewById(R.id.brightness_mirror_container); setDialogContent((FrameLayout) view); @@ -140,7 +140,7 @@ public class BrightnessDialog extends Activity { window.getDecorView(); window.setLayout(WRAP_CONTENT, WRAP_CONTENT); getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false); - if (QSComposeFragment.isEnabled()) { + if (QsInCompose.isEnabled()) { window.getDecorView().addOnAttachStateChangeListener( new View.OnAttachStateChangeListener() { @Override @@ -217,7 +217,7 @@ public class BrightnessDialog extends Activity { @Override protected void onStart() { super.onStart(); - if (!QSComposeFragment.isEnabled()) { + if (!QsInCompose.isEnabled()) { mBrightnessController.registerCallbacks(); } MetricsLogger.visible(this, MetricsEvent.BRIGHTNESS_DIALOG); @@ -241,7 +241,7 @@ public class BrightnessDialog extends Activity { protected void onStop() { super.onStop(); MetricsLogger.hidden(this, MetricsEvent.BRIGHTNESS_DIALOG); - if (!QSComposeFragment.isEnabled()) { + if (!QsInCompose.isEnabled()) { mBrightnessController.unregisterCallbacks(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index 6fd2d3fafb88..c997ac5ad9df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -768,6 +768,8 @@ public final class KeyboardShortcutListSearch { Intent.CATEGORY_APP_EMAIL, Intent.CATEGORY_APP_CALENDAR, Intent.CATEGORY_APP_MAPS, + Intent.CATEGORY_APP_MUSIC, + Intent.CATEGORY_APP_MESSAGING, Intent.CATEGORY_APP_CALCULATOR, }; String[] shortcutLabels = { @@ -776,15 +778,19 @@ public final class KeyboardShortcutListSearch { mContext.getString(R.string.keyboard_shortcut_group_applications_email), mContext.getString(R.string.keyboard_shortcut_group_applications_calendar), mContext.getString(R.string.keyboard_shortcut_group_applications_maps), + mContext.getString(R.string.keyboard_shortcut_group_applications_music), + mContext.getString(R.string.keyboard_shortcut_group_applications_sms), mContext.getString(R.string.keyboard_shortcut_group_applications_calculator) }; int[] keyCodes = { KeyEvent.KEYCODE_B, - KeyEvent.KEYCODE_P, - KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_C, + KeyEvent.KEYCODE_E, + KeyEvent.KEYCODE_K, KeyEvent.KEYCODE_M, + KeyEvent.KEYCODE_P, + KeyEvent.KEYCODE_S, KeyEvent.KEYCODE_U, }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt index b67092ca9348..0569074c88b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt @@ -17,98 +17,16 @@ package com.android.systemui.statusbar.notification import android.content.Context -import android.provider.DeviceConfig -import com.android.internal.annotations.VisibleForTesting -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag -import com.android.systemui.statusbar.notification.shared.NotificationMinimalism -import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection -import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING -import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE -import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP -import com.android.systemui.statusbar.notification.stack.BUCKET_MEDIA_CONTROLS -import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE -import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT -import com.android.systemui.statusbar.notification.stack.PriorityBucket -import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.Utils import javax.inject.Inject -private var sUsePeopleFiltering: Boolean? = null - -/** Feature controller for the NOTIFICATIONS_USE_PEOPLE_FILTERING config. */ @SysUISingleton class NotificationSectionsFeatureManager @Inject -constructor(val proxy: DeviceConfigProxy, val context: Context) { - - fun isFilteringEnabled(): Boolean { - return usePeopleFiltering(proxy) - } +constructor(val context: Context) { fun isMediaControlsEnabled(): Boolean { return Utils.useQsMediaPlayer(context) } - - fun getNotificationBuckets(): IntArray { - if ( - PriorityPeopleSection.isEnabled || - NotificationMinimalism.isEnabled || - NotificationClassificationFlag.isEnabled - ) { - // We don't need this list to be adaptive, it can be the superset of all features. - return PriorityBucket.getAllInOrder() - } - return when { - isFilteringEnabled() && isMediaControlsEnabled() -> - intArrayOf( - BUCKET_HEADS_UP, - BUCKET_FOREGROUND_SERVICE, - BUCKET_MEDIA_CONTROLS, - BUCKET_PEOPLE, - BUCKET_ALERTING, - BUCKET_SILENT - ) - !isFilteringEnabled() && isMediaControlsEnabled() -> - intArrayOf( - BUCKET_HEADS_UP, - BUCKET_FOREGROUND_SERVICE, - BUCKET_MEDIA_CONTROLS, - BUCKET_ALERTING, - BUCKET_SILENT - ) - isFilteringEnabled() && !isMediaControlsEnabled() -> - intArrayOf( - BUCKET_HEADS_UP, - BUCKET_FOREGROUND_SERVICE, - BUCKET_PEOPLE, - BUCKET_ALERTING, - BUCKET_SILENT - ) - else -> intArrayOf(BUCKET_ALERTING, BUCKET_SILENT) - } - } - - fun getNumberOfBuckets(): Int { - return getNotificationBuckets().size - } - - @VisibleForTesting - fun clearCache() { - sUsePeopleFiltering = null - } -} - -private fun usePeopleFiltering(proxy: DeviceConfigProxy): Boolean { - if (sUsePeopleFiltering == null) { - sUsePeopleFiltering = - proxy.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, - NOTIFICATIONS_USE_PEOPLE_FILTERING, - true - ) - } - - return sUsePeopleFiltering!! } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index b7ab996a608c..7ad65fc64735 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row; +import static android.app.Flags.notificationsRedesignTemplates; import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY; import static android.service.notification.NotificationListenerService.REASON_CANCEL; @@ -102,6 +103,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.headsup.PinnedStatus; import com.android.systemui.statusbar.notification.logging.NotificationCounters; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; @@ -121,7 +123,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationChildrenCon import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.SwipeableView; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.policy.InflatedSmartReplyState; import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.policy.SmartReplyConstants; @@ -2083,8 +2084,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView R.dimen.notification_min_height_before_p); mMaxSmallHeightBeforeS = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_min_height_before_s); - mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext, - R.dimen.notification_min_height); + if (notificationsRedesignTemplates()) { + mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_2025_min_height); + } else { + mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_min_height); + } mMaxSmallHeightLarge = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_min_height_increased); mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index 31e4d2cac50c..043d64e46039 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -21,7 +21,6 @@ import android.view.View import com.android.internal.annotations.VisibleForTesting import com.android.systemui.media.controls.ui.controller.KeyguardMediaController import com.android.systemui.shade.ShadeDisplayAware -import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.SourceType import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag import com.android.systemui.statusbar.notification.collection.render.MediaContainerController @@ -36,6 +35,7 @@ import com.android.systemui.statusbar.notification.dagger.SilentHeader import com.android.systemui.statusbar.notification.dagger.SocialHeader import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.notification.stack.PriorityBucket import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.foldToSparseArray @@ -51,7 +51,6 @@ class NotificationSectionsManager internal constructor( @ShadeDisplayAware private val configurationController: ConfigurationController, private val keyguardMediaController: KeyguardMediaController, - private val sectionsFeatureManager: NotificationSectionsFeatureManager, private val mediaContainerController: MediaContainerController, private val notificationRoundnessManager: NotificationRoundnessManager, @IncomingHeader private val incomingHeaderController: SectionHeaderController, @@ -120,8 +119,8 @@ internal constructor( } fun createSectionsForBuckets(): Array<NotificationSection> = - sectionsFeatureManager - .getNotificationBuckets() + PriorityBucket + .getAllInOrder() .map { NotificationSection(it) } .toTypedArray() diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt index 5c0cc8150d70..39b434ad65f1 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt @@ -21,20 +21,24 @@ import android.content.Context import android.graphics.PixelFormat import android.os.Bundle import android.view.MotionEvent +import android.view.View import android.view.ViewGroup import android.view.WindowManager +import com.android.app.tracing.coroutines.coroutineScopeTraced import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.volume.Events +import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor -import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder import javax.inject.Inject +import kotlinx.coroutines.awaitCancellation class VolumeDialog @Inject constructor( @Application context: Context, - private val viewBinder: VolumeDialogViewBinder, + private val componentFactory: VolumeDialogComponent.Factory, private val visibilityInteractor: VolumeDialogVisibilityInteractor, ) : Dialog(context, R.style.Theme_SystemUI_Dialog_Volume) { @@ -64,7 +68,14 @@ constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.volume_dialog) - viewBinder.bind(this) + requireViewById<View>(R.id.volume_dialog_root).repeatWhenAttached { + coroutineScopeTraced("[Volume]dialog") { + val component = componentFactory.create(this) + with(component.volumeDialogViewBinder()) { bind(this@VolumeDialog) } + + awaitCancellation() + } + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt index b912361ae059..094ec39c1c98 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt @@ -16,6 +16,7 @@ package com.android.systemui.volume.dialog +import com.android.app.tracing.coroutines.coroutineScopeTraced import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.VolumeDialog @@ -23,7 +24,6 @@ import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.coroutineScope class VolumeDialogPlugin @Inject @@ -38,7 +38,7 @@ constructor( override fun init(windowType: Int, callback: VolumeDialog.Callback?) { job = applicationCoroutineScope.launch { - coroutineScope { + coroutineScopeTraced("[Volume]plugin") { pluginComponent = volumeDialogPluginComponentFactory.create(this).also { it.viewModel().launchVolumeDialog() diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt index fb157958a630..434f6b567048 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt @@ -20,6 +20,7 @@ import com.android.systemui.volume.dialog.dagger.module.VolumeDialogModule import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent +import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder import dagger.BindsInstance import dagger.Subcomponent import kotlinx.coroutines.CoroutineScope @@ -32,21 +33,21 @@ import kotlinx.coroutines.CoroutineScope @Subcomponent(modules = [VolumeDialogModule::class]) interface VolumeDialogComponent { - /** - * Provides a coroutine scope to use inside [VolumeDialogScope]. - * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this scope. - * It's cancelled when the dialog is disposed. This helps to free occupied resources when volume - * dialog is not shown. - */ - @VolumeDialog fun coroutineScope(): CoroutineScope - - @VolumeDialogScope fun volumeDialog(): com.android.systemui.volume.dialog.VolumeDialog + fun volumeDialogViewBinder(): VolumeDialogViewBinder fun sliderComponentFactory(): VolumeDialogSliderComponent.Factory @Subcomponent.Factory interface Factory { - fun create(@BindsInstance @VolumeDialog scope: CoroutineScope): VolumeDialogComponent + fun create( + /** + * Provides a coroutine scope to use inside [VolumeDialogScope]. + * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this + * scope. It's cancelled when the dialog is disposed. This helps to free occupied + * resources when volume dialog is not shown. + */ + @BindsInstance @VolumeDialog scope: CoroutineScope + ): VolumeDialogComponent } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt index 7fd177d55d76..e0336243b327 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt @@ -18,9 +18,6 @@ package com.android.systemui.volume.dialog.sliders.ui import android.annotation.SuppressLint import android.view.View -import com.android.systemui.lifecycle.WindowLifecycleState -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.lifecycle.viewModel import com.android.systemui.res.R import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderTouchesViewModel @@ -30,22 +27,14 @@ import javax.inject.Inject @VolumeDialogSliderScope class VolumeDialogSliderTouchesViewBinder @Inject -constructor(private val viewModelFactory: VolumeDialogSliderTouchesViewModel.Factory) { +constructor(private val viewModel: VolumeDialogSliderTouchesViewModel) { @SuppressLint("ClickableViewAccessibility") fun bind(view: View) { with(view.requireViewById<Slider>(R.id.volume_dialog_slider)) { - repeatWhenAttached { - viewModel( - traceName = "VolumeDialogSliderTouchesViewBinder", - minWindowLifecycleState = WindowLifecycleState.ATTACHED, - factory = { viewModelFactory.create() }, - ) { viewModel -> - setOnTouchListener { _, event -> - viewModel.onTouchEvent(event) - false - } - } + setOnTouchListener { _, event -> + viewModel.onTouchEvent(event) + false } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index 1c231b521bae..f9334dfc7ba2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -20,9 +20,6 @@ import android.animation.Animator import android.animation.ObjectAnimator import android.view.View import android.view.animation.DecelerateInterpolator -import com.android.systemui.lifecycle.WindowLifecycleState -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.lifecycle.viewModel import com.android.systemui.res.R import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope @@ -33,7 +30,7 @@ import com.google.android.material.slider.LabelFormatter import com.google.android.material.slider.Slider import javax.inject.Inject import kotlin.math.roundToInt -import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -43,32 +40,20 @@ private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L class VolumeDialogSliderViewBinder @Inject constructor( - private val viewModelFactory: VolumeDialogSliderViewModel.Factory, + private val viewModel: VolumeDialogSliderViewModel, private val jankListenerFactory: JankListenerFactory, ) { - fun bind(view: View) { - with(view) { - val sliderView: Slider = - requireViewById<Slider>(R.id.volume_dialog_slider).apply { - labelBehavior = LabelFormatter.LABEL_GONE - } - repeatWhenAttached { - viewModel( - traceName = "VolumeDialogSliderViewBinder", - minWindowLifecycleState = WindowLifecycleState.ATTACHED, - factory = { viewModelFactory.create() }, - ) { viewModel -> - sliderView.addOnChangeListener { _, value, fromUser -> - viewModel.setStreamVolume(value.roundToInt(), fromUser) - } - - viewModel.model.onEach { it.bindToSlider(sliderView) }.launchIn(this) - - awaitCancellation() - } + fun CoroutineScope.bind(view: View) { + val sliderView: Slider = + view.requireViewById<Slider>(R.id.volume_dialog_slider).apply { + labelBehavior = LabelFormatter.LABEL_GONE } + sliderView.addOnChangeListener { _, value, fromUser -> + viewModel.setStreamVolume(value.roundToInt(), fromUser) } + + viewModel.model.onEach { it.bindToSlider(sliderView) }.launchIn(this) } private suspend fun VolumeDialogStreamModel.bindToSlider(slider: Slider) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt index 1b2b4fff5b8e..242845a47f29 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt @@ -21,58 +21,47 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.LayoutRes import androidx.compose.ui.util.fastForEachIndexed -import com.android.systemui.lifecycle.WindowLifecycleState -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.lifecycle.viewModel import com.android.systemui.res.R import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @VolumeDialogScope class VolumeDialogSlidersViewBinder @Inject -constructor(private val viewModelFactory: VolumeDialogSlidersViewModel.Factory) { +constructor(private val viewModel: VolumeDialogSlidersViewModel) { - fun bind(view: View) { - with(view) { - val floatingSlidersContainer: ViewGroup = - requireViewById(R.id.volume_dialog_floating_sliders_container) - val mainSliderContainer: View = - requireViewById(R.id.volume_dialog_main_slider_container) - repeatWhenAttached { - viewModel( - traceName = "VolumeDialogSlidersViewBinder", - minWindowLifecycleState = WindowLifecycleState.ATTACHED, - factory = { viewModelFactory.create() }, - ) { viewModel -> - viewModel.sliders - .onEach { uiModel -> - uiModel.sliderComponent.bindSlider(mainSliderContainer) + fun CoroutineScope.bind(view: View) { + val floatingSlidersContainer: ViewGroup = + view.requireViewById(R.id.volume_dialog_floating_sliders_container) + val mainSliderContainer: View = + view.requireViewById(R.id.volume_dialog_main_slider_container) + viewModel.sliders + .onEach { uiModel -> + bindSlider(uiModel.sliderComponent, mainSliderContainer) - val floatingSliderViewBinders = uiModel.floatingSliderComponent - floatingSlidersContainer.ensureChildCount( - viewLayoutId = R.layout.volume_dialog_slider_floating, - count = floatingSliderViewBinders.size, - ) - floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent -> - sliderComponent.bindSlider( - floatingSlidersContainer.getChildAt(index) - ) - } - } - .launchIn(this) + val floatingSliderViewBinders = uiModel.floatingSliderComponent + floatingSlidersContainer.ensureChildCount( + viewLayoutId = R.layout.volume_dialog_slider_floating, + count = floatingSliderViewBinders.size, + ) + floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent -> + bindSlider(sliderComponent, floatingSlidersContainer.getChildAt(index)) } } - } + .launchIn(this) } - private fun VolumeDialogSliderComponent.bindSlider(sliderContainer: View) { - sliderViewBinder().bind(sliderContainer) - sliderTouchesViewBinder().bind(sliderContainer) + private fun CoroutineScope.bindSlider( + component: VolumeDialogSliderComponent, + sliderContainer: View, + ) { + with(component.sliderViewBinder()) { bind(sliderContainer) } + with(component.sliderTouchesViewBinder()) { bind(sliderContainer) } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt index 144c75da2b2b..9126f45fe32a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt @@ -17,20 +17,16 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel import android.view.MotionEvent +import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInputEventsInteractor -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject +@VolumeDialogSliderScope class VolumeDialogSliderTouchesViewModel -@AssistedInject +@Inject constructor(private val interactor: VolumeDialogSliderInputEventsInteractor) { fun onTouchEvent(event: MotionEvent) { interactor.onTouchEvent(event) } - - @AssistedFactory - interface Factory { - fun create(): VolumeDialogSliderTouchesViewModel - } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt index cf04d45d54ff..6dd5b638a3bc 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt @@ -21,9 +21,9 @@ import com.android.systemui.volume.Events import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel +import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -49,8 +49,9 @@ import kotlinx.coroutines.flow.stateIn private const val VOLUME_UPDATE_GRACE_PERIOD = 1000 @OptIn(ExperimentalCoroutinesApi::class) +@VolumeDialogSliderScope class VolumeDialogSliderViewModel -@AssistedInject +@Inject constructor( private val interactor: VolumeDialogSliderInteractor, private val visibilityInteractor: VolumeDialogVisibilityInteractor, @@ -90,10 +91,4 @@ constructor( private fun getTimestampMillis(): Long = systemClock.uptimeMillis() private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long) - - @AssistedFactory - interface Factory { - - fun create(): VolumeDialogSliderViewModel - } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt index d1972231d373..d8e6aec026c6 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt @@ -17,10 +17,10 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted @@ -28,8 +28,9 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +@VolumeDialogScope class VolumeDialogSlidersViewModel -@AssistedInject +@Inject constructor( @VolumeDialog coroutineScope: CoroutineScope, private val slidersInteractor: VolumeDialogSlidersInteractor, @@ -47,12 +48,6 @@ constructor( } .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() - - @AssistedFactory - interface Factory { - - fun create(): VolumeDialogSlidersViewModel - } } /** Models slider ui */ diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt index f6c1743a4bea..a3166a9978f4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt @@ -25,9 +25,6 @@ import android.view.ViewTreeObserver import android.view.ViewTreeObserver.InternalInsetsInfo import androidx.constraintlayout.motion.widget.MotionLayout import com.android.internal.view.RotationPolicy -import com.android.systemui.lifecycle.WindowLifecycleState -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.lifecycle.viewModel import com.android.systemui.res.R import com.android.systemui.util.children import com.android.systemui.volume.SystemUIInterpolators @@ -44,7 +41,6 @@ import com.android.systemui.volume.dialog.utils.VolumeTracer import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn @@ -61,7 +57,7 @@ class VolumeDialogViewBinder @Inject constructor( private val volumeResources: VolumeDialogResources, - private val dialogViewModelFactory: VolumeDialogViewModel.Factory, + private val viewModel: VolumeDialogViewModel, private val jankListenerFactory: JankListenerFactory, private val tracer: VolumeTracer, private val volumeDialogRingerViewBinder: VolumeDialogRingerViewBinder, @@ -69,35 +65,27 @@ constructor( private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder, ) { - fun bind(dialog: Dialog) { + fun CoroutineScope.bind(dialog: Dialog) { // Root view of the Volume Dialog. val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root) root.alpha = 0f - root.repeatWhenAttached { - root.viewModel( - traceName = "VolumeDialogViewBinder", - minWindowLifecycleState = WindowLifecycleState.ATTACHED, - factory = { dialogViewModelFactory.create() }, - ) { viewModel -> - animateVisibility(root, dialog, viewModel.dialogVisibilityModel) - - viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this) - viewModel.motionState - .scan(0) { acc, motionState -> - // don't animate the initial state - root.transitionToState(motionState, animate = acc != 0) - acc + 1 - } - .launchIn(this) - launch { root.viewTreeObserver.computeInternalInsetsListener(root) } + animateVisibility(root, dialog, viewModel.dialogVisibilityModel) - awaitCancellation() + viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this) + viewModel.motionState + .scan(0) { acc, motionState -> + // don't animate the initial state + root.transitionToState(motionState, animate = acc != 0) + acc + 1 } - } - volumeDialogRingerViewBinder.bind(root) - slidersViewBinder.bind(root) - settingsButtonViewBinder.bind(root) + .launchIn(this) + + launch { root.viewTreeObserver.computeInternalInsetsListener(root) } + + with(volumeDialogRingerViewBinder) { bind(root) } + with(slidersViewBinder) { bind(root) } + with(settingsButtonViewBinder) { bind(root) } } private fun CoroutineScope.animateVisibility( diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt index e858cfef67ea..ff525f46a7ed 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt @@ -17,14 +17,14 @@ package com.android.systemui.volume.dialog.ui.viewmodel import com.android.systemui.volume.Events -import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent +import com.android.systemui.volume.dialog.VolumeDialog import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.shared.VolumeDialogLogger import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel import javax.inject.Inject +import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapLatest @@ -34,8 +34,8 @@ import kotlinx.coroutines.flow.mapLatest class VolumeDialogPluginViewModel @Inject constructor( - private val componentFactory: VolumeDialogComponent.Factory, private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor, + private val volumeDialogProvider: Provider<VolumeDialog>, private val logger: VolumeDialogLogger, ) { @@ -45,7 +45,7 @@ constructor( .mapLatest { visibilityModel -> with(visibilityModel) { if (this is VolumeDialogVisibilityModel.Visible) { - showDialog(componentFactory) + showDialog() Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked) logger.onShow(reason) } @@ -59,16 +59,14 @@ constructor( } } - private suspend fun showDialog(componentFactory: VolumeDialogComponent.Factory): Unit = - coroutineScope { - val volumeDialogComponent: VolumeDialogComponent = componentFactory.create(this) - val dialog = - volumeDialogComponent.volumeDialog().apply { - setOnDismissListener { - volumeDialogComponent.coroutineScope().cancel() - dialogVisibilityInteractor.dismissDialog(Events.DISMISS_REASON_UNKNOWN) - } + private fun showDialog() { + volumeDialogProvider + .get() + .apply { + setOnDismissListener { + dialogVisibilityInteractor.dismissDialog(Events.DISMISS_REASON_UNKNOWN) } - dialog.show() - } + } + .show() + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt index 0352799916bc..7a6ede4c8b9c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.devicePosture import com.android.systemui.statusbar.policy.onConfigChanged +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel @@ -30,9 +31,7 @@ import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityMod import com.android.systemui.volume.dialog.shared.model.streamLabel import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import kotlinx.coroutines.ExperimentalCoroutinesApi +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull @@ -40,9 +39,9 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart /** Provides a state for the Volume Dialog. */ -@OptIn(ExperimentalCoroutinesApi::class) +@VolumeDialogScope class VolumeDialogViewModel -@AssistedInject +@Inject constructor( private val context: Context, dialogVisibilityInteractor: VolumeDialogVisibilityInteractor, @@ -84,9 +83,4 @@ constructor( val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED return isLandscape && isHalfOpen } - - @AssistedFactory - interface Factory { - fun create(): VolumeDialogViewModel - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java index fa88f6207c49..ad5f96044c4c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -930,6 +930,10 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { } private void advanceTimeBy(long timeDelta) { + if (timeDelta == mWaitAnimationDuration) { + mAnimatorTestRule.advanceAnimationDuration(timeDelta); + return; + } mAnimatorTestRule.advanceTimeBy(timeDelta); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java index 7c0c5c209363..4553f983b898 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java @@ -59,6 +59,7 @@ import android.view.accessibility.AccessibilityManager; import android.widget.Button; import android.widget.CompoundButton; import android.widget.LinearLayout; +import android.widget.SeekBar; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -81,6 +82,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @SmallTest @@ -544,9 +546,10 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase { OnSeekBarWithIconButtonsChangeListener onChangeListener = mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener(); - mZoomSeekbar.setProgress(30); + SeekBar mockSeekBar = Mockito.mock(SeekBar.class); + when(mockSeekBar.getProgress()).thenReturn(30); onChangeListener.onUserInteractionFinalized( - mZoomSeekbar.getSeekbar(), + mockSeekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER); // should trigger callback to update magnifier scale and persist the scale diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 929b0aad3299..67e03e4bdb22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -273,6 +273,12 @@ class CustomizationProviderTest : SysuiTestCase() { "${Contract.AUTHORITY}." + Contract.FlagsTable.TABLE_NAME ) + assertThat(underTest.getType(Contract.RuntimeValuesTable.URI)) + .isEqualTo( + "vnd.android.cursor.dir/vnd." + + "${Contract.AUTHORITY}." + + Contract.RuntimeValuesTable.TABLE_NAME + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt index f7059e244084..a64ff321cd4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModelFactory import com.android.systemui.qs.flags.QSComposeFragment +import com.android.systemui.qs.flags.QsInCompose import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper @@ -70,8 +71,8 @@ class BrightnessDialogTest(val flags: FlagsParameterization) : SysuiTestCase() { mSetFlagsRule.setFlagsParameterization(flags) } - val viewId by lazy { - if (QSComposeFragment.isEnabled) { + private val viewId by lazy { + if (QsInCompose.isEnabled) { R.id.brightness_dialog_slider } else { R.id.brightness_mirror_container 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 af14edd10f5f..a1c9022e18bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -141,6 +141,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.interruption.AvalancheProvider; import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; @@ -154,7 +155,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController; import com.android.systemui.statusbar.policy.ZenModeController; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt index e2fc44fd2d0d..eb0aee415fdd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt @@ -16,6 +16,7 @@ package com.android.systemui.animation +import android.animation.Animator import java.util.function.Consumer import org.junit.rules.RuleChain import org.junit.rules.TestRule @@ -71,6 +72,22 @@ class AnimatorTestRule(test: Any?) : TestRule { } /** + * This is similar to [advanceTimeBy] but it expects to reach the end of an animation. This call + * may produce 2 frames for the last animation frame and end animation callback. + * + * @param durationMs the duration that is greater than or equal to the animation duration. + */ + fun advanceAnimationDuration(durationMs: Long) { + advanceTimeBy(durationMs) + if (Animator.isPostNotifyEndListenerEnabled()) { + // If the post-end-callback is enabled, the AnimatorListener#onAnimationEnd will be + // called on the next frame of last animation frame. So trigger additional doFrame to + // ensure the end callback method is called (by android.animation.AnimatorTestRule). + advanceTimeBy(0) + } + } + + /** * Returns the current time in milliseconds tracked by the AnimationHandlers. Note that this is * a different time than the time tracked by {@link SystemClock}. */ diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt index de9f629ef787..a90876551d20 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt @@ -18,7 +18,6 @@ package com.android.systemui.qs.tiles.viewmodel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope -import com.android.systemui.kosmos.testDispatcher import com.android.systemui.util.mockito.mock val Kosmos.qsTileViewModelAdaperFactory by @@ -29,7 +28,6 @@ val Kosmos.qsTileViewModelAdaperFactory by applicationCoroutineScope, mock(), qsTileViewModel, - testDispatcher, ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt index 3300c96b87fd..0eca818e9aac 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt @@ -13,6 +13,7 @@ import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.FakeOverlay +import com.android.systemui.scene.ui.composable.SceneContainerTransitions import com.android.systemui.scene.ui.viewmodel.SceneContainerHapticsViewModel import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector @@ -60,6 +61,7 @@ var Kosmos.sceneContainerConfig by Fixture { SceneContainerConfig( sceneKeys = sceneKeys, initialSceneKey = initialSceneKey, + transitions = SceneContainerTransitions, overlayKeys = overlayKeys, navigationDistances = navigationDistances, ) diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 0c2ce8dcb698..66c8d0fa32f9 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -282,20 +282,12 @@ cc_defaults { visibility: ["//visibility:private"], } -cc_library_host_shared { - name: "libravenwood_initializer", - defaults: ["ravenwood_jni_defaults"], - srcs: [ - "runtime-jni/ravenwood_initializer.cpp", - ], -} - // We need this as a separate library because we need to overload the // sysprop symbols before libbase is loaded into the process cc_library_host_shared { - name: "libravenwood_sysprop", + name: "libravenwood_initializer", defaults: ["ravenwood_jni_defaults"], - srcs: ["runtime-jni/ravenwood_sysprop.cpp"], + srcs: ["runtime-jni/ravenwood_initializer.cpp"], } cc_library_host_shared { @@ -669,7 +661,6 @@ android_ravenwood_libgroup { jni_libs: [ // Libraries has to be loaded in the following order "libravenwood_initializer", - "libravenwood_sysprop", "libravenwood_runtime", "libandroid_runtime", ], 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 e730a292a9da..172cec3b8e13 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -58,6 +58,7 @@ import android.provider.DeviceConfig_host; import android.system.ErrnoException; import android.system.Os; import android.util.Log; +import android.util.Log_ravenwood; import android.view.DisplayAdjustments; import androidx.test.platform.app.InstrumentationRegistry; @@ -102,7 +103,6 @@ public class RavenwoodRuntimeEnvironmentController { private static final String MAIN_THREAD_NAME = "RavenwoodMain"; private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer"; - private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop"; private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime"; /** @@ -214,22 +214,26 @@ public class RavenwoodRuntimeEnvironmentController { Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler); } - // Some process-wide initialization. (maybe redirect stdout/stderr) - RavenwoodCommonUtils.loadJniLibrary(LIBRAVENWOOD_INITIALIZER_NAME); + // Some process-wide initialization: + // - maybe redirect stdout/stderr + // - override native system property functions + var lib = RavenwoodCommonUtils.getJniLibraryPath(LIBRAVENWOOD_INITIALIZER_NAME); + System.load(lib); + RavenwoodRuntimeNative.reloadNativeLibrary(lib); + + // Redirect stdout/stdin to the Log API. + RuntimeInit.redirectLogStreams(); dumpCommandLineArgs(); // We haven't initialized liblog yet, so directly write to System.out here. RavenwoodCommonUtils.log(TAG, "globalInitInner()"); - // Load libravenwood_sysprop before other libraries that may use SystemProperties. - var libProp = RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_SYSPROP_NAME); - System.load(libProp); - RavenwoodRuntimeNative.reloadNativeLibrary(libProp); - // Make sure libravenwood_runtime is loaded. System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME)); + Log_ravenwood.onRavenwoodRuntimeNativeReady(); + // Do the basic set up for the android sysprops. RavenwoodSystemProperties.initialize(); @@ -247,9 +251,6 @@ public class RavenwoodRuntimeEnvironmentController { // Make sure libandroid_runtime is loaded. RavenwoodNativeLoader.loadFrameworkNativeCode(); - // Redirect stdout/stdin to liblog. - RuntimeInit.redirectLogStreams(); - // Touch some references early to ensure they're <clinit>'ed Objects.requireNonNull(Build.TYPE); Objects.requireNonNull(Build.VERSION.SDK); diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java index c545baacdf3e..99b38ed4c92c 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java @@ -132,9 +132,10 @@ public class RavenwoodSystemProperties { } private static boolean isKeyReadable(String key) { - final String root = getKeyRoot(key); + // All writable keys are also readable + if (isKeyWritable(key)) return true; - if (root.startsWith("debug.")) return true; + final String root = getKeyRoot(key); // This set is carefully curated to help identify situations where a test may // accidentally depend on a default value of an obscure property whose owner hasn't @@ -145,26 +146,24 @@ public class RavenwoodSystemProperties { if (root.startsWith("soc.")) return true; if (root.startsWith("system.")) return true; - // For PropertyInvalidatedCache - if (root.startsWith("cache_key.")) return true; - - switch (key) { - case "gsm.version.baseband": - case "no.such.thing": - case "qemu.sf.lcd_density": - case "ro.bootloader": - case "ro.debuggable": - case "ro.hardware": - case "ro.hw_timeout_multiplier": - case "ro.odm.build.media_performance_class": - case "ro.sf.lcd_density": - case "ro.treble.enabled": - case "ro.vndk.version": - case "ro.icu.data.path": - return true; - } - - return false; + // All core values should be readable + if (sDefaultValues.containsKey(key)) return true; + + // Hardcoded allowlist + return switch (key) { + case "gsm.version.baseband", + "no.such.thing", + "qemu.sf.lcd_density", + "ro.bootloader", + "ro.hardware", + "ro.hw_timeout_multiplier", + "ro.odm.build.media_performance_class", + "ro.sf.lcd_density", + "ro.treble.enabled", + "ro.vndk.version", + "ro.icu.data.path" -> true; + default -> false; + }; } private static boolean isKeyWritable(String key) { diff --git a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java b/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java index c85bd23db893..7b26fe531e7e 100644 --- a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java +++ b/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java @@ -18,9 +18,13 @@ package android.util; import android.util.Log.Level; import com.android.internal.os.RuntimeInit; +import com.android.ravenwood.RavenwoodRuntimeNative; import com.android.ravenwood.common.RavenwoodCommonUtils; import java.io.PrintStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; /** * Ravenwood "native substitution" class for {@link android.util.Log}. @@ -29,7 +33,10 @@ import java.io.PrintStream; * In order to switch to this Java implementation, uncomment the @RavenwoodNativeSubstitutionClass * annotation on {@link android.util.Log}. */ -public class Log_host { +public class Log_ravenwood { + + public static final SimpleDateFormat sTimestampFormat = + new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US); public static boolean isLoggable(String tag, @Level int level) { return true; @@ -39,15 +46,6 @@ public class Log_host { if (priority < Log.INFO && !RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING) { return msg.length(); // No verbose logging. } - final String buffer; - switch (bufID) { - case Log.LOG_ID_MAIN: buffer = "main"; break; - case Log.LOG_ID_RADIO: buffer = "radio"; break; - case Log.LOG_ID_EVENTS: buffer = "event"; break; - case Log.LOG_ID_SYSTEM: buffer = "system"; break; - case Log.LOG_ID_CRASH: buffer = "crash"; break; - default: buffer = "buf:" + bufID; break; - } final String prio; switch (priority) { @@ -60,8 +58,12 @@ public class Log_host { default: prio = "prio:" + priority; break; } + String leading = sTimestampFormat.format(new Date()) + + " %-6d %-6d %s %-8s: ".formatted(getPid(), getTid(), prio, tag); + var out = getRealOut(); for (String s : msg.split("\\n")) { - getRealOut().println(String.format("logd: [%s] %s %s: %s", buffer, prio, tag, s)); + out.print(leading); + out.println(s); } return msg.length(); } @@ -81,4 +83,34 @@ public class Log_host { return System.out; } } + + /** + * PID. We need to use a JNI method to get it, but JNI isn't initially ready. + * Call {@link #onRavenwoodRuntimeNativeReady} to signal when JNI is ready, at which point + * we set this field. + * (We don't want to call native methods that may not be fully initialized even with a + * try-catch, because partially initialized JNI methods could crash the process.) + */ + private static volatile int sPid = 0; + + private static ThreadLocal<Integer> sTid = + ThreadLocal.withInitial(RavenwoodRuntimeNative::gettid); + + /** + * Call it when {@link RavenwoodRuntimeNative} is usable. + */ + public static void onRavenwoodRuntimeNativeReady() { + sPid = RavenwoodRuntimeNative.getpid(); + } + + private static int getPid() { + return sPid; + } + + private static int getTid() { + if (sPid == 0) { + return 0; // Native methods not ready yet. + } + return sTid.get(); + } } diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java index 9a78989dad55..acbcdf1926db 100644 --- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java +++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java @@ -62,6 +62,8 @@ public class RavenwoodRuntimeNative { removeSystemProperty(null); } + public static native int getpid(); + public static native int gettid(); public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { diff --git a/ravenwood/runtime-jni/ravenwood_initializer.cpp b/ravenwood/runtime-jni/ravenwood_initializer.cpp index 89fb7c3c3510..dbbc3453b2f1 100644 --- a/ravenwood/runtime-jni/ravenwood_initializer.cpp +++ b/ravenwood/runtime-jni/ravenwood_initializer.cpp @@ -14,16 +14,174 @@ * limitations under the License. */ - /* - * This file is compiled into a single SO file, which we load at the very first. - * We can do process-wide initialization here. - */ +/* + * This file is compiled into a single SO file, which we load at the very first. + * We can do process-wide initialization here. + * Please be aware that all symbols defined in this SO file will be reloaded + * as `RTLD_GLOBAL`, so make sure all functions are static except those we EXPLICITLY + * want to expose and override globally. + */ +#include <dlfcn.h> #include <fcntl.h> -#include <unistd.h> + +#include <set> #include "jni_helper.h" +// Implement a rudimentary system properties data store + +#define PROP_VALUE_MAX 92 + +namespace { + +struct prop_info { + std::string key; + mutable std::string value; + mutable uint32_t serial; + + prop_info(const char* key, const char* value) : key(key), value(value), serial(0) {} +}; + +struct prop_info_cmp { + using is_transparent = void; + bool operator()(const prop_info& lhs, const prop_info& rhs) { + return lhs.key < rhs.key; + } + bool operator()(std::string_view lhs, const prop_info& rhs) { + return lhs < rhs.key; + } + bool operator()(const prop_info& lhs, std::string_view rhs) { + return lhs.key < rhs; + } +}; + +} // namespace + +static auto& g_properties_lock = *new std::mutex; +static auto& g_properties = *new std::set<prop_info, prop_info_cmp>; + +static bool property_set(const char* key, const char* value) { + if (key == nullptr || *key == '\0') return false; + if (value == nullptr) value = ""; + bool read_only = !strncmp(key, "ro.", 3); + if (!read_only && strlen(value) >= PROP_VALUE_MAX) return false; + + std::lock_guard lock(g_properties_lock); + auto [it, success] = g_properties.emplace(key, value); + if (read_only) return success; + if (!success) { + it->value = value; + ++it->serial; + } + return true; +} + +template <typename Func> +static void property_get(const char* key, Func callback) { + std::lock_guard lock(g_properties_lock); + auto it = g_properties.find(key); + if (it != g_properties.end()) { + callback(*it); + } +} + +// Redefine the __system_property_XXX functions here so we can perform +// logging and access checks for all sysprops in native code. + +static void check_system_property_access(const char* key, bool write); + +extern "C" { + +int __system_property_set(const char* key, const char* value) { + check_system_property_access(key, true); + return property_set(key, value) ? 0 : -1; +} + +int __system_property_get(const char* key, char* value) { + check_system_property_access(key, false); + *value = '\0'; + property_get(key, [&](const prop_info& info) { + snprintf(value, PROP_VALUE_MAX, "%s", info.value.c_str()); + }); + return strlen(value); +} + +const prop_info* __system_property_find(const char* key) { + check_system_property_access(key, false); + const prop_info* pi = nullptr; + property_get(key, [&](const prop_info& info) { pi = &info; }); + return pi; +} + +void __system_property_read_callback(const prop_info* pi, + void (*callback)(void*, const char*, const char*, uint32_t), + void* cookie) { + std::lock_guard lock(g_properties_lock); + callback(cookie, pi->key.c_str(), pi->value.c_str(), pi->serial); +} + +} // extern "C" + +// ---- JNI ---- + +static JavaVM* gVM = nullptr; +static jclass gRunnerState = nullptr; +static jmethodID gCheckSystemPropertyAccess; + +static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) { + ScopedUtfChars path(env, javaPath); + // Force reload ourselves as global + dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL | RTLD_NOLOAD); +} + +// Call back into Java code to check property access +static void check_system_property_access(const char* key, bool write) { + if (gVM != nullptr && gRunnerState != nullptr) { + JNIEnv* env; + if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) { + ALOGI("%s access to system property '%s'", write ? "Write" : "Read", key); + env->CallStaticVoidMethod(gRunnerState, gCheckSystemPropertyAccess, + env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE); + return; + } + } + // Not on JVM thread, abort + LOG_ALWAYS_FATAL("Access to system property '%s' on non-JVM threads is not allowed.", key); +} + +static jstring getSystemProperty(JNIEnv* env, jclass, jstring javaKey) { + ScopedUtfChars key(env, javaKey); + jstring value = nullptr; + property_get(key.c_str(), + [&](const prop_info& info) { value = env->NewStringUTF(info.value.c_str()); }); + return value; +} + +static jboolean setSystemProperty(JNIEnv* env, jclass, jstring javaKey, jstring javaValue) { + ScopedUtfChars key(env, javaKey); + ScopedUtfChars value(env, javaValue); + return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean removeSystemProperty(JNIEnv* env, jclass, jstring javaKey) { + std::lock_guard lock(g_properties_lock); + + if (javaKey == nullptr) { + g_properties.clear(); + return JNI_TRUE; + } else { + ScopedUtfChars key(env, javaKey); + auto it = g_properties.find(key); + if (it != g_properties.end()) { + g_properties.erase(it); + return JNI_TRUE; + } else { + return JNI_FALSE; + } + } +} + static void maybeRedirectLog() { auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT"); if (ravenwoodLogOut == NULL) { @@ -42,9 +200,30 @@ static void maybeRedirectLog() { dup2(ttyFd, 2); } +static const JNINativeMethod sMethods[] = { + {"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary}, + {"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty}, + {"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty}, + {"removeSystemProperty", "(Ljava/lang/String;)Z", (void*)removeSystemProperty}, +}; + extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { ALOGI("%s: JNI_OnLoad", __FILE__); maybeRedirectLog(); + + JNIEnv* env = GetJNIEnvOrDie(vm); + gVM = vm; + + // Fetch several references for future use + gRunnerState = FindGlobalClassOrDie(env, kRunnerState); + gCheckSystemPropertyAccess = + GetStaticMethodIDOrDie(env, gRunnerState, "checkSystemPropertyAccess", + "(Ljava/lang/String;Z)V"); + + // Expose raw property methods as JNI methods + jint res = jniRegisterNativeMethods(env, kRuntimeNative, sMethods, NELEM(sMethods)); + if (res < 0) return -1; + return JNI_VERSION_1_4; } diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp index c1993f691686..bab4c7e0fd14 100644 --- a/ravenwood/runtime-jni/ravenwood_runtime.cpp +++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp @@ -174,6 +174,9 @@ static void Linux_setenv(JNIEnv* env, jobject, jstring javaName, jstring javaVal throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0)); } +static jint Linux_getpid(JNIEnv* env, jobject) { + return getpid(); +} static jint Linux_gettid(JNIEnv* env, jobject) { // gettid(2() was added in glibc 2.30 but Android uses an older version in prebuilt. @@ -196,6 +199,7 @@ static const JNINativeMethod sMethods[] = { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat }, { "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open }, { "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv }, + { "getpid", "()I", (void*)Linux_getpid }, { "gettid", "()I", (void*)Linux_gettid }, }; diff --git a/ravenwood/runtime-jni/ravenwood_sysprop.cpp b/ravenwood/runtime-jni/ravenwood_sysprop.cpp deleted file mode 100644 index a78aa8da9052..000000000000 --- a/ravenwood/runtime-jni/ravenwood_sysprop.cpp +++ /dev/null @@ -1,200 +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. - */ - -#include <dlfcn.h> - -#include <set> - -#include "jni_helper.h" - -// Implement a rudimentary system properties data store - -#define PROP_VALUE_MAX 92 - -namespace { - -struct prop_info { - std::string key; - mutable std::string value; - mutable uint32_t serial; - - prop_info(const char* key, const char* value) : key(key), value(value), serial(0) {} -}; - -struct prop_info_cmp { - using is_transparent = void; - bool operator()(const prop_info& lhs, const prop_info& rhs) { - return lhs.key < rhs.key; - } - bool operator()(std::string_view lhs, const prop_info& rhs) { - return lhs < rhs.key; - } - bool operator()(const prop_info& lhs, std::string_view rhs) { - return lhs.key < rhs; - } -}; - -} // namespace - -static auto& g_properties_lock = *new std::mutex; -static auto& g_properties = *new std::set<prop_info, prop_info_cmp>; - -static bool property_set(const char* key, const char* value) { - if (key == nullptr || *key == '\0') return false; - if (value == nullptr) value = ""; - bool read_only = !strncmp(key, "ro.", 3); - if (!read_only && strlen(value) >= PROP_VALUE_MAX) return false; - - std::lock_guard lock(g_properties_lock); - auto [it, success] = g_properties.emplace(key, value); - if (read_only) return success; - if (!success) { - it->value = value; - ++it->serial; - } - return true; -} - -template <typename Func> -static void property_get(const char* key, Func callback) { - std::lock_guard lock(g_properties_lock); - auto it = g_properties.find(key); - if (it != g_properties.end()) { - callback(*it); - } -} - -// Redefine the __system_property_XXX functions here so we can perform -// logging and access checks for all sysprops in native code. - -static void check_system_property_access(const char* key, bool write); - -extern "C" { - -int __system_property_set(const char* key, const char* value) { - check_system_property_access(key, true); - return property_set(key, value) ? 0 : -1; -} - -int __system_property_get(const char* key, char* value) { - check_system_property_access(key, false); - *value = '\0'; - property_get(key, [&](const prop_info& info) { - snprintf(value, PROP_VALUE_MAX, "%s", info.value.c_str()); - }); - return strlen(value); -} - -const prop_info* __system_property_find(const char* key) { - check_system_property_access(key, false); - const prop_info* pi = nullptr; - property_get(key, [&](const prop_info& info) { pi = &info; }); - return pi; -} - -void __system_property_read_callback(const prop_info* pi, - void (*callback)(void*, const char*, const char*, uint32_t), - void* cookie) { - std::lock_guard lock(g_properties_lock); - callback(cookie, pi->key.c_str(), pi->value.c_str(), pi->serial); -} - -} // extern "C" - -// ---- JNI ---- - -static JavaVM* gVM = nullptr; -static jclass gRunnerState = nullptr; -static jmethodID gCheckSystemPropertyAccess; - -static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) { - ScopedUtfChars path(env, javaPath); - // Force reload ourselves as global - dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL | RTLD_NOLOAD); -} - -// Call back into Java code to check property access -static void check_system_property_access(const char* key, bool write) { - if (gVM != nullptr && gRunnerState != nullptr) { - JNIEnv* env; - if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) { - ALOGI("%s access to system property '%s'", write ? "Write" : "Read", key); - env->CallStaticVoidMethod(gRunnerState, gCheckSystemPropertyAccess, - env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE); - return; - } - } - // Not on JVM thread, abort - LOG_ALWAYS_FATAL("Access to system property '%s' on non-JVM threads is not allowed.", key); -} - -static jstring getSystemProperty(JNIEnv* env, jclass, jstring javaKey) { - ScopedUtfChars key(env, javaKey); - jstring value = nullptr; - property_get(key.c_str(), - [&](const prop_info& info) { value = env->NewStringUTF(info.value.c_str()); }); - return value; -} - -static jboolean setSystemProperty(JNIEnv* env, jclass, jstring javaKey, jstring javaValue) { - ScopedUtfChars key(env, javaKey); - ScopedUtfChars value(env, javaValue); - return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE; -} - -static jboolean removeSystemProperty(JNIEnv* env, jclass, jstring javaKey) { - std::lock_guard lock(g_properties_lock); - - if (javaKey == nullptr) { - g_properties.clear(); - return JNI_TRUE; - } else { - ScopedUtfChars key(env, javaKey); - auto it = g_properties.find(key); - if (it != g_properties.end()) { - g_properties.erase(it); - return JNI_TRUE; - } else { - return JNI_FALSE; - } - } -} - -static const JNINativeMethod sMethods[] = { - {"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary}, - {"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty}, - {"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty}, - {"removeSystemProperty", "(Ljava/lang/String;)Z", (void*)removeSystemProperty}, -}; - -extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { - ALOGI("%s: JNI_OnLoad", __FILE__); - - JNIEnv* env = GetJNIEnvOrDie(vm); - gVM = vm; - - // Fetch several references for future use - gRunnerState = FindGlobalClassOrDie(env, kRunnerState); - gCheckSystemPropertyAccess = - GetStaticMethodIDOrDie(env, gRunnerState, "checkSystemPropertyAccess", - "(Ljava/lang/String;Z)V"); - - // Expose raw property methods as JNI methods - jint res = jniRegisterNativeMethods(env, kRuntimeNative, sMethods, NELEM(sMethods)); - if (res < 0) return -1; - - return JNI_VERSION_1_4; -} diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md index c29fb7f67e78..6d82a744bc4f 100644 --- a/ravenwood/test-authors.md +++ b/ravenwood/test-authors.md @@ -106,45 +106,6 @@ You can also run your new tests automatically via `TEST_MAPPING` rules like this > **Note:** There's a known bug #308854804 where `TEST_MAPPING` is not being applied, so we're currently planning to run all Ravenwood tests unconditionally in presubmit for changes to `frameworks/base/` and `cts/` until there is a better path forward. -## Strategies for feature flags - -Ravenwood supports writing tests against logic that uses feature flags through the existing `SetFlagsRule` infrastructure maintained by the feature flagging team: - -``` -import android.platform.test.flag.junit.SetFlagsRule; - -@RunWith(AndroidJUnit4.class) -public class MyCodeTest { - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(SetFlagsRule.DefaultInitValueType.NULL_DEFAULT); - - @Test - public void testEnabled() { - mSetFlagsRule.enableFlags(Flags.FLAG_MY_FLAG); - // verify test logic that depends on flag being enabled - } -``` - -This naturally composes together well with any `RavenwoodRule` that your test may need. - -While `SetFlagsRule` is generally a best-practice (as it can explicitly confirm behaviors for both "on" and "off" states), you may need to write tests that use `CheckFlagsRule` (such as when writing CTS). Ravenwood currently supports `CheckFlagsRule` by offering "all-on" and "all-off" behaviors: - -``` -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; -import android.platform.test.ravenwood.RavenwoodRule; - -@RunWith(AndroidJUnit4.class) -public class MyCodeTest { - @Rule - public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() - ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() - : DeviceFlagsValueProvider.createCheckFlagsRule(); -``` - -Ravenwood currently doesn't have knowledge of the "default" value of any flags, so using `createAllOnCheckFlagsRule()` is recommended to verify the widest possible set of behaviors. The example code above falls back to using default values from `DeviceFlagsValueProvider` when not running on Ravenwood. - ## Strategies for migration/bivalent tests Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can be dual-compiled to run on both a real Android device and under a Ravenwood environment. diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 71a0fc453a95..ab556b39f516 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2269,8 +2269,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext, shortcutType, userState.mUserId)) : userState.getShortcutTargetsLocked(shortcutType); - if (Flags.clearDefaultFromA11yShortcutTargetServiceRestore() - && shortcutType == HARDWARE) { + if (shortcutType == HARDWARE) { final String defaultService = mContext.getString(R.string.config_defaultAccessibilityService); final ComponentName defaultServiceComponent = TextUtils.isEmpty(defaultService) diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig index 444844121190..57036335bb75 100644 --- a/services/autofill/features.aconfig +++ b/services/autofill/features.aconfig @@ -31,6 +31,14 @@ flag { } flag { + name: "fill_dialog_improvements_impl" + is_exported: true + namespace: "autofill" + description: "Improvements for Fill Dialog for non-api changes" + bug: "336223371" +} + +flag { name: "fill_dialog_improvements" is_exported: true namespace: "autofill" diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig index fcb7934f7ca0..28d3caeab45a 100644 --- a/services/backup/flags.aconfig +++ b/services/backup/flags.aconfig @@ -69,3 +69,11 @@ flag { bug: "376661510" is_fixed_read_only: true } + +flag { + name: "enable_read_all_external_storage_files" + is_exported: true + namespace: "onboarding" + description: "Enables read all external storage files" + bug: "376598575" +}
\ No newline at end of file diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index 4b9065bc7f72..6069e341521e 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -88,6 +88,9 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController /** Called when a secure window shows on the virtual display. */ void onSecureWindowShown(int displayId, @NonNull ActivityInfo activityInfo); + /** Called when a secure window is no longer shown on the virtual display. */ + void onSecureWindowHidden(int displayId); + /** Returns true when an intent should be intercepted */ boolean shouldInterceptIntent(@NonNull Intent intent); } @@ -123,6 +126,9 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController private boolean mIsMirrorDisplay = false; private final CountDownLatch mDisplayIdSetLatch = new CountDownLatch(1); + // Used for detecting changes in the window flags. + private int mCurrentWindowFlags = 0; + @NonNull @GuardedBy("mGenericWindowPolicyControllerLock") private final ArraySet<Integer> mRunningUids = new ArraySet<>(); @@ -371,12 +377,19 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags, int systemWindowFlags) { int displayId = waitAndGetDisplayId(); - // The callback is fired only when windowFlags are changed. To let VirtualDevice owner - // aware that the virtual display has a secure window on top. - if ((windowFlags & FLAG_SECURE) != 0 && displayId != INVALID_DISPLAY) { + if (displayId != INVALID_DISPLAY) { + // The callback is fired only when windowFlags are changed. To let VirtualDevice owner + // aware that the virtual display has a secure window on top. // Post callback on the main thread, so it doesn't block activity launching. - mHandler.post(() -> mActivityListener.onSecureWindowShown(displayId, activityInfo)); + if ((windowFlags & FLAG_SECURE) != 0 && (mCurrentWindowFlags & FLAG_SECURE) == 0) { + mHandler.post( + () -> mActivityListener.onSecureWindowShown(displayId, activityInfo)); + } + if ((windowFlags & FLAG_SECURE) == 0 && (mCurrentWindowFlags & FLAG_SECURE) != 0) { + mHandler.post(() -> mActivityListener.onSecureWindowHidden(displayId)); + } } + mCurrentWindowFlags = windowFlags; if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE, activityInfo.packageName, diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index d4beb019e049..a1d621d8dd1f 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -44,6 +44,7 @@ import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.app.compat.CompatChanges; import android.companion.AssociationInfo; +import android.companion.AssociationRequest; import android.companion.virtual.ActivityPolicyExemption; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; @@ -155,6 +156,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private static final String PERSISTENT_ID_PREFIX_CDM_ASSOCIATION = "companion:"; + private static final List<String> DEVICE_PROFILES_ALLOWING_MIRROR_DISPLAYS = List.of( + AssociationRequest.DEVICE_PROFILE_APP_STREAMING); + /** * Timeout until {@link #launchPendingIntent} stops waiting for an activity to be launched. */ @@ -308,6 +312,17 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + @Override + public void onSecureWindowHidden(int displayId) { + if (android.companion.virtualdevice.flags.Flags.activityControlApi()) { + try { + mActivityListener.onSecureWindowHidden(displayId); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e); + } + } + } + /** * Intercepts intent when matching any of the IntentFilter of any interceptor. Returns true * if the intent matches any filter notifying the DisplayPolicyController to abort the @@ -1348,6 +1363,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return hasCustomAudioInputSupportInternal(); } + @Override + public boolean canCreateMirrorDisplays() { + return DEVICE_PROFILES_ALLOWING_MIRROR_DISPLAYS.contains(getDeviceProfile()); + } + private boolean hasCustomAudioInputSupportInternal() { if (!Flags.vdmPublicApis()) { return false; diff --git a/services/core/Android.bp b/services/core/Android.bp index f88aa9b4579c..6d60164e3d0e 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -178,7 +178,6 @@ java_library_static { static_libs: [ "android.frameworks.vibrator-V1-java", // AIDL - "android.frameworks.devicestate-V1-java", // AIDL "android.hardware.authsecret-V1.0-java", "android.hardware.authsecret-V1-java", "android.hardware.boot-V1.0-java", // HIDL @@ -239,7 +238,6 @@ java_library_static { "connectivity_flags_lib", "device_config_service_flags_java", "dreams_flags_lib", - "aconfig_flags_java", "aconfig_new_storage_flags_lib", "powerstats_flags_lib", "locksettings_flags_lib", diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 2012f5632a64..a45b715ccac6 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -55,6 +55,8 @@ import android.net.vcn.VcnConfig; import android.net.vcn.VcnManager.VcnErrorCode; import android.net.vcn.VcnManager.VcnStatusCode; import android.net.vcn.VcnUnderlyingNetworkPolicy; +import android.net.vcn.util.PersistableBundleUtils; +import android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.net.wifi.WifiInfo; import android.os.Binder; import android.os.Build; @@ -90,8 +92,6 @@ import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; import com.android.server.vcn.VcnNetworkProvider; -import com.android.server.vcn.util.PersistableBundleUtils; -import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import java.io.File; import java.io.FileDescriptor; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 71cbc10074d6..a58d850e042f 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -5845,7 +5845,7 @@ public final class ActiveServices { if (r.inSharedIsolatedProcess) { app = mAm.mProcessList.getSharedIsolatedProcess(procName, r.appInfo.uid, r.appInfo.packageName); - if (app != null) { + if (app != null && !app.isKilled()) { final IApplicationThread thread = app.getThread(); final int pid = app.getPid(); final UidRecord uidRecord = app.getUidRecord(); @@ -5870,6 +5870,8 @@ public final class ActiveServices { // If a dead object exception was thrown -- fall through to // restart the application. } + } else { + app = null; } } else { // If this service runs in an isolated process, then each time diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 01daceba5fb9..78dee3169161 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -192,6 +192,7 @@ import static com.android.systemui.shared.Flags.enableHomeDelay; import android.Manifest; import android.Manifest.permission; +import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.PermissionMethod; @@ -19228,6 +19229,11 @@ public class ActivityManagerService extends IActivityManager.Stub return mKeyFields.mCreatorPackage; } + @VisibleForTesting + public @NonNull Key getKeyFields() { + return mKeyFields; + } + public static boolean isValid(@NonNull Intent intent) { IBinder binder = intent.getCreatorToken(); IntentCreatorToken token = null; @@ -19271,9 +19277,13 @@ public class ActivityManagerService extends IActivityManager.Stub this.mFlags = intent.getFlags() & Intent.IMMUTABLE_FLAGS; ClipData clipData = intent.getClipData(); if (clipData != null) { - this.mClipDataUris = new ArrayList<>(clipData.getItemCount()); - for (int i = 0; i < clipData.getItemCount(); i++) { - this.mClipDataUris.add(clipData.getItemAt(i).getUri()); + clipData = clipData.cloneOnlyUriItems(); + if (clipData != null) { + List<Uri> clipDataUris = new ArrayList<>(); + clipData.collectUris(clipDataUris); + if (!clipDataUris.isEmpty()) { + this.mClipDataUris = clipDataUris; + } } } } @@ -19375,11 +19385,34 @@ public class ActivityManagerService extends IActivityManager.Stub String creatorPackage) { if (IntentCreatorToken.isValid(intent)) return null; IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, creatorPackage, intent); + return createOrGetIntentCreatorToken(intent, key); + } + + /** + * @hide + */ + @EnforcePermission("android.permission.INTERACT_ACROSS_USERS_FULL") + public IBinder refreshIntentCreatorToken(Intent intent) { + refreshIntentCreatorToken_enforcePermission(); + IBinder binder = intent.getCreatorToken(); + if (binder instanceof IntentCreatorToken) { + IntentCreatorToken token = (IntentCreatorToken) binder; + IntentCreatorToken.Key key = new IntentCreatorToken.Key(token.getCreatorUid(), + token.getCreatorPackage(), intent); + return createOrGetIntentCreatorToken(intent, key); + + } else { + throw new IllegalArgumentException("intent does not contain a creator token."); + } + } + + private static IntentCreatorToken createOrGetIntentCreatorToken(Intent intent, + IntentCreatorToken.Key key) { IntentCreatorToken token; synchronized (sIntentCreatorTokenCache) { WeakReference<IntentCreatorToken> ref = sIntentCreatorTokenCache.get(key); if (ref == null || ref.get() == null) { - token = new IntentCreatorToken(creatorUid, creatorPackage, intent); + token = new IntentCreatorToken(key.mCreatorUid, key.mCreatorPackage, intent); sIntentCreatorTokenCache.put(key, token.mRef); } else { token = ref.get(); diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java index a32d3cb86c24..05aeb42dbc9f 100644 --- a/services/core/java/com/android/server/am/BroadcastFilter.java +++ b/services/core/java/com/android/server/am/BroadcastFilter.java @@ -57,7 +57,6 @@ public final class BroadcastFilter extends IntentFilter { final boolean visibleToInstantApp; public final boolean exported; final int initialPriority; - final ApplicationInfo applicationInfo; BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList, String _packageName, String _featureId, String _receiverId, String _requiredPermission, @@ -74,10 +73,9 @@ public final class BroadcastFilter extends IntentFilter { instantApp = _instantApp; visibleToInstantApp = _visibleToInstantApp; exported = _exported; - applicationInfo = _applicationInfo; initialPriority = getPriority(); setPriority(calculateAdjustedPriority(owningUid, initialPriority, - applicationInfo, platformCompat)); + _applicationInfo, platformCompat)); } public @Nullable String getReceiverClassName() { @@ -91,7 +89,7 @@ public final class BroadcastFilter extends IntentFilter { } public @NonNull ApplicationInfo getApplicationInfo() { - return applicationInfo; + return receiverList.app.info; } @NeverCompile diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 7660c154efd5..e838a8dc7e7f 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -42,7 +42,6 @@ import android.aconfigd.Aconfigd.StorageReturnMessages; import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon; import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides; import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately; -import static com.android.aconfig.flags.Flags.enableSystemAconfigdRust; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -461,9 +460,8 @@ public class SettingsToPropertiesMapper { static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) { // connect to aconfigd socket LocalSocket client = new LocalSocket(); - String socketName = enableSystemAconfigdRust() - ? "aconfigd_system" : "aconfigd"; - try{ + String socketName = "aconfigd_system"; + try { client.connect(new LocalSocketAddress( socketName, LocalSocketAddress.Namespace.RESERVED)); Slog.d(TAG, "connected to aconfigd socket"); diff --git a/services/core/java/com/android/server/audio/OWNERS b/services/core/java/com/android/server/audio/OWNERS index b70de299eeea..709e4c235290 100644 --- a/services/core/java/com/android/server/audio/OWNERS +++ b/services/core/java/com/android/server/audio/OWNERS @@ -1,2 +1,3 @@ +atneya@google.com jmtrivi@google.com elaurent@google.com diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index e1bb8a1a0f21..4c5f65285a9e 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -26,13 +26,13 @@ import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_AUTO; import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_AUTO; +import static android.net.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER; import static android.os.PowerWhitelistManager.REASON_VPN; import static android.os.UserHandle.PER_USER_RANGE; import static android.telephony.CarrierConfigManager.KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT; import static android.telephony.CarrierConfigManager.KEY_PREFERRED_IKE_PROTOCOL_INT; import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; -import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER; import static java.util.Objects.requireNonNull; @@ -103,6 +103,8 @@ import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.net.ipsec.ike.exceptions.IkeTimeoutException; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnTransportInfo; +import android.net.vcn.util.MtuUtils; +import android.net.vcn.util.PersistableBundleUtils; import android.os.Binder; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -150,8 +152,6 @@ import com.android.net.module.util.NetworkStackConstants; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.net.BaseNetworkObserver; -import com.android.server.vcn.util.MtuUtils; -import com.android.server.vcn.util.PersistableBundleUtils; import libcore.io.IoUtils; diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 251344395ae3..8b9c664a31fd 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -19,16 +19,7 @@ package com.android.server.devicestate; import static android.Manifest.permission.CONTROL_DEVICE_STATE; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED; -import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN; -import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN; -import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FEATURE_DUAL_DISPLAY; -import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FEATURE_REAR_DISPLAY; -import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY; -import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY; -import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT; import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY; -import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY; import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY; import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST; import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS; @@ -53,10 +44,6 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.IProcessObserver; import android.content.Context; -import android.frameworks.devicestate.DeviceStateConfiguration; -import android.frameworks.devicestate.ErrorCode; -import android.frameworks.devicestate.IDeviceStateListener; -import android.frameworks.devicestate.IDeviceStateService; import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateInfo; import android.hardware.devicestate.DeviceStateManager; @@ -69,12 +56,9 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; -import android.os.ServiceManager; -import android.os.ServiceSpecificException; import android.os.ShellCallback; import android.os.SystemProperties; import android.os.Trace; -import android.util.LongSparseLongArray; import android.util.Slog; import android.util.SparseArray; @@ -98,7 +82,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.WeakHashMap; @@ -147,8 +130,6 @@ public final class DeviceStateManagerService extends SystemService { @NonNull private final BinderService mBinderService; @NonNull - private final HalService mHalService; - @NonNull private final OverrideRequestController mOverrideRequestController; @NonNull private final DeviceStateProviderListener mDeviceStateProviderListener; @@ -158,7 +139,7 @@ public final class DeviceStateManagerService extends SystemService { // All supported device states keyed by identifier. @GuardedBy("mLock") - private final SparseArray<DeviceState> mDeviceStates = new SparseArray<>(); + private SparseArray<DeviceState> mDeviceStates = new SparseArray<>(); // The current committed device state. Will be empty until the first device state provided by // the DeviceStateProvider is committed. @@ -196,7 +177,7 @@ public final class DeviceStateManagerService extends SystemService { @GuardedBy("mLock") private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>(); - private final Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>(); + private Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>(); private Set<Integer> mFoldedDeviceStates = new HashSet<>(); @@ -278,7 +259,6 @@ public final class DeviceStateManagerService extends SystemService { mDeviceStateProviderListener = new DeviceStateProviderListener(); mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener); mBinderService = new BinderService(); - mHalService = new HalService(); mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mDeviceStateNotificationController = new DeviceStateNotificationController( context, mHandler, @@ -292,10 +272,6 @@ public final class DeviceStateManagerService extends SystemService { @Override public void onStart() { publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService); - String halServiceName = IDeviceStateService.DESCRIPTOR + "/default"; - if (ServiceManager.isDeclared(halServiceName)) { - publishBinderService(halServiceName, mHalService); - } publishLocalService(DeviceStateManagerInternal.class, new LocalService()); if (!Flags.deviceStatePropertyMigration()) { @@ -464,11 +440,6 @@ public final class DeviceStateManagerService extends SystemService { return mBinderService; } - @VisibleForTesting - IDeviceStateService getHalBinderService() { - return mHalService; - } - private void updateSupportedStates(DeviceState[] supportedDeviceStates, @DeviceStateProvider.SupportedStatesUpdatedReason int reason) { synchronized (mLock) { @@ -1311,124 +1282,6 @@ public final class DeviceStateManagerService extends SystemService { } } - private final class HalService extends IDeviceStateService.Stub { - private final LongSparseLongArray mPublicProperties = new LongSparseLongArray(); - public HalService() { - mPublicProperties.put( - DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED, - FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED); - mPublicProperties.put( - DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN, - FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN); - mPublicProperties.put( - DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN, - FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN); - mPublicProperties.put( - PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY, - FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY); - mPublicProperties.put( - PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY, - FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY); - mPublicProperties.put( - PROPERTY_FEATURE_REAR_DISPLAY, - FEATURE_REAR_DISPLAY); - mPublicProperties.put( - PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT, - FEATURE_DUAL_DISPLAY); - } - - private final class HalBinderCallback implements IDeviceStateManagerCallback { - private final IDeviceStateListener mListener; - - private HalBinderCallback(@NonNull IDeviceStateListener listener) { - mListener = listener; - } - - @Override - public void onDeviceStateInfoChanged(DeviceStateInfo info) throws RemoteException { - DeviceStateConfiguration config = new DeviceStateConfiguration(); - Set<Integer> systemProperties = new HashSet<>( - info.currentState.getConfiguration().getSystemProperties()); - Set<Integer> physicalProperties = new HashSet<>( - info.currentState.getConfiguration().getPhysicalProperties()); - config.deviceProperties = 0; - for (Integer prop : systemProperties) { - Long publicProperty = mPublicProperties.get(prop); - if (publicProperty != null) { - config.deviceProperties |= publicProperty.longValue(); - } - } - for (Integer prop : physicalProperties) { - Long publicProperty = mPublicProperties.get(prop); - if (publicProperty != null) { - config.deviceProperties |= publicProperty.longValue(); - } - } - mListener.onDeviceStateChanged(config); - } - - @Override - public void onRequestActive(IBinder token) { - //No-op - } - - @Override - public void onRequestCanceled(IBinder token) { - //No-op - } - - @Override - public IBinder asBinder() { - return mListener.asBinder(); - } - } - - @Override - public void registerListener(IDeviceStateListener listener) throws RemoteException { - if (listener == null) { - throw new ServiceSpecificException(ErrorCode.BAD_INPUT); - } - - final int callingPid = Binder.getCallingPid(); - final long token = Binder.clearCallingIdentity(); - try { - HalBinderCallback callback = new HalBinderCallback(listener); - DeviceStateInfo info = registerProcess(callingPid, callback); - if (info != null) { - callback.onDeviceStateInfoChanged(info); - } - } catch (SecurityException e) { - throw new ServiceSpecificException(ErrorCode.ALREADY_EXISTS); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void unregisterListener(IDeviceStateListener listener) throws RemoteException { - final int callingPid = Binder.getCallingPid(); - - synchronized (mLock) { - if (mProcessRecords.contains(callingPid)) { - mProcessRecords.remove(callingPid); - return; - } - } - - throw new ServiceSpecificException(ErrorCode.BAD_INPUT); - } - - @Override - public int getInterfaceVersion() throws RemoteException { - return IDeviceStateService.VERSION; - } - - @Override - public String getInterfaceHash() throws RemoteException { - return IDeviceStateService.HASH; - } - } - /** Implementation of {@link IDeviceStateManager} published as a binder service. */ private final class BinderService extends IDeviceStateManager.Stub { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 3871f2a57f76..c3cb913db679 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1681,7 +1681,12 @@ public final class DisplayManagerService extends SystemService { if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) { return checkCallingPermission(ADD_MIRROR_DISPLAY, "canCreateMirrorDisplays"); } - return virtualDevice != null; + try { + return virtualDevice.canCreateMirrorDisplays(); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to query virtual device for permissions", e); + return false; + } } private boolean canProjectVideo(IMediaProjection projection) { diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index dabef84fec31..6ae58c432081 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -43,6 +43,7 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED; import android.annotation.Nullable; import android.content.Context; import android.graphics.Point; +import android.hardware.display.IBrightnessListener; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; import android.media.projection.IMediaProjection; @@ -183,8 +184,11 @@ public class VirtualDisplayAdapter extends DisplayAdapter { if (projection != null) { mediaProjectionCallback = new MediaProjectionCallback(appToken); } + + Callback callbackDelegate = new Callback( + callback, virtualDisplayConfig.getBrightnessListener(), mHandler); VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken, - ownerUid, ownerPackageName, surface, flags, new Callback(callback, mHandler), + ownerUid, ownerPackageName, surface, flags, callbackDelegate, projection, mediaProjectionCallback, uniqueId, virtualDisplayConfig); mVirtualDisplayDevices.put(appToken, device); @@ -337,6 +341,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { private final DisplayCutout mDisplayCutout; private final float mDefaultBrightness; private float mCurrentBrightness; + private final IBrightnessListener mBrightnessListener; public VirtualDisplayDevice(IBinder displayToken, IBinder appToken, int ownerUid, String ownerPackageName, Surface surface, int flags, @@ -354,7 +359,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate(); mDisplayCutout = virtualDisplayConfig.getDisplayCutout(); mDefaultBrightness = virtualDisplayConfig.getDefaultBrightness(); - mCurrentBrightness = mDefaultBrightness; + mCurrentBrightness = PowerManager.BRIGHTNESS_INVALID; + mBrightnessListener = virtualDisplayConfig.getBrightnessListener(); mMode = createMode(mWidth, mHeight, getRefreshRate()); mSurface = surface; mFlags = flags; @@ -464,6 +470,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { } } if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower() + && mBrightnessListener != null && BrightnessUtils.isValidBrightnessValue(brightnessState) && brightnessState != mCurrentBrightness) { mCurrentBrightness = brightnessState; @@ -661,10 +668,13 @@ public class VirtualDisplayAdapter extends DisplayAdapter { private static final int MSG_ON_REQUESTED_BRIGHTNESS_CHANGED = 3; private final IVirtualDisplayCallback mCallback; + private final IBrightnessListener mBrightnessListener; - public Callback(IVirtualDisplayCallback callback, Handler handler) { + Callback(IVirtualDisplayCallback callback, IBrightnessListener brightnessListener, + Handler handler) { super(handler.getLooper()); mCallback = callback; + mBrightnessListener = brightnessListener; } @Override @@ -681,7 +691,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mCallback.onStopped(); break; case MSG_ON_REQUESTED_BRIGHTNESS_CHANGED: - mCallback.onRequestedBrightnessChanged((Float) msg.obj); + if (mBrightnessListener != null) { + mBrightnessListener.onBrightnessChanged((Float) msg.obj); + } break; } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 6e98bff8dda5..f049ef316cb1 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -725,6 +725,13 @@ public class HdmiControlService extends SystemService { } mPowerStatusController.setPowerStatus(getInitialPowerStatus()); setProhibitMode(false); + if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) { + Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy " + + " mode."); + mHdmiCecConfig.setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, + HDMI_CEC_CONTROL_ENABLED); + setWasCecDisabledOnStandbyByLowEnergyMode(false); + } mHdmiControlEnabled = mHdmiCecConfig.getIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED); @@ -771,14 +778,6 @@ public class HdmiControlService extends SystemService { Slog.i(TAG, "Device does not support eARC."); } mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController); - if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) { - Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy " - + " mode."); - getHdmiCecConfig().setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, - HDMI_CEC_CONTROL_ENABLED); - setWasCecDisabledOnStandbyByLowEnergyMode(false); - setCecEnabled(HDMI_CEC_CONTROL_ENABLED); - } if (isCecControlEnabled()) { initializeCec(INITIATED_BY_BOOT_UP); } else { diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index af329070ec22..65d0ab337400 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -72,7 +72,7 @@ public class MediaQualityService extends SystemService { private final class BinderService extends IMediaQualityManager.Stub { @Override - public PictureProfile createPictureProfile(PictureProfile pp) { + public PictureProfile createPictureProfile(PictureProfile pp, int userId) { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); @@ -88,17 +88,17 @@ public class MediaQualityService extends SystemService { } @Override - public void updatePictureProfile(String id, PictureProfile pp) { + public void updatePictureProfile(String id, PictureProfile pp, int userId) { // TODO: implement } @Override - public void removePictureProfile(String id) { + public void removePictureProfile(String id, int userId) { // TODO: implement } @Override - public PictureProfile getPictureProfile(int type, String name) { + public PictureProfile getPictureProfile(int type, String name, int userId) { SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase(); String selection = BaseParameters.PARAMETER_TYPE + " = ? AND " @@ -205,7 +205,7 @@ public class MediaQualityService extends SystemService { } @Override - public List<PictureProfile> getPictureProfilesByPackage(String packageName) { + public List<PictureProfile> getPictureProfilesByPackage(String packageName, int userId) { String selection = BaseParameters.PARAMETER_PACKAGE + " = ?"; String[] selectionArguments = {packageName}; return getPictureProfilesBasedOnConditions(getAllPictureProfileColumns(), selection, @@ -213,12 +213,12 @@ public class MediaQualityService extends SystemService { } @Override - public List<PictureProfile> getAvailablePictureProfiles() { + public List<PictureProfile> getAvailablePictureProfiles(int userId) { return new ArrayList<>(); } @Override - public List<String> getPictureProfilePackageNames() { + public List<String> getPictureProfilePackageNames(int userId) { String [] column = {BaseParameters.PARAMETER_NAME}; List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column, null, null); @@ -250,12 +250,12 @@ public class MediaQualityService extends SystemService { } @Override - public PictureProfileHandle getPictureProfileHandle(String id) { + public PictureProfileHandle getPictureProfileHandle(String id, int userId) { return null; } @Override - public SoundProfile createSoundProfile(SoundProfile sp) { + public SoundProfile createSoundProfile(SoundProfile sp, int userId) { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); @@ -269,12 +269,12 @@ public class MediaQualityService extends SystemService { } @Override - public void updateSoundProfile(String id, SoundProfile sp) { + public void updateSoundProfile(String id, SoundProfile pp, int userId) { // TODO: implement } @Override - public void removeSoundProfile(String id) { + public void removeSoundProfile(String id, int userId) { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); String selection = BaseParameters.PARAMETER_ID + " = ?"; String[] selectionArgs = {id}; @@ -282,7 +282,7 @@ public class MediaQualityService extends SystemService { } @Override - public SoundProfile getSoundProfile(int type, String id) { + public SoundProfile getSoundProfile(int type, String id, int userId) { SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase(); String selection = BaseParameters.PARAMETER_ID + " = ?"; @@ -314,7 +314,7 @@ public class MediaQualityService extends SystemService { } @Override - public List<SoundProfile> getSoundProfilesByPackage(String packageName) { + public List<SoundProfile> getSoundProfilesByPackage(String packageName, int userId) { String selection = BaseParameters.PARAMETER_PACKAGE + " = ?"; String[] selectionArguments = {packageName}; return getSoundProfilesBasedOnConditions(getAllSoundProfileColumns(), selection, @@ -322,12 +322,12 @@ public class MediaQualityService extends SystemService { } @Override - public List<SoundProfile> getAvailableSoundProfiles() { + public List<SoundProfile> getAvailableSoundProfiles(int userId) { return new ArrayList<>(); } @Override - public List<String> getSoundProfilePackageNames() { + public List<String> getSoundProfilePackageNames(int userId) { String [] column = {BaseParameters.PARAMETER_NAME}; List<SoundProfile> soundProfiles = getSoundProfilesBasedOnConditions(column, null, null); @@ -397,70 +397,70 @@ public class MediaQualityService extends SystemService { } @Override - public void setAmbientBacklightSettings(AmbientBacklightSettings settings) { + public void setAmbientBacklightSettings(AmbientBacklightSettings settings, int userId) { } @Override - public void setAmbientBacklightEnabled(boolean enabled) { + public void setAmbientBacklightEnabled(boolean enabled, int userId) { } @Override - public List<ParamCapability> getParamCapabilities(List<String> names) { + public List<ParamCapability> getParamCapabilities(List<String> names, int userId) { return new ArrayList<>(); } @Override - public List<String> getPictureProfileAllowList() { + public List<String> getPictureProfileAllowList(int userId) { return new ArrayList<>(); } @Override - public void setPictureProfileAllowList(List<String> packages) { + public void setPictureProfileAllowList(List<String> packages, int userId) { } @Override - public List<String> getSoundProfileAllowList() { + public List<String> getSoundProfileAllowList(int userId) { return new ArrayList<>(); } @Override - public void setSoundProfileAllowList(List<String> packages) { + public void setSoundProfileAllowList(List<String> packages, int userId) { } @Override - public boolean isSupported() { + public boolean isSupported(int userId) { return false; } @Override - public void setAutoPictureQualityEnabled(boolean enabled) { + public void setAutoPictureQualityEnabled(boolean enabled, int userId) { } @Override - public boolean isAutoPictureQualityEnabled() { + public boolean isAutoPictureQualityEnabled(int userId) { return false; } @Override - public void setSuperResolutionEnabled(boolean enabled) { + public void setSuperResolutionEnabled(boolean enabled, int userId) { } @Override - public boolean isSuperResolutionEnabled() { + public boolean isSuperResolutionEnabled(int userId) { return false; } @Override - public void setAutoSoundQualityEnabled(boolean enabled) { + public void setAutoSoundQualityEnabled(boolean enabled, int userId) { } @Override - public boolean isAutoSoundQualityEnabled() { + public boolean isAutoSoundQualityEnabled(int userId) { return false; } @Override - public boolean isAmbientBacklightEnabled() { + public boolean isAmbientBacklightEnabled(int userId) { return false; } } diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java index 42b8dd71c6e2..13aab11595d2 100644 --- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java +++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java @@ -97,7 +97,7 @@ public class InstallDependencyHelper { if (missing.isEmpty()) { if (DEBUG) { - Slog.i(TAG, "No missing dependency for " + pkg); + Slog.d(TAG, "No missing dependency for " + pkg); } // No need for dependency resolution. Move to installation directly. callback.onResult(null); @@ -110,7 +110,7 @@ public class InstallDependencyHelper { } IDependencyInstallerCallback serviceCallback = - new DependencyInstallerCallbackCallOnce(handler, callback); + new DependencyInstallerCallbackCallOnce(handler, callback, userId); boolean scheduleSuccess; synchronized (mRemoteServiceLock) { scheduleSuccess = mRemoteService.run(service -> { @@ -125,7 +125,7 @@ public class InstallDependencyHelper { void notifySessionComplete(int sessionId, boolean success) { if (DEBUG) { - Slog.i(TAG, "Session complete for " + sessionId + " result: " + success); + Slog.d(TAG, "Session complete for " + sessionId + " result: " + success); } synchronized (mTrackers) { List<DependencyInstallTracker> completedTrackers = new ArrayList<>(); @@ -292,79 +292,130 @@ public class InstallDependencyHelper { private final Handler mHandler; private final CallOnceProxy mCallback; + private final int mUserId; @GuardedBy("this") - private boolean mCalled = false; + private boolean mDependencyInstallerCallbackInvoked = false; - DependencyInstallerCallbackCallOnce(Handler handler, CallOnceProxy callback) { + DependencyInstallerCallbackCallOnce(Handler handler, CallOnceProxy callback, int userId) { mHandler = handler; mCallback = callback; + mUserId = userId; } - // TODO(b/372862145): Consider turning the binder call to two-way so that we can - // throw IllegalArgumentException @Override public void onAllDependenciesResolved(int[] sessionIds) throws RemoteException { synchronized (this) { - if (mCalled) { - return; + if (mDependencyInstallerCallbackInvoked) { + throw new IllegalStateException( + "Callback is being or has been already processed"); } - mCalled = true; + mDependencyInstallerCallbackInvoked = true; } - ArraySet<Integer> set = new ArraySet<>(); - for (int i = 0; i < sessionIds.length; i++) { - if (DEBUG) { - Slog.i(TAG, "onAllDependenciesResolved called with " + sessionIds[i]); - } - set.add(sessionIds[i]); + + if (DEBUG) { + Slog.d(TAG, "onAllDependenciesResolved started"); } - DependencyInstallTracker tracker = new DependencyInstallTracker(mCallback, set); + // Before creating any tracker, validate the arguments + ArraySet<Integer> validSessionIds = validateSessionIds(sessionIds); + + if (validSessionIds.isEmpty()) { + mCallback.onResult(null); + return; + } + + // Create a tracker now if there are any pending sessions remaining. + DependencyInstallTracker tracker = new DependencyInstallTracker( + mCallback, validSessionIds); synchronized (mTrackers) { mTrackers.add(tracker); } - // In case any of the session ids have already been installed, check if they - // are valid. - mHandler.post(() -> { - if (DEBUG) { - Slog.i(TAG, "onAllDependenciesResolved cleaning up invalid sessions"); + // By the time the tracker was created, some of the sessions in validSessionIds + // could have finished. Avoid waiting for them indefinitely. + for (int sessionId : validSessionIds) { + SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId); + + // Don't wait for sessions that finished already + if (sessionInfo == null) { + notifySessionComplete(sessionId, /*success=*/ true); + } + } + } + + @Override + public void onFailureToResolveAllDependencies() throws RemoteException { + synchronized (this) { + if (mDependencyInstallerCallbackInvoked) { + throw new IllegalStateException( + "Callback is being or has been already processed"); } + mDependencyInstallerCallbackInvoked = true; + } + onError(mCallback, "Failed to resolve all dependencies automatically"); + } - for (int i = 0; i < sessionIds.length; i++) { - int sessionId = sessionIds[i]; - SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId); + private ArraySet<Integer> validateSessionIds(int[] sessionIds) { + // Before creating any tracker, validate the arguments + ArraySet<Integer> validSessionIds = new ArraySet<>(); - // Continue waiting if session exists and hasn't passed or failed yet. - if (sessionInfo != null && !sessionInfo.isSessionApplied - && !sessionInfo.isSessionFailed) { - continue; + List<SessionInfo> historicalSessions = null; + for (int i = 0; i < sessionIds.length; i++) { + int sessionId = sessionIds[i]; + SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId); + + // Continue waiting if session exists and hasn't passed or failed yet. + if (sessionInfo != null) { + if (sessionInfo.isSessionFailed) { + throwValidationError("Session already finished: " + sessionId); } - if (DEBUG) { - Slog.i(TAG, "onAllDependenciesResolved cleaning up finished" - + " session: " + sessionId); + // Wait for session to finish install if it's not already successful. + if (!sessionInfo.isSessionApplied) { + if (DEBUG) { + Slog.d(TAG, "onAllDependenciesResolved pending session: " + sessionId); + } + validSessionIds.add(sessionId); } - // If session info is null, we assume it to be success. - // TODO(b/372862145): Check historical sessions to be more precise. - boolean success = sessionInfo == null || sessionInfo.isSessionApplied; + // An applied session found. No need to check historical session anymore. + continue; + } + + if (DEBUG) { + Slog.d(TAG, "onAllDependenciesResolved cleaning up finished" + + " session: " + sessionId); + } - notifySessionComplete(sessionId, /*success=*/success); + if (historicalSessions == null) { + historicalSessions = mPackageInstallerService.getHistoricalSessions( + mUserId).getList(); } - }); + + sessionInfo = historicalSessions.stream().filter( + s -> s.sessionId == sessionId).findFirst().orElse(null); + + if (sessionInfo == null) { + throwValidationError("Failed to find session: " + sessionId); + } + + // Historical session must have been successful, otherwise throw IAE. + if (!sessionInfo.isSessionApplied) { + throwValidationError("Session already finished: " + sessionId); + } + } + + return validSessionIds; } - @Override - public void onFailureToResolveAllDependencies() throws RemoteException { + private void throwValidationError(String msg) { + // Allow client to invoke callback again. synchronized (this) { - if (mCalled) { - return; - } - onError(mCallback, "Failed to resolve all dependencies automatically"); - mCalled = true; + mDependencyInstallerCallbackInvoked = false; } + throw new IllegalArgumentException(msg); } } @@ -377,6 +428,7 @@ public class InstallDependencyHelper { // TODO(b/372862145): Determine and add support for rebooting while dependency is being resolved private static class DependencyInstallTracker { private final CallOnceProxy mCallback; + @GuardedBy("this") private final ArraySet<Integer> mPendingSessionIds; DependencyInstallTracker(CallOnceProxy callback, ArraySet<Integer> pendingSessionIds) { @@ -399,7 +451,6 @@ public class InstallDependencyHelper { if (!success) { // If one of the dependency fails, the orig session would fail too. onError(mCallback, "Failed to install all dependencies"); - // TODO(b/372862145): Abandon the rest of the pending sessions. return false; // No point in tracking anymore } @@ -411,6 +462,5 @@ public class InstallDependencyHelper { return true; // Keep on tracking } } - } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index e5e274450655..8168c5493304 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -229,7 +229,6 @@ final class InstallPackageHelper { private final SharedLibrariesImpl mSharedLibraries; private final PackageManagerServiceInjector mInjector; private final UpdateOwnershipHelper mUpdateOwnershipHelper; - private final InstallDependencyHelper mInstallDependencyHelper; private final Object mInternalLock = new Object(); @GuardedBy("mInternalLock") @@ -240,8 +239,7 @@ final class InstallPackageHelper { AppDataHelper appDataHelper, RemovePackageHelper removePackageHelper, DeletePackageHelper deletePackageHelper, - BroadcastHelper broadcastHelper, - InstallDependencyHelper installDependencyHelper) { + BroadcastHelper broadcastHelper) { mPm = pm; mInjector = pm.mInjector; mAppDataHelper = appDataHelper; @@ -255,7 +253,6 @@ final class InstallPackageHelper { mPackageAbiHelper = pm.mInjector.getAbiHelper(); mSharedLibraries = pm.mInjector.getSharedLibrariesImpl(); mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper(); - mInstallDependencyHelper = installDependencyHelper; } /** @@ -1367,10 +1364,6 @@ final class InstallPackageHelper { } } } - - for (InstallRequest request : requests) { - mInstallDependencyHelper.notifySessionComplete(request.getSessionId(), success); - } } @GuardedBy("mPm.mInstallLock") diff --git a/services/core/java/com/android/server/pm/PackageAbiHelper.java b/services/core/java/com/android/server/pm/PackageAbiHelper.java index c66a9e98c1d3..09302996d228 100644 --- a/services/core/java/com/android/server/pm/PackageAbiHelper.java +++ b/services/core/java/com/android/server/pm/PackageAbiHelper.java @@ -18,6 +18,7 @@ package com.android.server.pm; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.pm.ApplicationInfo; import android.util.ArraySet; import android.util.Pair; @@ -28,8 +29,6 @@ import com.android.server.pm.pkg.PackageStateInternal; import java.io.File; - - // TODO: Move to .parsing sub-package @VisibleForTesting public interface PackageAbiHelper { @@ -79,6 +78,23 @@ public interface PackageAbiHelper { AndroidPackage scannedPackage); /** + * Checks alignment of APK and native libraries for 16KB device + * + * @param pkg AndroidPackage for which alignment check is being done + * @param libraryRoot directory for libraries + * @param nativeLibraryRootRequiresIsa use isa + * @param cpuAbiOverride ABI override mentioned in package + * @return {ApplicationInfo.PageSizeAppCompat} if successful or error code + * which suggests undefined mode + */ + @ApplicationInfo.PageSizeAppCompatFlags + int checkPackageAlignment( + AndroidPackage pkg, + String libraryRoot, + boolean nativeLibraryRootRequiresIsa, + String cpuAbiOverride); + + /** * The native library paths and related properties that should be set on a * {@link ParsedPackage}. */ diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java index 9db4d33106ce..7229f070acf0 100644 --- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java +++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java @@ -29,6 +29,7 @@ import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.pm.ApplicationInfo; import android.content.pm.Flags; import android.content.pm.PackageManager; import android.os.Build; @@ -625,4 +626,22 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { } return adjustedAbi; } + + @Override + public int checkPackageAlignment( + AndroidPackage pkg, + String libraryRoot, + boolean nativeLibraryRootRequiresIsa, + String abiOverride) { + NativeLibraryHelper.Handle handle = null; + try { + handle = AndroidPackageUtils.createNativeLibraryHandle(pkg); + return NativeLibraryHelper.checkAlignmentForCompatMode( + handle, libraryRoot, nativeLibraryRootRequiresIsa, abiOverride); + } catch (IOException e) { + Slog.e(PackageManagerService.TAG, "Failed to check alignment of package : " + + pkg.getPackageName()); + return ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ERROR; + } + } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 47b785040d44..ceb931400d48 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -2329,6 +2329,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + mInstallDependencyHelper.notifySessionComplete(session.sessionId, success); + final File appIconFile = buildAppIconFile(session.sessionId); if (appIconFile.exists()) { appIconFile.delete(); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 8f8802e66004..891d66a5d238 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -61,6 +61,7 @@ import static com.android.server.pm.PackageManagerShellCommandDataLoader.Metadat import android.Manifest; import android.annotation.AnyThread; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -188,6 +189,7 @@ import com.android.internal.util.Preconditions; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; +import com.android.server.art.ArtManagedInstallFileHelper; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.dex.DexManager; import com.android.server.pm.pkg.AndroidPackage; @@ -852,7 +854,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (file.getName().endsWith(REMOVE_MARKER_EXTENSION)) return false; if (file.getName().endsWith(V4Signature.EXT)) return false; if (isAppMetadata(file)) return false; - if (DexMetadataHelper.isDexMetadataFile(file)) return false; + if (com.android.art.flags.Flags.artServiceV3()) { + if (ArtManagedInstallFileHelper.isArtManaged(file.getPath())) return false; + } else { + if (DexMetadataHelper.isDexMetadataFile(file)) return false; + } if (VerityUtils.isFsveritySignatureFile(file)) return false; if (ApkChecksums.isDigestOrDigestSignatureFile(file)) return false; return true; @@ -876,6 +882,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return true; } }; + private static final FileFilter sArtManagedFilter = new FileFilter() { + @Override + public boolean accept(File file) { + return !file.isDirectory() && com.android.art.flags.Flags.artServiceV3() + && ArtManagedInstallFileHelper.isArtManaged(file.getPath()); + } + }; static boolean isDataLoaderInstallation(SessionParams params) { return params.dataLoaderParams != null; @@ -1607,6 +1620,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @GuardedBy("mLock") + private List<String> getArtManagedFilePathsLocked() { + String[] names = getNamesLocked(); + ArrayList<String> result = new ArrayList<>(names.length); + for (String name : names) { + File file = new File(stageDir, name); + if (sArtManagedFilter.accept(file)) { + result.add(file.getPath()); + } + } + return result; + } + + @GuardedBy("mLock") private void enableFsVerityToAddedApksWithIdsig() throws PackageManagerException { try { List<File> files = getAddedApksLocked(); @@ -3453,7 +3479,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } final File targetFile = new File(stageDir, targetName); - resolveAndStageFileLocked(addedFile, targetFile, null); + resolveAndStageFileLocked(addedFile, targetFile, null, List.of() /* artManagedFilePaths */); mResolvedBaseFile = targetFile; // Populate package name of the apex session @@ -3546,6 +3572,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { TextUtils.formatSimple("Session: %d. No packages staged in %s", sessionId, stageDir.getAbsolutePath())); } + final List<String> artManagedFilePaths = getArtManagedFilePathsLocked(); // Verify that all staged packages are internally consistent final ArraySet<String> stagedSplits = new ArraySet<>(); @@ -3602,7 +3629,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final File targetFile = new File(stageDir, targetName); if (!isArchivedInstallation()) { final File sourceFile = new File(apk.getPath()); - resolveAndStageFileLocked(sourceFile, targetFile, apk.getSplitName()); + resolveAndStageFileLocked( + sourceFile, targetFile, apk.getSplitName(), artManagedFilePaths); } // Base is coming from session @@ -3763,7 +3791,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Inherit base if not overridden. if (mResolvedBaseFile == null) { mResolvedBaseFile = new File(appInfo.getBaseCodePath()); - inheritFileLocked(mResolvedBaseFile); + inheritFileLocked(mResolvedBaseFile, artManagedFilePaths); // Collect the requiredSplitTypes from base CollectionUtils.addAll(requiredSplitTypes, existing.getBaseRequiredSplitTypes()); } else if ((params.installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) { @@ -3782,7 +3810,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean splitRemoved = removeSplitList.contains(splitName); final boolean splitReplaced = stagedSplits.contains(splitName); if (!splitReplaced && !splitRemoved) { - inheritFileLocked(splitFile); + inheritFileLocked(splitFile, artManagedFilePaths); // Collect the requiredSplitTypes and staged splitTypes from splits CollectionUtils.addAll(requiredSplitTypes, existing.getRequiredSplitTypes()[i]); @@ -3968,6 +3996,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { DexMetadataHelper.isFsVerityRequired()); } + @FlaggedApi(com.android.art.flags.Flags.FLAG_ART_SERVICE_V3) + @GuardedBy("mLock") + private void maybeStageArtManagedInstallFilesLocked(File origFile, File targetFile, + List<String> artManagedFilePaths) throws PackageManagerException { + for (String path : ArtManagedInstallFileHelper.filterPathsForApk( + artManagedFilePaths, origFile.getPath())) { + File artManagedFile = new File(path); + if (!FileUtils.isValidExtFilename(artManagedFile.getName())) { + throw new PackageManagerException( + INSTALL_FAILED_INVALID_APK, "Invalid filename: " + artManagedFile); + } + File targetArtManagedFile = new File( + ArtManagedInstallFileHelper.getTargetPathForApk(path, targetFile.getPath())); + stageFileLocked(artManagedFile, targetArtManagedFile); + } + } + private IncrementalFileStorages getIncrementalFileStorages() { synchronized (mLock) { return mIncrementalFileStorages; @@ -4065,8 +4110,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @GuardedBy("mLock") - private void resolveAndStageFileLocked(File origFile, File targetFile, String splitName) - throws PackageManagerException { + private void resolveAndStageFileLocked(File origFile, File targetFile, String splitName, + List<String> artManagedFilePaths) throws PackageManagerException { stageFileLocked(origFile, targetFile); // Stage APK's fs-verity signature if present. @@ -4077,8 +4122,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { && VerityUtils.isFsVeritySupported()) { maybeStageV4SignatureLocked(origFile, targetFile); } - // Stage dex metadata (.dm) and corresponding fs-verity signature if present. - maybeStageDexMetadataLocked(origFile, targetFile); + // Stage ART managed install files (e.g., dex metadata (.dm)) and corresponding fs-verity + // signature if present. + if (com.android.art.flags.Flags.artServiceV3()) { + maybeStageArtManagedInstallFilesLocked(origFile, targetFile, artManagedFilePaths); + } else { + maybeStageDexMetadataLocked(origFile, targetFile); + } // Stage checksums (.digests) if present. maybeStageDigestsLocked(origFile, targetFile, splitName); } @@ -4103,7 +4153,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @GuardedBy("mLock") - private void inheritFileLocked(File origFile) { + private void inheritFileLocked(File origFile, List<String> artManagedFilePaths) { mResolvedInheritedFiles.add(origFile); maybeInheritFsveritySignatureLocked(origFile); @@ -4111,12 +4161,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { maybeInheritV4SignatureLocked(origFile); } - // Inherit the dex metadata if present. - final File dexMetadataFile = - DexMetadataHelper.findDexMetadataForFile(origFile); - if (dexMetadataFile != null) { - mResolvedInheritedFiles.add(dexMetadataFile); - maybeInheritFsveritySignatureLocked(dexMetadataFile); + // Inherit ART managed install files (e.g., dex metadata (.dm)) if present. + if (com.android.art.flags.Flags.artServiceV3()) { + for (String path : ArtManagedInstallFileHelper.filterPathsForApk( + artManagedFilePaths, origFile.getPath())) { + File artManagedFile = new File(path); + mResolvedInheritedFiles.add(artManagedFile); + maybeInheritFsveritySignatureLocked(artManagedFile); + } + } else { + final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(origFile); + if (dexMetadataFile != null) { + mResolvedInheritedFiles.add(dexMetadataFile); + maybeInheritFsveritySignatureLocked(dexMetadataFile); + } } // Inherit the digests if present. final File digestsFile = ApkChecksums.findDigestsForFile(origFile); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 040b1943b23d..ab26f024a18a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2118,8 +2118,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper, mBroadcastHelper); mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper, mRemovePackageHelper, - mDeletePackageHelper, mBroadcastHelper, - injector.getPackageInstallerService().getInstallDependencyHelper()); + mDeletePackageHelper, mBroadcastHelper); mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager, mInjector.getUserManagerInternal(), mDeletePackageHelper); diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 0802e9ee50bc..a317e1628f97 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -52,6 +52,7 @@ import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTi import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ApplicationInfo; +import android.content.pm.Flags; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; @@ -63,6 +64,8 @@ import android.os.Process; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; +import android.system.Os; +import android.system.OsConstants; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -105,6 +108,9 @@ import java.util.UUID; * Helper class that handles package scanning logic */ final class ScanPackageUtils { + + public static final int PAGE_SIZE_16KB = 16384; + /** * Just scans the package without any side effects. * @@ -418,6 +424,37 @@ final class ScanPackageUtils { + " abiOverride=" + pkgSetting.getCpuAbiOverride()); } + boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == PAGE_SIZE_16KB; + if (Flags.appCompatOption16kb() && is16KbDevice) { + // Alignment checks are used decide whether this app should run in compat mode when + // nothing was specified in manifest. Manifest should always take precedence over + // something decided by platform. + if (parsedPackage.getPageSizeAppCompatFlags() + > ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) { + pkgSetting.setPageSizeAppCompatFlags(parsedPackage.getPageSizeAppCompatFlags()); + } else { + // 16 KB is only support for 64 bit ABIs and for apps which are being installed + // Check alignment. System, Apex and Platform packages should be page-agnostic now + if ((Build.SUPPORTED_64_BIT_ABIS.length > 0) + && !isSystemApp + && !isApex + && !isPlatformPackage) { + int mode = + packageAbiHelper.checkPackageAlignment( + parsedPackage, + pkgSetting.getLegacyNativeLibraryPath(), + parsedPackage.isNativeLibraryRootRequiresIsa(), + pkgSetting.getCpuAbiOverride()); + if (mode >= ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) { + pkgSetting.setPageSizeAppCompatFlags(mode); + } else { + Slog.e(TAG, "Error occurred while checking alignment of package : " + + parsedPackage.getPackageName()); + } + } + } + } + if ((scanFlags & SCAN_BOOTING) == 0 && oldSharedUserSetting != null) { // We don't do this here during boot because we can do it all // at once after scanning all existing packages. diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java index 2cc08c327b71..a15360760e32 100644 --- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java +++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java @@ -199,7 +199,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub } void sendCallbackAdded(boolean enabled, IAdvancedProtectionCallback callback) { - Message.obtain(mHandler, MODE_CHANGED, /*enabled*/ enabled ? 1 : 0, /*unused*/ -1, + Message.obtain(mHandler, CALLBACK_ADDED, /*enabled*/ enabled ? 1 : 0, /*unused*/ -1, /*callback*/ callback) .sendToTarget(); } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java index 38bc026c473a..e191ff20a518 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java @@ -119,14 +119,14 @@ public final class ClientProfile { * If resource holder retains ownership of the resource in a challenge scenario then value is * true. */ - private boolean mResourceHolderRetain; + private boolean mResourceOwnershipRetention; private ClientProfile(Builder builder) { this.mId = builder.mId; this.mTvInputSessionId = builder.mTvInputSessionId; this.mUseCase = builder.mUseCase; this.mProcessId = builder.mProcessId; - this.mResourceHolderRetain = builder.mResourceHolderRetain; + this.mResourceOwnershipRetention = builder.mResourceOwnershipRetention; } public int getId() { @@ -149,8 +149,8 @@ public final class ClientProfile { * Returns true when the resource holder retains ownership of the resource in a challenge * scenario. */ - public boolean shouldResourceHolderRetain() { - return mResourceHolderRetain; + public boolean resourceOwnershipRetentionEnabled() { + return mResourceOwnershipRetention; } /** @@ -199,12 +199,12 @@ public final class ClientProfile { * scenario, when both resource holder and resource challenger have same processId and same * priority. * - * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or - * false (or resourceHolderRetain not set at all) to allow the resource challenger to - * acquire the resource. If not explicitly set, resourceHolderRetain is set to false. + * @param enabled Set to {@code true} to allow the resource holder to retain ownership, + * or false to allow the resource challenger to acquire the resource. + * If not explicitly set, enabled is set to {@code false}. */ - public void setResourceHolderRetain(boolean resourceHolderRetain) { - mResourceHolderRetain = resourceHolderRetain; + public void setResourceOwnershipRetention(boolean enabled) { + mResourceOwnershipRetention = enabled; } /** @@ -389,7 +389,7 @@ public final class ClientProfile { private String mTvInputSessionId; private int mUseCase; private int mProcessId; - private boolean mResourceHolderRetain = false; + private boolean mResourceOwnershipRetention = false; Builder(int id) { this.mId = id; @@ -428,12 +428,12 @@ public final class ClientProfile { /** * Builder for {@link ClientProfile}. * - * @param resourceHolderRetain the determining factor for resource ownership during - * challenger scenario. The default behavior favors the resource challenger and grants - * them ownership of the resource if resourceHolderRetain is not explicitly set to true. + * @param enabled the determining factor for resource ownership during challenger scenario. + * The default behavior favors the resource challenger and grants them ownership of + * the resource if resourceOwnershipRetention is not explicitly set to true. */ - public Builder resourceHolderRetain(boolean resourceHolderRetain) { - this.mResourceHolderRetain = resourceHolderRetain; + public Builder resourceOwnershipRetention(boolean enabled) { + this.mResourceOwnershipRetention = enabled; return this; } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index 5ae8c11f1d8f..bb192c0b4603 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -231,10 +231,10 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @Override - public void setResourceHolderRetain(int clientId, boolean resourceHolderRetain) { - enforceTrmAccessPermission("setResourceHolderRetain"); + public void setResourceOwnershipRetention(int clientId, boolean enabled) { + enforceTrmAccessPermission("setResourceOwnershipRetention"); synchronized (mLock) { - getClientProfile(clientId).setResourceHolderRetain(resourceHolderRetain); + getClientProfile(clientId).setResourceOwnershipRetention(enabled); } } @@ -1079,7 +1079,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde || ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess && !(setResourceHolderRetain() - && requestClient.shouldResourceHolderRetain())))) { + && requestClient + .resourceOwnershipRetentionEnabled())))) { frontendHandle[0] = inUseLowestPriorityFrontend.getHandle(); reclaimOwnerId[0] = inUseLowestPriorityFrontend.getOwnerClientId(); return true; @@ -1265,7 +1266,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde || ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess && !(setResourceHolderRetain() - && requestClient.shouldResourceHolderRetain())))) { + && requestClient + .resourceOwnershipRetentionEnabled())))) { lnbHandle[0] = inUseLowestPriorityLnb.getHandle(); reclaimOwnerId[0] = inUseLowestPriorityLnb.getOwnerClientId(); return true; @@ -1352,7 +1354,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde || ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess && !(setResourceHolderRetain() - && requestClient.shouldResourceHolderRetain())))) { + && requestClient + .resourceOwnershipRetentionEnabled())))) { casSessionHandle[0] = cas.getHandle(); reclaimOwnerId[0] = lowestPriorityOwnerId; return true; @@ -1439,7 +1442,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde || ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess && !(setResourceHolderRetain() - && requestClient.shouldResourceHolderRetain())))) { + && requestClient + .resourceOwnershipRetentionEnabled())))) { ciCamHandle[0] = ciCam.getHandle(); reclaimOwnerId[0] = lowestPriorityOwnerId; return true; @@ -1677,7 +1681,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde || ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess && !(setResourceHolderRetain() - && requestClient.shouldResourceHolderRetain())))) { + && requestClient + .resourceOwnershipRetentionEnabled())))) { demuxHandle[0] = inUseLowestPriorityDemux.getHandle(); reclaimOwnerId[0] = inUseLowestPriorityDemux.getOwnerClientId(); return true; diff --git a/services/core/java/com/android/server/vcn/Android.bp b/services/core/java/com/android/server/vcn/Android.bp deleted file mode 100644 index ab5da3ee45b4..000000000000 --- a/services/core/java/com/android/server/vcn/Android.bp +++ /dev/null @@ -1,13 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -filegroup { - name: "framework-vcn-util-sources", - srcs: ["util/**/*.java"], -} diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index 3392d039109a..154897eecee4 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -27,6 +27,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.vcn.VcnManager; +import android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.os.Handler; import android.os.HandlerExecutor; import android.os.ParcelUuid; @@ -45,7 +46,6 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; -import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import java.util.ArrayList; import java.util.Collections; diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index 1fba29779f0f..95acb107fd05 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -38,6 +38,7 @@ import android.net.Uri; import android.net.vcn.VcnConfig; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnManager.VcnErrorCode; +import android.net.vcn.util.LogUtils; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Message; @@ -54,7 +55,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.server.VcnManagementService.VcnCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; -import com.android.server.vcn.util.LogUtils; import java.util.Arrays; import java.util.Collections; diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 2325f358e301..9ccf0401e91d 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -30,10 +30,10 @@ import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENAB import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR; import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR; import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR; +import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static com.android.server.VcnManagementService.LOCAL_LOG; import static com.android.server.VcnManagementService.VDBG; -import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -78,6 +78,9 @@ import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnManager; import android.net.vcn.VcnTransportInfo; +import android.net.vcn.util.LogUtils; +import android.net.vcn.util.MtuUtils; +import android.net.vcn.util.OneWayBoolean; import android.net.wifi.WifiInfo; import android.os.Handler; import android.os.HandlerExecutor; @@ -103,9 +106,6 @@ import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import com.android.server.vcn.routeselection.UnderlyingNetworkController; import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback; import com.android.server.vcn.routeselection.UnderlyingNetworkRecord; -import com.android.server.vcn.util.LogUtils; -import com.android.server.vcn.util.MtuUtils; -import com.android.server.vcn.util.OneWayBoolean; import java.io.IOException; import java.net.Inet4Address; 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 16ab51e8d604..e6a1ff967508 100644 --- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java +++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java @@ -16,8 +16,9 @@ package com.android.server.vcn.routeselection; +import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; + import static com.android.internal.annotations.VisibleForTesting.Visibility; -import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.annotation.IntDef; import android.annotation.NonNull; diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java index 0d4c3736775b..86cee554be6f 100644 --- a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java @@ -16,8 +16,9 @@ package com.android.server.vcn.routeselection; +import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; + import static com.android.server.VcnManagementService.LOCAL_LOG; -import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java index d32e5cc8ef80..79c4116d0cd4 100644 --- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java @@ -23,9 +23,9 @@ import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; +import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static com.android.server.VcnManagementService.LOCAL_LOG; -import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java index 3eeeece5da46..f7a564ad5281 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java @@ -19,12 +19,12 @@ package com.android.server.vcn.routeselection; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; +import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; import static com.android.server.VcnManagementService.LOCAL_LOG; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiEntryRssiThreshold; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiExitRssiThreshold; -import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -39,6 +39,7 @@ import android.net.TelephonyNetworkSpecifier; import android.net.vcn.VcnCellUnderlyingNetworkTemplate; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnUnderlyingNetworkTemplate; +import android.net.vcn.util.LogUtils; import android.os.Handler; import android.os.HandlerExecutor; import android.os.ParcelUuid; @@ -54,7 +55,6 @@ import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.VcnContext; import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback; -import com.android.server.vcn.util.LogUtils; import java.util.ArrayList; import java.util.Collections; 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 08be11e29689..30f4ed1b9f0b 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java @@ -16,8 +16,9 @@ package com.android.server.vcn.routeselection; +import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; + import static com.android.server.VcnManagementService.LOCAL_LOG; -import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index ef33ffe509b3..776bf537837a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3238,10 +3238,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } final boolean compatEnabled = isLargeScreen && Flags.universalResizableByDefault() && appInfo.isChangeEnabled(ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT); - if (!compatEnabled && !wms.mConstants.mIgnoreActivityOrientationRequest) { - return false; - } - if (wms.mConstants.isPackageOptOutIgnoreActivityOrientationRequest(appInfo.packageName)) { + final boolean configEnabled = (isLargeScreen + ? wms.mConstants.mIgnoreActivityOrientationRequestLargeScreen + : wms.mConstants.mIgnoreActivityOrientationRequestSmallScreen) + && !wms.mConstants.isPackageOptOutIgnoreActivityOrientationRequest( + appInfo.packageName); + if (!compatEnabled && !configEnabled) { return false; } if (forActivity) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 2e2ca147dcdd..90d3834b4543 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1838,7 +1838,7 @@ class ActivityStarter { remoteTransition, null /* displayChange */); } else if (result == START_SUCCESS && mStartActivity.isState(RESUMED)) { // Do nothing if the activity is started and is resumed directly. - } else if (isStarted) { + } else if (isStarted && (mBalCode != BAL_BLOCK || mDoResume)) { // Make the collecting transition wait until this request is ready. if (transition != null) { transition.setReady(started, false); diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java index fcaab2c0f8c7..601b17c46c03 100644 --- a/services/core/java/com/android/server/wm/AppWarnings.java +++ b/services/core/java/com/android/server/wm/AppWarnings.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.Flags; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.os.Build; @@ -40,6 +41,8 @@ import android.os.Looper; import android.os.Message; import android.os.SystemProperties; import android.os.UserHandle; +import android.system.Os; +import android.system.OsConstants; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -76,6 +79,7 @@ class AppWarnings { public static final int FLAG_HIDE_COMPILE_SDK = 0x02; public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04; public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08; + public static final int FLAG_HIDE_PAGE_SIZE_MISMATCH = 0x10; /** * Map of package flags for each user. @@ -101,6 +105,7 @@ class AppWarnings { private SparseArray<UnsupportedCompileSdkDialog> mUnsupportedCompileSdkDialogs; private SparseArray<DeprecatedTargetSdkVersionDialog> mDeprecatedTargetSdkVersionDialogs; private SparseArray<DeprecatedAbiDialog> mDeprecatedAbiDialogs; + private SparseArray<PageSizeMismatchDialog> mPageSizeMismatchDialogs; /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */ private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities = @@ -250,6 +255,19 @@ class AppWarnings { } } + public void showPageSizeMismatchDialogIfNeeded(ActivityRecord r) { + // Don't show dialog if the app compat is enabled using property + final boolean appCompatEnabled = SystemProperties.getBoolean( + "bionic.linker.16kb.app_compat.enabled", false); + if (appCompatEnabled) { + return; + } + boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == 16384; + if (is16KbDevice) { + mUiHandler.showPageSizeMismatchDialog(r); + } + } + /** * Called when an activity is being started. * @@ -260,6 +278,9 @@ class AppWarnings { showUnsupportedDisplaySizeDialogIfNeeded(r); showDeprecatedTargetDialogIfNeeded(r); showDeprecatedAbiDialogIfNeeded(r); + if (Flags.appCompatOption16kb()) { + showPageSizeMismatchDialogIfNeeded(r); + } } /** @@ -457,6 +478,41 @@ class AppWarnings { } } + @UiThread + private void showPageSizeMismatchDialogUiThread(@NonNull ActivityRecord ar) { + String warning = + mAtm.mContext + .getPackageManager() + .getPageSizeCompatWarningMessage(ar.info.packageName); + if (warning == null) { + return; + } + + final int userId = getUserIdForActivity(ar); + PageSizeMismatchDialog pageSizeMismatchDialog; + if (mPageSizeMismatchDialogs != null) { + pageSizeMismatchDialog = mPageSizeMismatchDialogs.get(userId); + if (pageSizeMismatchDialog != null) { + pageSizeMismatchDialog.dismiss(); + mPageSizeMismatchDialogs.remove(userId); + } + } + if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_PAGE_SIZE_MISMATCH)) { + pageSizeMismatchDialog = + new PageSizeMismatchDialog( + AppWarnings.this, + getUiContextForActivity(ar), + ar.info.applicationInfo, + userId, + warning); + pageSizeMismatchDialog.show(); + if (mPageSizeMismatchDialogs == null) { + mPageSizeMismatchDialogs = new SparseArray<>(); + } + mPageSizeMismatchDialogs.put(userId, pageSizeMismatchDialog); + } + } + /** * Dismisses all warnings for the given package. * <p> @@ -510,6 +566,16 @@ class AppWarnings { mDeprecatedAbiDialogs.remove(userId); } } + + // Hides the "page size app compat" dialog if necessary. + if (mPageSizeMismatchDialogs != null) { + PageSizeMismatchDialog pageSizeMismatchDialog = mPageSizeMismatchDialogs.get(userId); + if (pageSizeMismatchDialog != null + && (name == null || name.equals(pageSizeMismatchDialog.mPackageName))) { + pageSizeMismatchDialog.dismiss(); + mPageSizeMismatchDialogs.remove(userId); + } + } } /** @@ -649,6 +715,7 @@ class AppWarnings { private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4; private static final int MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG = 5; private static final int MSG_SHOW_DEPRECATED_ABI_DIALOG = 6; + private static final int MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG = 7; public UiHandler(Looper looper) { super(looper, null, true); @@ -681,6 +748,10 @@ class AppWarnings { final ActivityRecord ar = (ActivityRecord) msg.obj; showDeprecatedAbiDialogUiThread(ar); } break; + case MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG: { + final ActivityRecord ar = (ActivityRecord) msg.obj; + showPageSizeMismatchDialogUiThread(ar); + } break; } } @@ -712,6 +783,11 @@ class AppWarnings { public void hideDialogsForPackage(String name, int userId) { obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, userId, 0, name).sendToTarget(); } + + public void showPageSizeMismatchDialog(ActivityRecord r) { + removeMessages(MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG); + obtainMessage(MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG, r).sendToTarget(); + } } static class BaseDialog { diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java index 506477f67bfc..cb95b3655c61 100644 --- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java +++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java @@ -65,6 +65,9 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa @NonNull private final CameraStateMonitor mCameraStateMonitor; + // TODO(b/380840084): Consider moving this to the CameraStateMonitor, and keeping track of + // all current camera activities, especially when the camera access is switching from one app to + // another. @Nullable private Task mCameraTask; @@ -123,8 +126,7 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa } @Override - public void onCameraOpened(@NonNull ActivityRecord cameraActivity, - @NonNull String cameraId) { + public void onCameraOpened(@NonNull ActivityRecord cameraActivity) { // Do not check orientation outside of the config recompute, as the app's orientation intent // might be obscured by a fullscreen override. Especially for apps which have a camera // functionality which is not the main focus of the app: while most of the app might work @@ -136,18 +138,15 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa return; } - cameraActivity.recomputeConfiguration(); - cameraActivity.getTask().dispatchTaskInfoChangedIfNeeded(/* force= */ true); - cameraActivity.ensureActivityConfiguration(/* ignoreVisibility= */ false); + mCameraTask = cameraActivity.getTask(); + updateAndDispatchCameraConfiguration(); } @Override - public boolean onCameraClosed(@NonNull String cameraId) { + public boolean canCameraBeClosed(@NonNull String cameraId) { // Top activity in the same task as the camera activity, or `null` if the task is // closed. - final ActivityRecord topActivity = mCameraTask != null - ? mCameraTask.getTopActivity(/* isFinishing */ false, /* includeOverlays */ false) - : null; + final ActivityRecord topActivity = getTopActivityFromCameraTask(); if (topActivity != null) { if (isActivityForCameraIdRefreshing(topActivity, cameraId)) { ProtoLog.v(WmProtoLogGroups.WM_DEBUG_STATES, @@ -157,10 +156,36 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa return false; } } - mCameraTask = null; return true; } + @Override + public void onCameraClosed() { + // Top activity in the same task as the camera activity, or `null` if the task is + // closed. + final ActivityRecord topActivity = getTopActivityFromCameraTask(); + // Only clean up if the camera is not running - this close signal could be from switching + // cameras (e.g. back to front camera, and vice versa). + if (topActivity == null || !mCameraStateMonitor.isCameraRunningForActivity(topActivity)) { + updateAndDispatchCameraConfiguration(); + mCameraTask = null; + } + } + + private void updateAndDispatchCameraConfiguration() { + if (mCameraTask == null) { + return; + } + final ActivityRecord activity = getTopActivityFromCameraTask(); + if (activity != null) { + activity.recomputeConfiguration(); + mCameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true); + activity.ensureActivityConfiguration(/* ignoreVisibility= */ true); + } else { + mCameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true); + } + } + boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) { return isCameraRunningAndWindowingModeEligible(activity); } @@ -262,10 +287,17 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa && !activity.isEmbedded(); } + @Nullable + private ActivityRecord getTopActivityFromCameraTask() { + return mCameraTask != null + ? mCameraTask.getTopActivity(/* isFinishing */ false, /* includeOverlays */ false) + : null; + } + private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord topActivity, @NonNull String cameraId) { if (!isTreatmentEnabledForActivity(topActivity, /* checkOrientation= */ true) - || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) { + || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) { return false; } return topActivity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested(); diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java index 3b6e30ab2a6d..3aa355869d85 100644 --- a/services/core/java/com/android/server/wm/CameraStateMonitor.java +++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java @@ -67,6 +67,10 @@ class CameraStateMonitor { // when camera connection is closed and we need to clean up our records. private final CameraIdPackageNameBiMapping mCameraIdPackageBiMapping = new CameraIdPackageNameBiMapping(); + // TODO(b/380840084): Consider making this a set of CameraId/PackageName pairs. This is to + // keep track of camera-closed signals when apps are switching camera access, so that the policy + // can restore app configuration when an app closes camera (e.g. loses camera access due to + // another app). private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>(); // TODO(b/336474959): should/can this go in the compat listeners? @@ -163,15 +167,14 @@ class CameraStateMonitor { if (cameraActivity == null || cameraActivity.getTask() == null) { return; } - notifyListenersCameraOpened(cameraActivity, cameraId); + notifyListenersCameraOpened(cameraActivity); } } - private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity, - @NonNull String cameraId) { + private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity) { for (int i = 0; i < mCameraStateListeners.size(); i++) { CameraCompatStateListener listener = mCameraStateListeners.get(i); - listener.onCameraOpened(cameraActivity, cameraId); + listener.onCameraOpened(cameraActivity); } } @@ -224,11 +227,11 @@ class CameraStateMonitor { // Already reconnected to this camera, no need to clean up. return; } - - final boolean closeSuccessfulForAllListeners = notifyListenersCameraClosed(cameraId); - if (closeSuccessfulForAllListeners) { + final boolean canClose = checkCanCloseForAllListeners(cameraId); + if (canClose) { // Finish cleaning up. mCameraIdPackageBiMapping.removeCameraId(cameraId); + notifyListenersCameraClosed(); } else { // Not ready to process closure yet - the camera activity might be refreshing. // Try again later. @@ -238,15 +241,21 @@ class CameraStateMonitor { } /** - * @return {@code false} if any listeners have reported issues processing the close. + * @return {@code false} if any listener has reported that they cannot process camera close now. */ - private boolean notifyListenersCameraClosed(@NonNull String cameraId) { - boolean closeSuccessfulForAllListeners = true; + private boolean checkCanCloseForAllListeners(@NonNull String cameraId) { for (int i = 0; i < mCameraStateListeners.size(); i++) { - closeSuccessfulForAllListeners &= mCameraStateListeners.get(i).onCameraClosed(cameraId); + if (!mCameraStateListeners.get(i).canCameraBeClosed(cameraId)) { + return false; + } } + return true; + } - return closeSuccessfulForAllListeners; + private void notifyListenersCameraClosed() { + for (int i = 0; i < mCameraStateListeners.size(); i++) { + mCameraStateListeners.get(i).onCameraClosed(); + } } // TODO(b/335165310): verify that this works in multi instance and permission dialogs. @@ -297,14 +306,18 @@ class CameraStateMonitor { /** * Notifies the compat listener that an activity has opened camera. */ - // TODO(b/336474959): try to decouple `cameraId` from the listeners. - void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId); + void onCameraOpened(@NonNull ActivityRecord cameraActivity); /** - * Notifies the compat listener that camera is closed. + * Checks whether a listener is ready to do a cleanup when camera is closed. * - * @return true if cleanup has been successful - the notifier might try again if false. + * <p>The notifier might try again if false is returned. */ // TODO(b/336474959): try to decouple `cameraId` from the listeners. - boolean onCameraClosed(@NonNull String cameraId); + boolean canCameraBeClosed(@NonNull String cameraId); + + /** + * Notifies the compat listener that camera is closed. + */ + void onCameraClosed(); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 9a33df13bb6a..fc08a91278b8 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -7061,12 +7061,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } @Override - public void setImeInputTargetRequestedVisibility(boolean visible) { + public void setImeInputTargetRequestedVisibility(boolean visible, + @NonNull ImeTracker.Token statsToken) { if (android.view.inputmethod.Flags.refactorInsetsController()) { // TODO(b/353463205) we won't have the statsToken in all cases, but should still log try { - mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible); + mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible, + statsToken); } catch (RemoteException e) { + // TODO(b/353463205) fail statsToken Slog.w(TAG, "Failed to deliver setImeInputTargetRequestedVisibility", e); } } diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index 0ccc0fe80b52..3c199dba565b 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -71,6 +71,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp @NonNull private final ActivityRefresher mActivityRefresher; + // TODO(b/380840084): Consider moving this to the CameraStateMonitor, and keeping track of + // all current camera activities, especially when the camera access is switching from one app to + // another. @Nullable private Task mCameraTask; @@ -327,8 +330,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp } @Override - public void onCameraOpened(@NonNull ActivityRecord cameraActivity, - @NonNull String cameraId) { + public void onCameraOpened(@NonNull ActivityRecord cameraActivity) { mCameraTask = cameraActivity.getTask(); // Checking whether an activity in fullscreen rather than the task as this camera // compat treatment doesn't cover activity embedding. @@ -374,16 +376,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp } @Override - public boolean onCameraClosed(@NonNull String cameraId) { - final ActivityRecord topActivity; - if (Flags.cameraCompatFullscreenPickSameTaskActivity()) { - topActivity = mCameraTask != null ? mCameraTask.getTopActivity( - /* includeFinishing= */ true, /* includeOverlays= */ false) : null; - } else { - topActivity = mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true); - } + public boolean canCameraBeClosed(@NonNull String cameraId) { + final ActivityRecord topActivity = getTopActivity(); - mCameraTask = null; if (topActivity == null) { return true; } @@ -399,6 +394,23 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp return false; } } + return true; + } + + @Override + public void onCameraClosed() { + final ActivityRecord topActivity = getTopActivity(); + + // Only clean up if the camera is not running - this close signal could be from switching + // cameras (e.g. back to front camera, and vice versa). + if (topActivity == null || !mCameraStateMonitor.isCameraRunningForActivity(topActivity)) { + // Call after getTopActivity(), as that method might use the activity from mCameraTask. + mCameraTask = null; + } + + if (topActivity == null) { + return; + } ProtoLog.v(WM_DEBUG_ORIENTATION, "Display id=%d is notified that Camera is closed, updating rotation.", @@ -406,11 +418,10 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp // Checking whether an activity in fullscreen rather than the task as this camera compat // treatment doesn't cover activity embedding. if (topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { - return true; + return; } recomputeConfigurationForCameraCompatIfNeeded(topActivity); mDisplayContent.updateOrientation(); - return true; } // TODO(b/336474959): Do we need cameraId here? @@ -430,6 +441,16 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp } } + @Nullable + private ActivityRecord getTopActivity() { + if (Flags.cameraCompatFullscreenPickSameTaskActivity()) { + return mCameraTask != null ? mCameraTask.getTopActivity( + /* includeFinishing= */ true, /* includeOverlays= */ false) : null; + } else { + return mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true); + } + } + /** * @return {@code true} if the configuration needs to be recomputed after a camera state update. */ diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 48e1c069821c..59a9e857ec63 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -40,6 +40,7 @@ import android.view.inputmethod.Flags; import android.view.inputmethod.ImeTracker; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.protolog.ProtoLog; import java.io.PrintWriter; @@ -316,15 +317,21 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { if (Flags.refactorInsetsController() && target != null) { InsetsControlTarget imeControlTarget = getControlTarget(); if (target != imeControlTarget) { - // TODO(b/353463205): start new request here? + // TODO(b/353463205): check if fromUser=false is correct here + boolean imeVisible = target.isRequestedVisible(WindowInsets.Type.ime()); + ImeTracker.Token statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_SERVER, + imeVisible ? SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED + : SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED, + false /* fromUser */); reportImeInputTargetStateToControlTarget(target, imeControlTarget, - null /* statsToken */); + statsToken); } } } private void reportImeInputTargetStateToControlTarget(@NonNull InsetsTarget imeInsetsTarget, - InsetsControlTarget controlTarget, @Nullable ImeTracker.Token statsToken) { + InsetsControlTarget controlTarget, @NonNull ImeTracker.Token statsToken) { // In case of the multi window mode, update the requestedVisibleTypes from // the controlTarget (=RemoteInsetsControlTarget) via DisplayImeController. // Then, trigger onRequestedVisibleTypesChanged for the controlTarget with @@ -333,7 +340,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { if (controlTarget != null) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); - controlTarget.setImeInputTargetRequestedVisibility(imeVisible); + controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken); } else if (imeInsetsTarget instanceof InsetsControlTarget) { // In case of a virtual display that cannot show the IME, the // controlTarget will be null here, as no controlTarget was set yet. In @@ -345,7 +352,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { if (controlTarget != imeInsetsTarget) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); - controlTarget.setImeInputTargetRequestedVisibility(imeVisible); + controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken); // not all virtual displays have an ImeInsetsSourceProvider, so it is not // guaranteed that the IME will be started when the control target reports its // requested visibility back. Thus, invoking the listener here. @@ -390,9 +397,9 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { WindowToken imeToken = mWindowContainer.asWindowState() != null ? mWindowContainer.asWindowState().mToken : null; final var rotationController = mDisplayContent.getAsyncRotationController(); - if ((rotationController != null && rotationController.isTargetToken(imeToken)) - || (imeToken != null && imeToken.isSelfAnimating( - 0 /* flags */, SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM))) { + if ((rotationController != null && rotationController.isTargetToken(imeToken)) || ( + imeToken != null && imeToken.isSelfAnimating(0 /* flags */, + SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM))) { // Skip reporting IME drawn state when the control target is in fixed // rotation, AsyncRotationController will report after the animation finished. return; diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java index 7043aacfc44d..cee49676eeae 100644 --- a/services/core/java/com/android/server/wm/InsetsControlTarget.java +++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import android.annotation.NonNull; import android.annotation.Nullable; import android.inputmethodservice.InputMethodService; import android.os.IBinder; @@ -90,8 +91,10 @@ interface InsetsControlTarget extends InsetsTarget { /** * @param visible the requested visibility for the IME, used for * {@link com.android.server.wm.DisplayContent.RemoteInsetsControlTarget} + * @param statsToken the token tracking the current IME request */ - default void setImeInputTargetRequestedVisibility(boolean visible) { + default void setImeInputTargetRequestedVisibility(boolean visible, + @NonNull ImeTracker.Token statsToken) { } /** Returns {@code target.getWindow()}, or null if {@code target} is {@code null}. */ diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS index c7667b40ddd9..98521d36ad44 100644 --- a/services/core/java/com/android/server/wm/OWNERS +++ b/services/core/java/com/android/server/wm/OWNERS @@ -20,6 +20,7 @@ wilsonshih@google.com jiamingliu@google.com pdwilliams@google.com charlesccchen@google.com +marziana@google.com # Files related to background activity launches per-file Background*Start* = set noparent diff --git a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java new file mode 100644 index 000000000000..8c50913dd563 --- /dev/null +++ b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.text.Html.FROM_HTML_MODE_COMPACT; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.text.Html; +import android.view.Window; +import android.view.WindowManager; + +import com.android.internal.R; + +/** + * Show warning dialog when + * - Uncompressed libs inside apk are not aligned to page size + * - ELF Load segments are not page size aligned + * This dialog will be shown everytime when app is launched. Apps can choose to override + * by setting compat mode pageSizeCompat="enabled" in manifest or "disabled" to opt out. + * Both cases will skip the PageSizeMismatchDialog. + * + */ +class PageSizeMismatchDialog extends AppWarnings.BaseDialog { + PageSizeMismatchDialog( + final AppWarnings manager, + Context context, + ApplicationInfo appInfo, + int userId, + String warning) { + super(manager, context, appInfo.packageName, userId); + + final PackageManager pm = context.getPackageManager(); + final CharSequence label = + appInfo.loadSafeLabel( + pm, + PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, + PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE + | PackageItemInfo.SAFE_LABEL_FLAG_TRIM); + + final AlertDialog.Builder builder = + new AlertDialog.Builder(context) + .setPositiveButton( + R.string.ok, + (dialog, which) -> {/* Do nothing */}) + .setMessage(Html.fromHtml(warning, FROM_HTML_MODE_COMPACT)) + .setTitle(label); + + mDialog = builder.create(); + mDialog.create(); + + final Window window = mDialog.getWindow(); + window.setType(WindowManager.LayoutParams.TYPE_PHONE); + } +} diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java index 31ca24c46f4b..3ad9b62ef058 100644 --- a/services/core/java/com/android/server/wm/WindowManagerConstants.java +++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java @@ -36,7 +36,15 @@ import java.util.concurrent.Executor; */ final class WindowManagerConstants { - /** The orientation of activity will be always "unspecified" except for game apps. */ + /** + * The orientation of activity will be always "unspecified" except for game apps. + * <p>Possible values: + * <ul> + * <li>false: applies to no apps (default)</li> + * <li>true: applies to all apps</li> + * <li>large: applies to all apps but only on large screens</li> + * </ul> + */ private static final String KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST = "ignore_activity_orientation_request"; @@ -69,7 +77,8 @@ final class WindowManagerConstants { boolean mSystemGestureExcludedByPreQStickyImmersive; /** @see #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST */ - boolean mIgnoreActivityOrientationRequest; + boolean mIgnoreActivityOrientationRequestLargeScreen; + boolean mIgnoreActivityOrientationRequestSmallScreen; /** @see #KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST */ private ArraySet<String> mOptOutIgnoreActivityOrientationRequestPackages; @@ -177,9 +186,12 @@ final class WindowManagerConstants { } private void updateIgnoreActivityOrientationRequest() { - mIgnoreActivityOrientationRequest = mDeviceConfig.getBoolean( + final String value = mDeviceConfig.getProperty( DeviceConfig.NAMESPACE_WINDOW_MANAGER, - KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST, false); + KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST); + mIgnoreActivityOrientationRequestSmallScreen = Boolean.parseBoolean(value); + mIgnoreActivityOrientationRequestLargeScreen = mIgnoreActivityOrientationRequestSmallScreen + || ("large".equals(value)); } private void updateOptOutIgnoreActivityOrientationRequestList() { @@ -196,8 +208,7 @@ final class WindowManagerConstants { } boolean isPackageOptOutIgnoreActivityOrientationRequest(String packageName) { - return mIgnoreActivityOrientationRequest - && mOptOutIgnoreActivityOrientationRequestPackages != null + return mOptOutIgnoreActivityOrientationRequestPackages != null && mOptOutIgnoreActivityOrientationRequestPackages.contains(packageName); } @@ -211,7 +222,8 @@ final class WindowManagerConstants { pw.print(" "); pw.print(KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE); pw.print("="); pw.println(mSystemGestureExcludedByPreQStickyImmersive); pw.print(" "); pw.print(KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST); - pw.print("="); pw.println(mIgnoreActivityOrientationRequest); + pw.print("="); pw.println(mIgnoreActivityOrientationRequestSmallScreen ? "true" + : mIgnoreActivityOrientationRequestLargeScreen ? "large" : "false"); if (mOptOutIgnoreActivityOrientationRequestPackages != null) { pw.print(" "); pw.print(KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST); pw.print("="); pw.println(mOptOutIgnoreActivityOrientationRequestPackages); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a0c0b9836507..7a53ccf6110c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -4668,21 +4668,25 @@ public class WindowManagerService extends IWindowManager.Stub @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS) @Override - public void updateDisplayWindowRequestedVisibleTypes( - int displayId, @InsetsType int requestedVisibleTypes) { + public void updateDisplayWindowRequestedVisibleTypes(int displayId, + @InsetsType int requestedVisibleTypes, @Nullable ImeTracker.Token statsToken) { updateDisplayWindowRequestedVisibleTypes_enforcePermission(); final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { final DisplayContent dc = mRoot.getDisplayContent(displayId); if (dc == null || dc.mRemoteInsetsControlTarget == null) { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES); return; } + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES); dc.mRemoteInsetsControlTarget.setRequestedVisibleTypes(requestedVisibleTypes); // TODO(b/353463205) the statsToken shouldn't be null as it is used later in the - // IME provider. Check if we have to create a new request here + // IME provider. Check if we have to create a new request here, if null. dc.getInsetsStateController().onRequestedVisibleTypesChanged( - dc.mRemoteInsetsControlTarget, null /* statsToken */); + dc.mRemoteInsetsControlTarget, statsToken); } } finally { Binder.restoreCallingIdentity(origId); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 112414e6c4df..fde6ce26cb68 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -16,6 +16,7 @@ package com.android.server; +import static android.media.tv.flags.Flags.mediaQualityFw; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; @@ -2616,9 +2617,11 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } - t.traceBegin("StartMediaQuality"); - mSystemServiceManager.startService(MediaQualityService.class); - t.traceEnd(); + if (mediaQualityFw() && isTv) { + t.traceBegin("StartMediaQuality"); + mSystemServiceManager.startService(MediaQualityService.class); + t.traceEnd(); + } if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) { t.traceBegin("StartMediaResourceMonitor"); diff --git a/services/manifest_services.xml b/services/manifest_services.xml index 945720544991..114fe324f016 100644 --- a/services/manifest_services.xml +++ b/services/manifest_services.xml @@ -4,9 +4,4 @@ <version>2</version> <fqname>IAltitudeService/default</fqname> </hal> - <hal format="aidl"> - <name>android.frameworks.devicestate</name> - <version>1</version> - <fqname>IDeviceStateService/default</fqname> - </hal> </manifest> diff --git a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java index 9772ef929eae..5db6a8f12e52 100644 --- a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java +++ b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java @@ -145,6 +145,7 @@ import android.net.ipsec.ike.exceptions.IkeNonProtocolException; import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.net.ipsec.ike.exceptions.IkeTimeoutException; import android.net.vcn.VcnTransportInfo; +import android.net.vcn.util.PersistableBundleUtils; import android.net.wifi.WifiInfo; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -179,7 +180,6 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.server.DeviceIdleInternal; import com.android.server.IpSecService; import com.android.server.VpnTestBase; -import com.android.server.vcn.util.PersistableBundleUtils; import org.junit.Before; import org.junit.Test; diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 47e96d378149..1b56b3ff0654 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -123,7 +123,6 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserManager; import android.os.test.FakePermissionEnforcer; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; @@ -1387,21 +1386,23 @@ public class DisplayManagerServiceTest { } /** - * Tests that it's not allowed to create an auto-mirror virtual display without - * CAPTURE_VIDEO_OUTPUT permission or a virtual device that can mirror displays + * Tests that it is not allowed to create an auto-mirror virtual display for a virtual device + * without ADD_MIRROR_DISPLAY permission / without the mirror display capability. */ - @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_ENABLE_LIMITED_VDM_ROLE) @Test - public void createAutoMirrorDisplay_withoutPermissionOrAllowedVirtualDevice_throwsException() - throws Exception { + public void createAutoMirrorDisplay_withoutPermission_throwsException() throws Exception { DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); DisplayManagerInternal localService = displayManager.new LocalService(); registerDefaultDisplays(displayManager); when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); IVirtualDevice virtualDevice = mock(IVirtualDevice.class); when(virtualDevice.getDeviceId()).thenReturn(1); - when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY)) - .thenReturn(PackageManager.PERMISSION_DENIED); + if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) { + when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY)) + .thenReturn(PackageManager.PERMISSION_DENIED); + } else { + when(virtualDevice.canCreateMirrorDisplays()).thenReturn(false); + } when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true); when(mContext.checkCallingPermission(CAPTURE_VIDEO_OUTPUT)).thenReturn( PackageManager.PERMISSION_DENIED); @@ -1432,8 +1433,12 @@ public class DisplayManagerServiceTest { when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); IVirtualDevice virtualDevice = mock(IVirtualDevice.class); when(virtualDevice.getDeviceId()).thenReturn(1); - when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY)) - .thenReturn(PackageManager.PERMISSION_GRANTED); + if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) { + when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + } else { + when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true); + } when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true); // Create an auto-mirror virtual display using a virtual device. @@ -1466,8 +1471,12 @@ public class DisplayManagerServiceTest { when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); IVirtualDevice virtualDevice = mock(IVirtualDevice.class); when(virtualDevice.getDeviceId()).thenReturn(1); - when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY)) - .thenReturn(PackageManager.PERMISSION_GRANTED); + if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) { + when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + } else { + when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true); + } when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true); // Create an auto-mirror virtual display using a virtual device. @@ -1534,8 +1543,12 @@ public class DisplayManagerServiceTest { when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); IVirtualDevice virtualDevice = mock(IVirtualDevice.class); when(virtualDevice.getDeviceId()).thenReturn(1); - when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY)) - .thenReturn(PackageManager.PERMISSION_GRANTED); + if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) { + when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + } else { + when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true); + } when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true); when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY)) .thenReturn(PackageManager.PERMISSION_GRANTED); @@ -1571,8 +1584,12 @@ public class DisplayManagerServiceTest { when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); IVirtualDevice virtualDevice = mock(IVirtualDevice.class); when(virtualDevice.getDeviceId()).thenReturn(1); - when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY)) - .thenReturn(PackageManager.PERMISSION_GRANTED); + if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) { + when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + } else { + when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true); + } when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true); // Create an auto-mirror virtual display using a virtual device. diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java index e0b0fec380dd..3ac7fb0dbe53 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java @@ -335,10 +335,6 @@ public class VirtualDisplayAdapterTest { @Override public void onStopped() { } - - @Override - public void onRequestedBrightnessChanged(float brightness) { - } }; } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java index d9332ec05697..6defadf44d05 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -89,6 +89,7 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.SyncNotedAppOp; import android.app.backup.BackupAnnotations; +import android.content.ClipData; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -100,6 +101,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ServiceInfo; +import android.graphics.Rect; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; @@ -204,6 +206,16 @@ public class ActivityManagerServiceTest { private static final String TEST_AUTHORITY = "test_authority"; private static final String TEST_MIME_TYPE = "application/test_type"; + private static final Uri TEST_URI = Uri.parse("content://com.example/people"); + private static final int TEST_CREATOR_UID = 12345; + private static final String TEST_CREATOR_PACKAGE = "android.content.testCreatorPackage"; + private static final String TEST_TYPE = "testType"; + private static final String TEST_IDENTIFIER = "testIdentifier"; + private static final String TEST_CATEGORY = "testCategory"; + private static final String TEST_LAUNCH_TOKEN = "testLaunchToken"; + private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE, + "TestClass"); + private static final int ALL_SET_FLAG = 0xFFFFFFFF; private static final int[] UID_RECORD_CHANGES = { UidRecord.CHANGE_PROCSTATE, @@ -1414,6 +1426,34 @@ public class ActivityManagerServiceTest { & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0); } + @Test + public void testUseCloneForCreatorTokenAndOriginalIntent_createSameIntentCreatorToken() { + Intent testIntent = new Intent(TEST_ACTION1) + .setComponent(TEST_COMPONENT) + .setDataAndType(TEST_URI, TEST_TYPE) + .setIdentifier(TEST_IDENTIFIER) + .addCategory(TEST_CATEGORY); + testIntent.setOriginalIntent(new Intent(TEST_ACTION2)); + testIntent.setSelector(new Intent(TEST_ACTION3)); + testIntent.setSourceBounds(new Rect(0, 0, 100, 100)); + testIntent.setLaunchToken(TEST_LAUNCH_TOKEN); + testIntent.addFlags(ALL_SET_FLAG) + .addExtendedFlags(ALL_SET_FLAG); + ClipData testClipData = ClipData.newHtmlText("label", "text", "<html/>"); + testClipData.addItem(new ClipData.Item(new Intent(TEST_ACTION1))); + testClipData.addItem(new ClipData.Item(TEST_URI)); + testIntent.putExtra(TEST_EXTRA_KEY1, TEST_EXTRA_VALUE1); + + ActivityManagerService.IntentCreatorToken tokenForFullIntent = + new ActivityManagerService.IntentCreatorToken(TEST_CREATOR_UID, + TEST_CREATOR_PACKAGE, testIntent); + ActivityManagerService.IntentCreatorToken tokenForCloneIntent = + new ActivityManagerService.IntentCreatorToken(TEST_CREATOR_UID, + TEST_CREATOR_PACKAGE, testIntent.cloneForCreatorToken()); + + assertThat(tokenForFullIntent.getKeyFields()).isEqualTo(tokenForCloneIntent.getKeyFields()); + } + private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq, long lastNetworkUpdatedProcStateSeq, final long procStateSeqToWait, boolean expectWait) throws Exception { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java index 405024cc0e34..769f071e3ddc 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java @@ -134,7 +134,7 @@ public class ApexManagerTest { mMockSystem.system().validateFinalState(); mInstallPackageHelper = new InstallPackageHelper(mPmService, mock(AppDataHelper.class), mock(RemovePackageHelper.class), mock(DeletePackageHelper.class), - mock(BroadcastHelper.class), mock(InstallDependencyHelper.class)); + mock(BroadcastHelper.class)); } @NonNull diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java index 005ceee703a8..c7fad76969bf 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java @@ -29,8 +29,6 @@ import android.os.UserHandle; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; -import android.platform.test.ravenwood.RavenwoodRule; import android.provider.DeviceConfig; import androidx.test.InstrumentationRegistry; @@ -59,13 +57,8 @@ import java.util.regex.Pattern; @LargeTest @android.platform.test.annotations.DisabledOnRavenwood(reason = "Integration test") public class CpuPowerStatsCollectorValidationTest { - @Rule(order = 0) - public final RavenwoodRule mRavenwood = new RavenwoodRule(); - - @Rule(order = 1) - public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood() - ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() - : DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final int WORK_DURATION_MS = 2000; private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp"; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java index ef0b570a1354..1ff347f93da2 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java @@ -31,8 +31,6 @@ import android.os.Process; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; -import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; @@ -58,18 +56,13 @@ import java.util.Collection; @SuppressWarnings("GuardedBy") public class SystemServicePowerCalculatorTest { @Rule(order = 0) - public final RavenwoodRule mRavenwood = new RavenwoodRule(); - - @Rule(order = 1) - public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood() - ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() - : DeviceFlagsValueProvider.createCheckFlagsRule(); + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final double PRECISION = 0.000001; private static final int APP_UID1 = 100; private static final int APP_UID2 = 200; - @Rule(order = 2) + @Rule(order = 1) public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720) .setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200}) diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 0c058df35195..009ce88cfd6c 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -897,16 +897,6 @@ test_module_config { } test_module_config { - name: "FrameworksServicesTests_server_accessibility", - base: "FrameworksServicesTests", - test_suites: [ - "automotive-tests", - "device-tests", - ], - include_filters: ["com.android.server.accessibility"], -} - -test_module_config { name: "FrameworksServicesTests_server_binarytransparencyservicetest", base: "FrameworksServicesTests", test_suites: [ diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 27de7644f6b2..08fdaf44f913 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -1815,9 +1815,7 @@ public class AccessibilityManagerServiceTest { } @Test - @EnableFlags({ - android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE, - Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE}) + @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE) public void restoreShortcutTargets_hardware_alreadyHadDefaultService_doesNotClear() { final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME; mTestableContext.getOrCreateTestableResources().addOverride( @@ -1843,9 +1841,7 @@ public class AccessibilityManagerServiceTest { } @Test - @EnableFlags({ - android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE, - Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE}) + @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE) public void restoreShortcutTargets_hardware_didNotHaveDefaultService_clearsDefaultService() { final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME; final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(); @@ -1870,9 +1866,7 @@ public class AccessibilityManagerServiceTest { } @Test - @EnableFlags({ - android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE, - Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE}) + @EnableFlags(Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE) public void restoreShortcutTargets_hardware_nullSetting_clearsDefaultService() { final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME; final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java index 1a593dd9baba..42b7f4bcda7b 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java @@ -755,6 +755,7 @@ public class GenericWindowPolicyControllerTest { verify(mActivityListener, after(TIMEOUT_MILLIS).never()) .onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo)); + verify(mActivityListener, never()).onSecureWindowHidden(eq(DISPLAY_ID)); verify(mActivityListener, never()) .onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any()); } @@ -776,6 +777,10 @@ public class GenericWindowPolicyControllerTest { .onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo)); verify(mActivityListener, after(TIMEOUT_MILLIS).never()) .onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any()); + + assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, 0)).isTrue(); + + verify(mActivityListener, timeout(TIMEOUT_MILLIS)).onSecureWindowHidden(eq(DISPLAY_ID)); } @Test @@ -794,6 +799,7 @@ public class GenericWindowPolicyControllerTest { verify(mActivityListener, after(TIMEOUT_MILLIS).never()) .onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo)); + verify(mActivityListener, never()).onSecureWindowHidden(eq(DISPLAY_ID)); verify(mActivityListener, never()) .onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any()); } diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index 5127b2d11e44..ab5a5a9bf2fe 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -24,14 +24,9 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertThrows; import android.content.Context; -import android.frameworks.devicestate.DeviceStateConfiguration; -import android.frameworks.devicestate.ErrorCode; -import android.frameworks.devicestate.IDeviceStateListener; -import android.frameworks.devicestate.IDeviceStateService; import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateInfo; import android.hardware.devicestate.DeviceStateRequest; @@ -39,7 +34,6 @@ import android.hardware.devicestate.IDeviceStateManagerCallback; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; -import android.os.ServiceSpecificException; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; @@ -342,53 +336,6 @@ public final class DeviceStateManagerServiceTest { } @Test - public void halRegisterUnregisterCallback() throws RemoteException { - IDeviceStateService halService = mService.getHalBinderService(); - IDeviceStateListener halListener = new IDeviceStateListener.Stub() { - @Override - public void onDeviceStateChanged(DeviceStateConfiguration deviceState) { } - - @Override - public int getInterfaceVersion() { - return IDeviceStateListener.VERSION; - } - - @Override - public String getInterfaceHash() { - return IDeviceStateListener.HASH; - } - }; - - int errorCode = ErrorCode.OK; - try { - halService.unregisterListener(halListener); - } catch(ServiceSpecificException e) { - errorCode = e.errorCode; - } - assertEquals(errorCode, ErrorCode.BAD_INPUT); - - errorCode = ErrorCode.OK; - try { - halService.unregisterListener(null); - } catch(ServiceSpecificException e) { - errorCode = e.errorCode; - } - assertEquals(errorCode, ErrorCode.BAD_INPUT); - - halService.registerListener(halListener); - - errorCode = ErrorCode.OK; - try { - halService.registerListener(halListener); - } catch (ServiceSpecificException e) { - errorCode = e.errorCode; - } - assertEquals(errorCode, ErrorCode.ALREADY_EXISTS); - - halService.unregisterListener(halListener); - } - - @Test public void registerCallback() throws RemoteException { final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java index 5852af780b8b..d20f73d3c834 100644 --- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java @@ -506,9 +506,9 @@ public class TunerResourceManagerServiceTest { assertThat(client0.getProfile().getInUseFrontendHandles()) .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle))); - // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder - // (client0) to maintain ownership such as requester will not get the resources. - client1.getProfile().setResourceHolderRetain(true); + // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource + // Holder (client0) to maintain ownership such as requester will not get the resources. + client1.getProfile().setResourceOwnershipRetention(true); request = tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle)) @@ -520,10 +520,10 @@ public class TunerResourceManagerServiceTest { .isEqualTo(client0.getId()); assertThat(client0.isReclaimed()).isFalse(); - // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource + // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the // resources. - client1.getProfile().setResourceHolderRetain(false); + client1.getProfile().setResourceOwnershipRetention(false); assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle)) .isTrue(); @@ -645,9 +645,9 @@ public class TunerResourceManagerServiceTest { .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId()))); assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue(); - // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder - // to maintain ownership such as requester (client1) will not get the resources. - client1.getProfile().setResourceHolderRetain(true); + // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource + // Holder to maintain ownership such as requester (client1) will not get the resources. + client1.getProfile().setResourceOwnershipRetention(true); request = casSessionRequest(client1.getId(), 1); assertThat( @@ -663,10 +663,10 @@ public class TunerResourceManagerServiceTest { assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue(); assertThat(client0.isReclaimed()).isFalse(); - // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource + // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the // resources. - client1.getProfile().setResourceHolderRetain(false); + client1.getProfile().setResourceOwnershipRetention(false); assertThat( mTunerResourceManagerService.requestCasSessionInternal(request, casSessionHandle)) @@ -759,9 +759,9 @@ public class TunerResourceManagerServiceTest { .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId()))); assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue(); - // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder - // (client0) to maintain ownership such as requester will not get the resources. - client1.getProfile().setResourceHolderRetain(true); + // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource + // Holder (client0) to maintain ownership such as requester will not get the resources. + client1.getProfile().setResourceOwnershipRetention(true); request = tunerCiCamRequest(client1.getId(), 1); assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle)) @@ -776,10 +776,10 @@ public class TunerResourceManagerServiceTest { assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue(); assertThat(client0.isReclaimed()).isFalse(); - // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource + // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the // resources. - client1.getProfile().setResourceHolderRetain(false); + client1.getProfile().setResourceOwnershipRetention(false); assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle)) .isTrue(); @@ -927,9 +927,9 @@ public class TunerResourceManagerServiceTest { assertThat(client0.getProfile().getInUseLnbHandles()) .isEqualTo(new HashSet<Long>(Arrays.asList(lnbHandles[0]))); - // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder - // (client0) to maintain ownership such as requester will not get the resources. - client1.getProfile().setResourceHolderRetain(true); + // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource + // Holder (client0) to maintain ownership such as requester will not get the resources. + client1.getProfile().setResourceOwnershipRetention(true); request = new TunerLnbRequest(); request.clientId = client1.getId(); @@ -942,10 +942,10 @@ public class TunerResourceManagerServiceTest { assertThat(client0.isReclaimed()).isFalse(); assertThat(client1.getProfile().getInUseLnbHandles().size()).isEqualTo(0); - // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource + // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the // resources. - client1.getProfile().setResourceHolderRetain(false); + client1.getProfile().setResourceOwnershipRetention(false); assertThat(mTunerResourceManagerService.requestLnbInternal(request, lnbHandle)).isTrue(); assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 863f42f3d1c1..90bf1d36a4ce 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -6884,10 +6884,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testReadPolicyXml_backupRestoreLogging() throws Exception { BackupRestoreEventLogger logger = mock(BackupRestoreEventLogger.class); + if (ActivityManager.getCurrentUser() != UserHandle.USER_SYSTEM) { + // By default, the ZenModeHelper only has a configuration for the system user. + // If the current user is not the system user, the user must be updated. + mService.mZenModeHelper.onUserSwitched(ActivityManager.getCurrentUser()); + } UserInfo ui = new UserInfo(ActivityManager.getCurrentUser(), "Clone", UserInfo.FLAG_FULL); ui.userType = USER_TYPE_FULL_SYSTEM; when(mUmInternal.getUserInfo(ActivityManager.getCurrentUser())).thenReturn(ui); - when(mPermissionHelper.getNotificationPermissionValues(0)).thenReturn(new ArrayMap<>()); + when(mPermissionHelper.getNotificationPermissionValues(ActivityManager.getCurrentUser())) + .thenReturn(new ArrayMap<>()); TypedXmlSerializer serializer = Xml.newFastSerializer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); @@ -8931,8 +8937,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testAreBubblesEnabled_false() throws Exception { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.NOTIFICATION_BUBBLES, 0); + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.NOTIFICATION_BUBBLES, 0, UserHandle.getUserId(mUid)); mService.mPreferencesHelper.updateBubblesEnabled(); assertFalse(mBinderService.areBubblesEnabled(UserHandle.getUserHandleForUid(mUid))); } @@ -13310,6 +13316,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPermissionHelper.hasPermission(UID_N_MR1)).thenReturn(false); when(mPermissionHelper.hasPermission(UID_P)).thenReturn(true); + enableInteractAcrossUsers(); assertThat(mBinderService.getPackagesBypassingDnd(UserHandle.getUserId(UID_P)).getList()) .containsExactly(new ZenBypassingApp(PKG_P, false)); } diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml index 3fc7c7692abc..787f4e85c012 100644 --- a/services/tests/wmtests/res/xml/bookmarks.xml +++ b/services/tests/wmtests/res/xml/bookmarks.xml @@ -22,7 +22,7 @@ androidprv:modifierState="META" /> <bookmark category="android.intent.category.APP_CONTACTS" - androidprv:keycode="KEYCODE_P" + androidprv:keycode="KEYCODE_C" androidprv:modifierState="META" /> <bookmark category="android.intent.category.APP_EMAIL" @@ -30,13 +30,21 @@ androidprv:modifierState="META" /> <bookmark category="android.intent.category.APP_CALENDAR" - androidprv:keycode="KEYCODE_C" + androidprv:keycode="KEYCODE_K" androidprv:modifierState="META" /> <bookmark category="android.intent.category.APP_MAPS" androidprv:keycode="KEYCODE_M" androidprv:modifierState="META" /> <bookmark + category="android.intent.category.APP_MUSIC" + androidprv:keycode="KEYCODE_P" + androidprv:modifierState="META" /> + <bookmark + role="android.app.role.SMS" + androidprv:keycode="KEYCODE_S" + androidprv:modifierState="META" /> + <bookmark category="android.intent.category.APP_CALCULATOR" androidprv:keycode="KEYCODE_U" androidprv:modifierState="META" /> diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java index 35b077e30f12..cf5323e1f3a5 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java @@ -26,6 +26,7 @@ import static android.view.KeyEvent.KEYCODE_E; import static android.view.KeyEvent.KEYCODE_ENTER; import static android.view.KeyEvent.KEYCODE_H; import static android.view.KeyEvent.KEYCODE_J; +import static android.view.KeyEvent.KEYCODE_K; import static android.view.KeyEvent.KEYCODE_M; import static android.view.KeyEvent.KEYCODE_META_LEFT; import static android.view.KeyEvent.KEYCODE_N; @@ -66,12 +67,14 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { // These shortcuts should align with those defined in // services/tests/wmtests/res/xml/bookmarks.xml INTENT_SHORTCUTS.append(KEYCODE_U, Intent.CATEGORY_APP_CALCULATOR); - INTENT_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_CONTACTS); + INTENT_SHORTCUTS.append(KEYCODE_C, Intent.CATEGORY_APP_CONTACTS); INTENT_SHORTCUTS.append(KEYCODE_E, Intent.CATEGORY_APP_EMAIL); - INTENT_SHORTCUTS.append(KEYCODE_C, Intent.CATEGORY_APP_CALENDAR); + INTENT_SHORTCUTS.append(KEYCODE_K, Intent.CATEGORY_APP_CALENDAR); INTENT_SHORTCUTS.append(KEYCODE_M, Intent.CATEGORY_APP_MAPS); + INTENT_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_MUSIC); ROLE_SHORTCUTS.append(KEYCODE_B, RoleManager.ROLE_BROWSER); + ROLE_SHORTCUTS.append(KEYCODE_S, RoleManager.ROLE_SMS); } private static final int ANY_DISPLAY_ID = 123; @@ -106,7 +109,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_B}, 0); mPhoneWindowManager.assertLaunchRole(RoleManager.ROLE_BROWSER); - sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_P}, 0); + sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_C}, 0); mPhoneWindowManager.assertLaunchCategory(Intent.CATEGORY_APP_CONTACTS); sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_J}, 0); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index fee646d9cb9c..d4a921c5f00a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -2670,7 +2670,7 @@ public class ActivityRecordTests extends WindowTestsBase { assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_GAME, true); // Blanket application, also ignoring Target SDK - mWm.mConstants.mIgnoreActivityOrientationRequest = true; + mWm.mConstants.mIgnoreActivityOrientationRequestLargeScreen = true; assertSetOrientation(Build.VERSION_CODES.VANILLA_ICE_CREAM, CATEGORY_SOCIAL, false); } diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java index ad80f82c8ea8..4810c7fc32d2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java @@ -22,6 +22,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; @@ -137,6 +139,14 @@ public final class CameraStateMonitorTests extends WindowTestsBase { } @Test + public void testOnCameraOpened_listenerAdded_cameraRegistersAsOpenedDuringTheCallback() { + mCameraStateMonitor.addCameraStateListener(mListener); + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + + assertTrue(mListener.mIsCameraOpened); + } + + @Test public void testOnCameraOpened_cameraClosed_notifyCameraClosed() { mCameraStateMonitor.addCameraStateListener(mListener); // Listener returns true on `onCameraOpened`. @@ -144,10 +154,21 @@ public final class CameraStateMonitorTests extends WindowTestsBase { mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); + assertEquals(1, mListener.mCheckCanCloseCounter); assertEquals(1, mListener.mOnCameraClosedCounter); } @Test + public void testOnCameraOpenedAndClosed_cameraRegistersAsClosedDuringTheCallback() { + mCameraStateMonitor.addCameraStateListener(mListener); + // Listener returns true on `onCameraOpened`. + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + + mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); + assertFalse(mListener.mIsCameraOpened); + } + + @Test public void testOnCameraOpened_listenerCannotCloseYet_notifyCameraClosedAgain() { mCameraStateMonitor.addCameraStateListener(mListenerCannotClose); // Listener returns true on `onCameraOpened`. @@ -155,7 +176,8 @@ public final class CameraStateMonitorTests extends WindowTestsBase { mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); - assertEquals(2, mListenerCannotClose.mOnCameraClosedCounter); + assertEquals(2, mListenerCannotClose.mCheckCanCloseCounter); + assertEquals(1, mListenerCannotClose.mOnCameraClosedCounter); } @Test @@ -197,39 +219,49 @@ public final class CameraStateMonitorTests extends WindowTestsBase { CameraStateMonitor.CameraCompatStateListener { int mOnCameraOpenedCounter = 0; + int mCheckCanCloseCounter = 0; int mOnCameraClosedCounter = 0; - private boolean mOnCameraClosedReturnValue = true; + boolean mIsCameraOpened; + + private boolean mCheckCanCloseReturnValue = true; /** - * @param simulateUnsuccessfulCloseOnce When false, returns `true` on every - * `onCameraClosed`. When true, returns `false` on the - * first `onCameraClosed` callback, and `true on the + * @param simulateCannotCloseOnce When false, returns `true` on every + * `checkCanClose`. When true, returns `false` on the + * first `checkCanClose` callback, and `true on the * subsequent calls. This fake implementation tests the * retry mechanism in {@link CameraStateMonitor}. */ - FakeCameraCompatStateListener(boolean simulateUnsuccessfulCloseOnce) { - mOnCameraClosedReturnValue = !simulateUnsuccessfulCloseOnce; + FakeCameraCompatStateListener(boolean simulateCannotCloseOnce) { + mCheckCanCloseReturnValue = !simulateCannotCloseOnce; } @Override - public void onCameraOpened(@NonNull ActivityRecord cameraActivity, - @NonNull String cameraId) { + public void onCameraOpened(@NonNull ActivityRecord cameraActivity) { mOnCameraOpenedCounter++; + mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(cameraActivity); } @Override - public boolean onCameraClosed(@NonNull String cameraId) { - mOnCameraClosedCounter++; - boolean returnValue = mOnCameraClosedReturnValue; + public boolean canCameraBeClosed(@NonNull String cameraId) { + mCheckCanCloseCounter++; + final boolean returnValue = mCheckCanCloseReturnValue; // If false, return false only the first time, so it doesn't fall in the infinite retry // loop. - mOnCameraClosedReturnValue = true; + mCheckCanCloseReturnValue = true; return returnValue; } + @Override + public void onCameraClosed() { + mOnCameraClosedCounter++; + mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(mActivity); + } + void resetCounters() { mOnCameraOpenedCounter = 0; + mCheckCanCloseCounter = 0; mOnCameraClosedCounter = 0; } } 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 87db6c0e8577..bf96f0eb03b8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4892,7 +4892,8 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testUniversalResizeable() { - mWm.mConstants.mIgnoreActivityOrientationRequest = true; + mWm.mConstants.mIgnoreActivityOrientationRequestSmallScreen = true; + mWm.mConstants.mIgnoreActivityOrientationRequestLargeScreen = true; setUpApp(mDisplayContent); final float maxAspect = 1.8f; final float minAspect = 1.5f; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 757c358f51d0..78e6cbf9c36a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -1027,7 +1027,8 @@ public class WindowTestsBase extends SystemServiceTestsBase { } @Override - public void setImeInputTargetRequestedVisibility(boolean visible) { + public void setImeInputTargetRequestedVisibility(boolean visible, + @NonNull ImeTracker.Token statsToken) { } }; } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 5a34b0038872..b96b6c53703c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -221,6 +221,14 @@ public final class SatelliteManager { /** * Bundle key to get the response from + * {@link #requestSessionStats(Executor, OutcomeReceiver)}. + * @hide + */ + + public static final String KEY_SESSION_STATS_V2 = "session_stats_v2"; + + /** + * Bundle key to get the response from * {@link #requestIsProvisioned(Executor, OutcomeReceiver)}. * @hide */ @@ -3468,21 +3476,33 @@ public final class SatelliteManager { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode == SATELLITE_RESULT_SUCCESS) { + SatelliteSessionStats stats; if (resultData.containsKey(KEY_SESSION_STATS)) { - SatelliteSessionStats stats = - resultData.getParcelable(KEY_SESSION_STATS, - SatelliteSessionStats.class); - executor.execute(() -> Binder.withCleanCallingIdentity(() -> - callback.onResult(stats))); + stats = resultData.getParcelable(KEY_SESSION_STATS, + SatelliteSessionStats.class); + if (resultData.containsKey(KEY_SESSION_STATS_V2)) { + SatelliteSessionStats stats1 = resultData.getParcelable( + KEY_SESSION_STATS_V2, SatelliteSessionStats.class); + if (stats != null && stats1 != null) { + stats.setSatelliteSessionStats( + stats1.getSatelliteSessionStats()); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> callback.onResult(stats))); + return; + } + } else { + loge("KEY_SESSION_STATS_V2 does not exist."); + } } else { loge("KEY_SESSION_STATS does not exist."); - executor.execute(() -> Binder.withCleanCallingIdentity(() -> - callback.onError(new SatelliteException( - SATELLITE_RESULT_REQUEST_FAILED)))); } + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> callback.onError(new SatelliteException( + SATELLITE_RESULT_REQUEST_FAILED)))); + } else { - executor.execute(() -> Binder.withCleanCallingIdentity(() -> - callback.onError(new SatelliteException(resultCode)))); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> callback.onError(new SatelliteException(resultCode)))); } } }; diff --git a/telephony/java/android/telephony/satellite/SatelliteSessionStats.java b/telephony/java/android/telephony/satellite/SatelliteSessionStats.java index aabb058691d8..1c59449b7c73 100644 --- a/telephony/java/android/telephony/satellite/SatelliteSessionStats.java +++ b/telephony/java/android/telephony/satellite/SatelliteSessionStats.java @@ -19,23 +19,38 @@ package android.telephony.satellite; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; /** * SatelliteSessionStats is used to represent the usage stats of the satellite service. + * * @hide */ public class SatelliteSessionStats implements Parcelable { + private int mCountOfSuccessfulUserMessages; private int mCountOfUnsuccessfulUserMessages; private int mCountOfTimedOutUserMessagesWaitingForConnection; private int mCountOfTimedOutUserMessagesWaitingForAck; private int mCountOfUserMessagesInQueueToBeSent; + private long mLatencyOfSuccessfulUserMessages; + + private Map<Integer, SatelliteSessionStats> datagramStats; + private long mMaxLatency; + private long mLastMessageLatency; + + public SatelliteSessionStats() { + this.datagramStats = new HashMap<>(); + } /** * SatelliteSessionStats constructor - * @param builder Builder to create SatelliteSessionStats object/ + * + * @param builder Builder to create SatelliteSessionStats object/ */ public SatelliteSessionStats(@NonNull Builder builder) { mCountOfSuccessfulUserMessages = builder.mCountOfSuccessfulUserMessages; @@ -45,6 +60,7 @@ public class SatelliteSessionStats implements Parcelable { mCountOfTimedOutUserMessagesWaitingForAck = builder.mCountOfTimedOutUserMessagesWaitingForAck; mCountOfUserMessagesInQueueToBeSent = builder.mCountOfUserMessagesInQueueToBeSent; + mLatencyOfSuccessfulUserMessages = builder.mLatencyOfSuccessfulUserMessages; } private SatelliteSessionStats(Parcel in) { @@ -63,6 +79,19 @@ public class SatelliteSessionStats implements Parcelable { out.writeInt(mCountOfTimedOutUserMessagesWaitingForConnection); out.writeInt(mCountOfTimedOutUserMessagesWaitingForAck); out.writeInt(mCountOfUserMessagesInQueueToBeSent); + out.writeLong(mLatencyOfSuccessfulUserMessages); + out.writeLong(mMaxLatency); + out.writeLong(mLastMessageLatency); + + if (datagramStats != null && !datagramStats.isEmpty()) { + out.writeInt(datagramStats.size()); + for (Map.Entry<Integer, SatelliteSessionStats> entry : datagramStats.entrySet()) { + out.writeInt(entry.getKey()); + out.writeParcelable(entry.getValue(), flags); + } + } else { + out.writeInt(0); + } } @NonNull @@ -83,6 +112,40 @@ public class SatelliteSessionStats implements Parcelable { @NonNull public String toString() { StringBuilder sb = new StringBuilder(); + if (datagramStats != null) { + sb.append(" ====== SatelliteSessionStatsWrapper Info ============="); + for (Map.Entry<Integer, SatelliteSessionStats> entry : datagramStats.entrySet()) { + Integer key = entry.getKey(); + SatelliteSessionStats value = entry.getValue(); + sb.append("\n"); + sb.append("Key:"); + sb.append(key); + sb.append(", SatelliteSessionStats:["); + value.getPrintableCounters(sb); + sb.append(","); + sb.append(" LatencyOfSuccessfulUserMessages:"); + sb.append(value.mLatencyOfSuccessfulUserMessages); + sb.append(","); + sb.append(" mMaxLatency:"); + sb.append(value.mMaxLatency); + sb.append(","); + sb.append(" mLastMessageLatency:"); + sb.append(value.mLastMessageLatency); + sb.append("]"); + sb.append("\n"); + } + sb.append(" ============== ================== ==============="); + sb.append("\n"); + sb.append("\n"); + } else { + sb.append("\n"); + getPrintableCounters(sb); + } + sb.append("\n"); + return sb.toString(); + } + + private void getPrintableCounters(StringBuilder sb) { sb.append("countOfSuccessfulUserMessages:"); sb.append(mCountOfSuccessfulUserMessages); sb.append(","); @@ -101,7 +164,6 @@ public class SatelliteSessionStats implements Parcelable { sb.append("countOfUserMessagesInQueueToBeSent:"); sb.append(mCountOfUserMessagesInQueueToBeSent); - return sb.toString(); } @Override @@ -110,49 +172,176 @@ public class SatelliteSessionStats implements Parcelable { if (o == null || getClass() != o.getClass()) return false; SatelliteSessionStats that = (SatelliteSessionStats) o; return mCountOfSuccessfulUserMessages == that.mCountOfSuccessfulUserMessages + && mLatencyOfSuccessfulUserMessages == that.mLatencyOfSuccessfulUserMessages && mCountOfUnsuccessfulUserMessages == that.mCountOfUnsuccessfulUserMessages && mCountOfTimedOutUserMessagesWaitingForConnection == that.mCountOfTimedOutUserMessagesWaitingForConnection && mCountOfTimedOutUserMessagesWaitingForAck == that.mCountOfTimedOutUserMessagesWaitingForAck - && mCountOfUserMessagesInQueueToBeSent - == that.mCountOfUserMessagesInQueueToBeSent; + && mCountOfUserMessagesInQueueToBeSent == that.mCountOfUserMessagesInQueueToBeSent; } @Override public int hashCode() { - return Objects.hash(mCountOfSuccessfulUserMessages, mCountOfUnsuccessfulUserMessages, - mCountOfTimedOutUserMessagesWaitingForConnection, - mCountOfTimedOutUserMessagesWaitingForAck, - mCountOfUserMessagesInQueueToBeSent); + return Objects.hash(mCountOfSuccessfulUserMessages, mLatencyOfSuccessfulUserMessages, + mCountOfUnsuccessfulUserMessages, mCountOfTimedOutUserMessagesWaitingForConnection, + mCountOfTimedOutUserMessagesWaitingForAck, mCountOfUserMessagesInQueueToBeSent); } public int getCountOfSuccessfulUserMessages() { return mCountOfSuccessfulUserMessages; } + public void incrementSuccessfulUserMessageCount() { + mCountOfSuccessfulUserMessages++; + } + public int getCountOfUnsuccessfulUserMessages() { return mCountOfUnsuccessfulUserMessages; } + public void incrementUnsuccessfulUserMessageCount() { + mCountOfUnsuccessfulUserMessages++; + } + public int getCountOfTimedOutUserMessagesWaitingForConnection() { return mCountOfTimedOutUserMessagesWaitingForConnection; } + public void incrementTimedOutUserMessagesWaitingForConnection() { + mCountOfTimedOutUserMessagesWaitingForConnection++; + } + public int getCountOfTimedOutUserMessagesWaitingForAck() { return mCountOfTimedOutUserMessagesWaitingForAck; } + public void incrementTimedOutUserMessagesWaitingForAck() { + mCountOfTimedOutUserMessagesWaitingForAck++; + } + public int getCountOfUserMessagesInQueueToBeSent() { return mCountOfUserMessagesInQueueToBeSent; } + public long getLatencyOfAllSuccessfulUserMessages() { + return mLatencyOfSuccessfulUserMessages; + } + + public void updateLatencyOfAllSuccessfulUserMessages(long messageLatency) { + mLatencyOfSuccessfulUserMessages += messageLatency; + } + + public void recordSuccessfulOutgoingDatagramStats( + @SatelliteManager.DatagramType int datagramType, long latency) { + try { + datagramStats.putIfAbsent(datagramType, new SatelliteSessionStats.Builder().build()); + SatelliteSessionStats data = datagramStats.get(datagramType); + data.incrementSuccessfulUserMessageCount(); + if (data.mMaxLatency < latency) { + data.mMaxLatency = latency; + } + data.mLastMessageLatency = latency; + data.updateLatencyOfAllSuccessfulUserMessages(latency); + } catch (Exception e) { + Log.e("SatelliteSessionStats", + "Error while recordSuccessfulOutgoingDatagramStats: " + e.getMessage()); + } + } + + public int getCountOfSuccessfulOutgoingDatagram( + @SatelliteManager.DatagramType int datagramType) { + SatelliteSessionStats data = datagramStats.getOrDefault(datagramType, + new SatelliteSessionStats()); + return data.getCountOfSuccessfulUserMessages(); + } + + public long getMaxLatency() { + return this.mMaxLatency; + } + + public Long getLatencyOfAllSuccessfulUserMessages( + @SatelliteManager.DatagramType int datagramType) { + SatelliteSessionStats data = datagramStats.getOrDefault(datagramType, + new SatelliteSessionStats()); + return data.getLatencyOfAllSuccessfulUserMessages(); + } + + + public long getLastMessageLatency() { + return this.mLastMessageLatency; + } + + public void addCountOfUnsuccessfulUserMessages(@SatelliteManager.DatagramType int datagramType, + @SatelliteManager.SatelliteResult int resultCode) { + try { + datagramStats.putIfAbsent(datagramType, new SatelliteSessionStats.Builder().build()); + SatelliteSessionStats data = datagramStats.get(datagramType); + data.incrementUnsuccessfulUserMessageCount(); + if (resultCode == SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE) { + data.incrementTimedOutUserMessagesWaitingForConnection(); + } else if (resultCode == SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT) { + data.incrementTimedOutUserMessagesWaitingForAck(); + } + } catch (Exception e) { + Log.e("SatelliteSessionStats", + "Error while addCountOfUnsuccessfulUserMessages: " + e.getMessage()); + } + } + + public int getCountOfUnsuccessfulUserMessages(@SatelliteManager.DatagramType int datagramType) { + SatelliteSessionStats data = datagramStats.get(datagramType); + return data.getCountOfUnsuccessfulUserMessages(); + } + + public int getCountOfTimedOutUserMessagesWaitingForConnection( + @SatelliteManager.DatagramType int datagramType) { + SatelliteSessionStats data = datagramStats.get(datagramType); + return data.getCountOfTimedOutUserMessagesWaitingForConnection(); + } + + public int getCountOfTimedOutUserMessagesWaitingForAck( + @SatelliteManager.DatagramType int datagramType) { + SatelliteSessionStats data = datagramStats.get(datagramType); + return data.getCountOfTimedOutUserMessagesWaitingForAck(); + } + + public int getCountOfUserMessagesInQueueToBeSent( + @SatelliteManager.DatagramType int datagramType) { + SatelliteSessionStats data = datagramStats.get(datagramType); + return data.getCountOfUserMessagesInQueueToBeSent(); + } + + public void clear() { + datagramStats.clear(); + } + + public Map<Integer, SatelliteSessionStats> getSatelliteSessionStats() { + return datagramStats; + } + + public void setSatelliteSessionStats(Map<Integer, SatelliteSessionStats> sessionStats) { + this.datagramStats = sessionStats; + } + private void readFromParcel(Parcel in) { mCountOfSuccessfulUserMessages = in.readInt(); mCountOfUnsuccessfulUserMessages = in.readInt(); mCountOfTimedOutUserMessagesWaitingForConnection = in.readInt(); mCountOfTimedOutUserMessagesWaitingForAck = in.readInt(); mCountOfUserMessagesInQueueToBeSent = in.readInt(); + mLatencyOfSuccessfulUserMessages = in.readLong(); + mMaxLatency = in.readLong(); + mLastMessageLatency = in.readLong(); + + int size = in.readInt(); + datagramStats = new HashMap<>(); + for (int i = 0; i < size; i++) { + Integer key = in.readInt(); + SatelliteSessionStats value = in.readParcelable( + SatelliteSessionStats.class.getClassLoader()); + datagramStats.put(key, value); + } } /** @@ -164,7 +353,10 @@ public class SatelliteSessionStats implements Parcelable { private int mCountOfTimedOutUserMessagesWaitingForConnection; private int mCountOfTimedOutUserMessagesWaitingForAck; private int mCountOfUserMessagesInQueueToBeSent; + private long mLatencyOfSuccessfulUserMessages; + private long mMaxLatency; + private long mLastMessageLatency; /** * Sets countOfSuccessfulUserMessages value of {@link SatelliteSessionStats} * and then returns the Builder class. @@ -215,10 +407,28 @@ public class SatelliteSessionStats implements Parcelable { return this; } + @NonNull + public Builder setLatencyOfSuccessfulUserMessages(long latency) { + mLatencyOfSuccessfulUserMessages = latency; + return this; + } + + @NonNull + public Builder setMaxLatency(long maxLatency) { + mMaxLatency = maxLatency; + return this; + } + + @NonNull + public Builder setLastLatency(long lastLatency) { + mLastMessageLatency = lastLatency; + return this; + } + /** Returns SatelliteSessionStats object. */ @NonNull public SatelliteSessionStats build() { return new SatelliteSessionStats(this); } } -} +}
\ No newline at end of file diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml index 8b65efdfb5f9..685ae9a5fef2 100644 --- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml +++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml @@ -45,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml index 3382c1e227b3..5f92d7fe830b 100644 --- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml @@ -45,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml index e941e79faea3..1b90e99a8ba2 100644 --- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml @@ -45,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml index 4e06dca17fe2..ffdbb02984a7 100644 --- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml +++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml @@ -45,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml index 0cadd68597b6..12670cda74b2 100644 --- a/tests/FlickerTests/IME/AndroidTestTemplate.xml +++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml @@ -47,6 +47,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml index f32e8bed85ef..e2ac5a9579ae 100644 --- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml +++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml @@ -45,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml index 68ae4f1f7f4f..1a4feb6e9eca 100644 --- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml +++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml @@ -45,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml index ec186723b4a4..481a8bb66fee 100644 --- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml +++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml @@ -45,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java deleted file mode 100644 index eeee7b4dfc6b..000000000000 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker.helpers; - -import android.annotation.NonNull; -import android.app.Instrumentation; -import android.app.UiAutomation; -import android.os.SystemClock; -import android.view.InputDevice; -import android.view.InputEvent; -import android.view.MotionEvent; -import android.view.MotionEvent.PointerCoords; -import android.view.MotionEvent.PointerProperties; - -import androidx.annotation.Nullable; - -/** - * Injects gestures given an {@link Instrumentation} object. - */ -public class GestureHelper { - // Inserted after each motion event injection. - private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5; - - private final UiAutomation mUiAutomation; - - /** - * Primary pointer should be cached here for separate release - */ - @Nullable private PointerProperties mPrimaryPtrProp; - @Nullable private PointerCoords mPrimaryPtrCoord; - private long mPrimaryPtrDownTime; - - /** - * A pair of floating point values. - */ - public static class Tuple { - public float x; - public float y; - - public Tuple(float x, float y) { - this.x = x; - this.y = y; - } - } - - public GestureHelper(Instrumentation instrumentation) { - mUiAutomation = instrumentation.getUiAutomation(); - } - - /** - * Injects a series of {@link MotionEvent}s to simulate tapping. - * - * @param point coordinates of pointer to tap - * @param times the number of times to tap - */ - public boolean tap(@NonNull Tuple point, int times) throws InterruptedException { - PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER); - PointerCoords ptrCoord = getPointerCoord(point.x, point.y, 1, 1); - - for (int i = 0; i <= times; i++) { - // If already tapped, inject delay in between movements - if (times > 0) { - SystemClock.sleep(50L); - } - if (!primaryPointerDown(ptrProp, ptrCoord, SystemClock.uptimeMillis())) { - return false; - } - // Delay before releasing tap - SystemClock.sleep(100L); - if (!primaryPointerUp(ptrProp, ptrCoord, SystemClock.uptimeMillis())) { - return false; - } - } - return true; - } - - /** - * Injects a series of {@link MotionEvent}s to simulate a drag gesture without pointer release. - * - * Simulates a drag gesture without releasing the primary pointer. The primary pointer info - * will be cached for potential release later on by {@code releasePrimaryPointer()} - * - * @param startPoint initial coordinates of the primary pointer - * @param endPoint final coordinates of the primary pointer - * @param steps number of steps to take to animate dragging - * @return true if gesture is injected successfully - */ - public boolean dragWithoutRelease(@NonNull Tuple startPoint, - @NonNull Tuple endPoint, int steps) { - PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER); - PointerCoords ptrCoord = getPointerCoord(startPoint.x, startPoint.y, 1, 1); - - PointerProperties[] ptrProps = new PointerProperties[] { ptrProp }; - PointerCoords[] ptrCoords = new PointerCoords[] { ptrCoord }; - - long downTime = SystemClock.uptimeMillis(); - - if (!primaryPointerDown(ptrProp, ptrCoord, downTime)) { - return false; - } - - // cache the primary pointer info for later potential release - mPrimaryPtrProp = ptrProp; - mPrimaryPtrCoord = ptrCoord; - mPrimaryPtrDownTime = downTime; - - return movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint }, downTime, steps); - } - - /** - * Release primary pointer if previous gesture has cached the primary pointer info. - * - * @return true if the release was injected successfully - */ - public boolean releasePrimaryPointer() { - if (mPrimaryPtrProp != null && mPrimaryPtrCoord != null) { - return primaryPointerUp(mPrimaryPtrProp, mPrimaryPtrCoord, mPrimaryPtrDownTime); - } - - return false; - } - - /** - * Injects a series of {@link MotionEvent} objects to simulate a pinch gesture. - * - * @param startPoint1 initial coordinates of the first pointer - * @param startPoint2 initial coordinates of the second pointer - * @param endPoint1 final coordinates of the first pointer - * @param endPoint2 final coordinates of the second pointer - * @param steps number of steps to take to animate pinching - * @return true if gesture is injected successfully - */ - public boolean pinch(@NonNull Tuple startPoint1, @NonNull Tuple startPoint2, - @NonNull Tuple endPoint1, @NonNull Tuple endPoint2, int steps) { - PointerProperties ptrProp1 = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER); - PointerProperties ptrProp2 = getPointerProp(1, MotionEvent.TOOL_TYPE_FINGER); - - PointerCoords ptrCoord1 = getPointerCoord(startPoint1.x, startPoint1.y, 1, 1); - PointerCoords ptrCoord2 = getPointerCoord(startPoint2.x, startPoint2.y, 1, 1); - - PointerProperties[] ptrProps = new PointerProperties[] { - ptrProp1, ptrProp2 - }; - - PointerCoords[] ptrCoords = new PointerCoords[] { - ptrCoord1, ptrCoord2 - }; - - long downTime = SystemClock.uptimeMillis(); - - if (!primaryPointerDown(ptrProp1, ptrCoord1, downTime)) { - return false; - } - - if (!nonPrimaryPointerDown(ptrProps, ptrCoords, downTime, 1)) { - return false; - } - - if (!movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint1, endPoint2 }, - downTime, steps)) { - return false; - } - - if (!nonPrimaryPointerUp(ptrProps, ptrCoords, downTime, 1)) { - return false; - } - - return primaryPointerUp(ptrProp1, ptrCoord1, downTime); - } - - private boolean primaryPointerDown(@NonNull PointerProperties prop, - @NonNull PointerCoords coord, long downTime) { - MotionEvent event = getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, 1, - new PointerProperties[]{ prop }, new PointerCoords[]{ coord }); - - return injectEventSync(event); - } - - private boolean nonPrimaryPointerDown(@NonNull PointerProperties[] props, - @NonNull PointerCoords[] coords, long downTime, int index) { - // at least 2 pointers are needed - if (props.length != coords.length || coords.length < 2) { - return false; - } - - long eventTime = SystemClock.uptimeMillis(); - - MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_DOWN - + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords); - - return injectEventSync(event); - } - - private boolean movePointers(@NonNull PointerProperties[] props, - @NonNull PointerCoords[] coords, @NonNull Tuple[] endPoints, long downTime, int steps) { - // the number of endpoints should be the same as the number of pointers - if (props.length != coords.length || coords.length != endPoints.length) { - return false; - } - - // prevent division by 0 and negative number of steps - if (steps < 1) { - steps = 1; - } - - // save the starting points before updating any pointers - Tuple[] startPoints = new Tuple[coords.length]; - - for (int i = 0; i < coords.length; i++) { - startPoints[i] = new Tuple(coords[i].x, coords[i].y); - } - - MotionEvent event; - long eventTime; - - for (int i = 0; i < steps; i++) { - // inject a delay between movements - SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS); - - // update the coordinates - for (int j = 0; j < coords.length; j++) { - coords[j].x += (endPoints[j].x - startPoints[j].x) / steps; - coords[j].y += (endPoints[j].y - startPoints[j].y) / steps; - } - - eventTime = SystemClock.uptimeMillis(); - - event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_MOVE, - coords.length, props, coords); - - boolean didInject = injectEventSync(event); - - if (!didInject) { - return false; - } - } - - return true; - } - - private boolean primaryPointerUp(@NonNull PointerProperties prop, - @NonNull PointerCoords coord, long downTime) { - long eventTime = SystemClock.uptimeMillis(); - - MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_UP, 1, - new PointerProperties[]{ prop }, new PointerCoords[]{ coord }); - - return injectEventSync(event); - } - - private boolean nonPrimaryPointerUp(@NonNull PointerProperties[] props, - @NonNull PointerCoords[] coords, long downTime, int index) { - // at least 2 pointers are needed - if (props.length != coords.length || coords.length < 2) { - return false; - } - - long eventTime = SystemClock.uptimeMillis(); - - MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_UP - + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords); - - return injectEventSync(event); - } - - private PointerCoords getPointerCoord(float x, float y, float pressure, float size) { - PointerCoords ptrCoord = new PointerCoords(); - ptrCoord.x = x; - ptrCoord.y = y; - ptrCoord.pressure = pressure; - ptrCoord.size = size; - return ptrCoord; - } - - private PointerProperties getPointerProp(int id, int toolType) { - PointerProperties ptrProp = new PointerProperties(); - ptrProp.id = id; - ptrProp.toolType = toolType; - return ptrProp; - } - - private static MotionEvent getMotionEvent(long downTime, long eventTime, int action, - int pointerCount, PointerProperties[] ptrProps, PointerCoords[] ptrCoords) { - return MotionEvent.obtain(downTime, eventTime, action, pointerCount, - ptrProps, ptrCoords, 0, 0, 1.0f, 1.0f, - 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); - } - - private boolean injectEventSync(InputEvent event) { - return mUiAutomation.injectInputEvent(event, true); - } -} diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt index fd13d14074d4..d5334cbd541c 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt @@ -21,6 +21,7 @@ import android.graphics.Rect import android.graphics.Region import android.tools.device.apphelpers.StandardAppHelper import android.tools.helpers.FIND_TIMEOUT +import android.tools.helpers.GestureHelper import android.tools.helpers.SYSTEMUI_PACKAGE import android.tools.traces.component.ComponentNameMatcher import android.tools.traces.parsers.WindowManagerStateHelper @@ -38,7 +39,8 @@ constructor( ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.COMPONENT.toFlickerComponent() ) : StandardAppHelper(instr, launcherName, component) { - private val gestureHelper: GestureHelper = GestureHelper(instrumentation) + private val gestureHelper: GestureHelper = + GestureHelper(instrumentation) fun clickRestart(wmHelper: WindowManagerStateHelper) { val restartButton = diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt index db4838ee6092..de17bf422c0c 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt @@ -18,29 +18,26 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation import android.content.Intent -import android.graphics.Rect import android.graphics.Region import android.media.session.MediaController import android.media.session.MediaSessionManager -import android.tools.datatypes.coversMoreThan -import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.apphelpers.BasePipAppHelper import android.tools.helpers.FIND_TIMEOUT import android.tools.helpers.SYSTEMUI_PACKAGE import android.tools.traces.ConditionsFactory +import android.tools.traces.component.ComponentNameMatcher import android.tools.traces.component.IComponentMatcher import android.tools.traces.parsers.WindowManagerStateHelper import android.tools.traces.parsers.toFlickerComponent -import android.util.Log import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -open class PipAppHelper(instrumentation: Instrumentation) : - StandardAppHelper( - instrumentation, - ActivityOptions.Pip.LABEL, - ActivityOptions.Pip.COMPONENT.toFlickerComponent() - ) { +open class PipAppHelper( + instrumentation: Instrumentation, + appName: String = ActivityOptions.Pip.LABEL, + componentNameMatcher: ComponentNameMatcher = ActivityOptions.Pip.COMPONENT.toFlickerComponent(), +) : BasePipAppHelper(instrumentation, appName, componentNameMatcher) { private val mediaSessionManager: MediaSessionManager get() = context.getSystemService(MediaSessionManager::class.java) @@ -52,189 +49,6 @@ open class PipAppHelper(instrumentation: Instrumentation) : it.packageName == packageName } - private val gestureHelper: GestureHelper = GestureHelper(instrumentation) - - open fun clickObject(resId: String) { - val selector = By.res(packageName, resId) - val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object") - - obj.click() - } - - /** Drags the PIP window to the provided final coordinates without releasing the pointer. */ - fun dragPipWindowAwayFromEdgeWithoutRelease(wmHelper: WindowManagerStateHelper, steps: Int) { - val initWindowRect = Rect(getWindowRect(wmHelper)) - - // initial pointer at the center of the window - val initialCoord = - GestureHelper.Tuple( - initWindowRect.centerX().toFloat(), - initWindowRect.centerY().toFloat() - ) - - // the offset to the right (or left) of the window center to drag the window to - val offset = 50 - - // the actual final x coordinate with the offset included; - // if the pip window is closer to the right edge of the display the offset is negative - // otherwise the offset is positive - val endX = - initWindowRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) -1 else 1) - val finalCoord = GestureHelper.Tuple(endX.toFloat(), initWindowRect.centerY().toFloat()) - - // drag to the final coordinate - gestureHelper.dragWithoutRelease(initialCoord, finalCoord, steps) - } - - /** - * Releases the primary pointer. - * - * Injects the release of the primary pointer if the primary pointer info was cached after - * another gesture was injected without pointer release. - */ - fun releasePipAfterDragging() { - gestureHelper.releasePrimaryPointer() - } - - /** - * Drags the PIP window away from the screen edge while not crossing the display center. - * - * @throws IllegalStateException if default display bounds are not available - */ - fun dragPipWindowAwayFromEdge(wmHelper: WindowManagerStateHelper, steps: Int) { - val initWindowRect = Rect(getWindowRect(wmHelper)) - - // initial pointer at the center of the window - val startX = initWindowRect.centerX() - val y = initWindowRect.centerY() - - val displayRect = - wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect - ?: throw IllegalStateException("Default display is null") - - // the offset to the right (or left) of the display center to drag the window to - val offset = 20 - - // the actual final x coordinate with the offset included; - // if the pip window is closer to the right edge of the display the offset is positive - // otherwise the offset is negative - val endX = displayRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) 1 else -1) - - // drag the window to the left but not beyond the center of the display - uiDevice.drag(startX, y, endX, y, steps) - } - - /** - * Returns true if PIP window is closer to the right edge of the display than left. - * - * @throws IllegalStateException if default display bounds are not available - */ - fun isCloserToRightEdge(wmHelper: WindowManagerStateHelper): Boolean { - val windowRect = getWindowRect(wmHelper) - - val displayRect = - wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect - ?: throw IllegalStateException("Default display is null") - - return windowRect.centerX() > displayRect.centerX() - } - - /** - * Expands the PIP window by using the pinch out gesture. - * - * @param percent The percentage by which to increase the pip window size. - * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f - */ - fun pinchOpenPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) { - // the percentage must be between 0.0f and 1.0f - if (percent <= 0.0f || percent > 1.0f) { - throw IllegalArgumentException("Percent must be between 0.0f and 1.0f") - } - - val windowRect = getWindowRect(wmHelper) - - // first pointer's initial x coordinate is halfway between the left edge and the center - val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat() - // second pointer's initial x coordinate is halfway between the right edge and the center - val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat() - - // horizontal distance the window should increase by - val distIncrease = windowRect.width() * percent - - // final x-coordinates - val finalLeftX = initLeftX - (distIncrease / 2) - val finalRightX = initRightX + (distIncrease / 2) - - // y-coordinate is the same throughout this animation - val yCoord = windowRect.centerY().toFloat() - - var adjustedSteps = MIN_STEPS_TO_ANIMATE - - // if distance per step is at least 1, then we can use the number of steps requested - if (distIncrease.toInt() / (steps * 2) >= 1) { - adjustedSteps = steps - } - - // if the distance per step is less than 1, carry out the animation in two steps - gestureHelper.pinch( - GestureHelper.Tuple(initLeftX, yCoord), - GestureHelper.Tuple(initRightX, yCoord), - GestureHelper.Tuple(finalLeftX, yCoord), - GestureHelper.Tuple(finalRightX, yCoord), - adjustedSteps - ) - - waitForPipWindowToExpandFrom(wmHelper, Region(windowRect)) - } - - /** - * Minimizes the PIP window by using the pinch in gesture. - * - * @param percent The percentage by which to decrease the pip window size. - * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f - */ - fun pinchInPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) { - // the percentage must be between 0.0f and 1.0f - if (percent <= 0.0f || percent > 1.0f) { - throw IllegalArgumentException("Percent must be between 0.0f and 1.0f") - } - - val windowRect = getWindowRect(wmHelper) - - // first pointer's initial x coordinate is halfway between the left edge and the center - val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat() - // second pointer's initial x coordinate is halfway between the right edge and the center - val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat() - - // decrease by the distance specified through the percentage - val distDecrease = windowRect.width() * percent - - // get the final x-coordinates and make sure they are not passing the center of the window - val finalLeftX = Math.min(initLeftX + (distDecrease / 2), windowRect.centerX().toFloat()) - val finalRightX = Math.max(initRightX - (distDecrease / 2), windowRect.centerX().toFloat()) - - // y-coordinate is the same throughout this animation - val yCoord = windowRect.centerY().toFloat() - - var adjustedSteps = MIN_STEPS_TO_ANIMATE - - // if distance per step is at least 1, then we can use the number of steps requested - if (distDecrease.toInt() / (steps * 2) >= 1) { - adjustedSteps = steps - } - - // if the distance per step is less than 1, carry out the animation in two steps - gestureHelper.pinch( - GestureHelper.Tuple(initLeftX, yCoord), - GestureHelper.Tuple(initRightX, yCoord), - GestureHelper.Tuple(finalLeftX, yCoord), - GestureHelper.Tuple(finalRightX, yCoord), - adjustedSteps - ) - - waitForPipWindowToMinimizeFrom(wmHelper, Region(windowRect)) - } - /** * Launches the app through an intent instead of interacting with the launcher and waits until * the app window is in PIP mode @@ -331,126 +145,6 @@ open class PipAppHelper(instrumentation: Instrumentation) : closePipWindow(WindowManagerStateHelper(instrumentation)) } - /** Returns the pip window bounds. */ - fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect { - val windowRegion = wmHelper.getWindowRegion(this) - require(!windowRegion.isEmpty) { "Unable to find a PIP window in the current state" } - return windowRegion.bounds - } - - /** Taps the pip window and dismisses it by clicking on the X button. */ - open fun closePipWindow(wmHelper: WindowManagerStateHelper) { - val windowRect = getWindowRect(wmHelper) - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - // search and interact with the dismiss button - val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss") - uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT) - val dismissPipObject = - uiDevice.findObject(dismissSelector) ?: error("PIP window dismiss button not found") - val dismissButtonBounds = dismissPipObject.visibleBounds - uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY()) - - // Wait for animation to complete. - wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify() - } - - open fun tapPipToShowMenu(wmHelper: WindowManagerStateHelper) { - val windowRect = getWindowRect(wmHelper) - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - // search and interact with the dismiss button - val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss") - uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT) - } - - /** Close the pip window by pressing the expand button */ - fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) { - val windowRect = getWindowRect(wmHelper) - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - // search and interact with the expand button - val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button") - uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT) - val expandPipObject = - uiDevice.findObject(expandSelector) ?: error("PIP window expand button not found") - val expandButtonBounds = expandPipObject.visibleBounds - uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY()) - wmHelper.StateSyncBuilder().withPipGone().withFullScreenApp(this).waitForAndVerify() - } - - /** Double click on the PIP window to expand it */ - fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) { - val windowRect = getWindowRect(wmHelper) - Log.d(TAG, "First click") - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - Log.d(TAG, "Second click") - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - Log.d(TAG, "Wait for app transition to end") - wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() - waitForPipWindowToExpandFrom(wmHelper, Region(windowRect)) - } - - private fun waitForPipWindowToExpandFrom( - wmHelper: WindowManagerStateHelper, - windowRect: Region - ) { - wmHelper - .StateSyncBuilder() - .add("pipWindowExpanded") { - val pipAppWindow = - it.wmState.visibleWindows.firstOrNull { window -> - this.windowMatchesAnyOf(window) - } - ?: return@add false - val pipRegion = pipAppWindow.frameRegion - return@add pipRegion.coversMoreThan(windowRect) - } - .waitForAndVerify() - } - - private fun waitForPipWindowToMinimizeFrom( - wmHelper: WindowManagerStateHelper, - windowRect: Region - ) { - wmHelper - .StateSyncBuilder() - .add("pipWindowMinimized") { - val pipAppWindow = - it.wmState.visibleWindows.firstOrNull { window -> - this.windowMatchesAnyOf(window) - } - Log.d(TAG, "window " + pipAppWindow) - if (pipAppWindow == null) return@add false - val pipRegion = pipAppWindow.frameRegion - Log.d( - TAG, - "region " + pipRegion + " covers " + windowRect.coversMoreThan(pipRegion) - ) - return@add windowRect.coversMoreThan(pipRegion) - } - .waitForAndVerify() - } - - /** - * Waits until the PIP window snaps horizontally to the provided bounds. - * - * @param finalBounds the bounds to wait for PIP window to snap to - */ - fun waitForPipToSnapTo(wmHelper: WindowManagerStateHelper, finalBounds: android.graphics.Rect) { - wmHelper - .StateSyncBuilder() - .add("pipWindowSnapped") { - val pipAppWindow = - it.wmState.visibleWindows.firstOrNull { window -> - this.windowMatchesAnyOf(window) - } - ?: return@add false - val pipRegionBounds = pipAppWindow.frameRegion.bounds - return@add pipRegionBounds.left == finalBounds.left && - pipRegionBounds.right == finalBounds.right - } - .add(ConditionsFactory.isWMStateComplete()) - .waitForAndVerify() - } - companion object { private const val TAG = "PipAppHelper" private const val ENTER_PIP_BUTTON_ID = "enter_pip" @@ -459,8 +153,5 @@ open class PipAppHelper(instrumentation: Instrumentation) : private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual" private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter" private const val SOURCE_RECT_HINT = "set_source_rect_hint" - // minimum number of steps to take, when animating gestures, needs to be 2 - // so that there is at least a single intermediate layer that flicker tests can check - private const val MIN_STEPS_TO_ANIMATE = 2 } -} +}
\ No newline at end of file diff --git a/tests/Input/res/xml/bookmarks.xml b/tests/Input/res/xml/bookmarks.xml index 68ec1233cdd7..a4c898d8159a 100644 --- a/tests/Input/res/xml/bookmarks.xml +++ b/tests/Input/res/xml/bookmarks.xml @@ -23,7 +23,7 @@ androidprv:modifierState="META" /> <bookmark category="android.intent.category.APP_CONTACTS" - androidprv:keycode="KEYCODE_P" + androidprv:keycode="KEYCODE_C" androidprv:modifierState="META" /> <bookmark category="android.intent.category.APP_EMAIL" @@ -31,13 +31,21 @@ androidprv:modifierState="META" /> <bookmark category="android.intent.category.APP_CALENDAR" - androidprv:keycode="KEYCODE_C" + androidprv:keycode="KEYCODE_K" androidprv:modifierState="META" /> <bookmark category="android.intent.category.APP_MAPS" androidprv:keycode="KEYCODE_M" androidprv:modifierState="META" /> <bookmark + category="android.intent.category.APP_MUSIC" + androidprv:keycode="KEYCODE_P" + androidprv:modifierState="META" /> + <bookmark + role="android.app.role.SMS" + androidprv:keycode="KEYCODE_S" + androidprv:modifierState="META" /> + <bookmark category="android.intent.category.APP_CALCULATOR" androidprv:keycode="KEYCODE_U" androidprv:modifierState="META" /> @@ -49,7 +57,7 @@ <bookmark category="android.intent.category.APP_CONTACTS" - androidprv:keycode="KEYCODE_P" + androidprv:keycode="KEYCODE_C" shift="true" /> <bookmark @@ -57,4 +65,4 @@ class="com.test.BookmarkTest" androidprv:keycode="KEYCODE_J" shift="true" /> -</bookmarks> +</bookmarks>
\ No newline at end of file diff --git a/tests/Input/res/xml/bookmarks_legacy.xml b/tests/Input/res/xml/bookmarks_legacy.xml index 78cc48b19416..8bacf490ad9e 100644 --- a/tests/Input/res/xml/bookmarks_legacy.xml +++ b/tests/Input/res/xml/bookmarks_legacy.xml @@ -22,17 +22,23 @@ shortcut="b" /> <bookmark category="android.intent.category.APP_CONTACTS" - shortcut="p" /> + shortcut="c" /> <bookmark category="android.intent.category.APP_EMAIL" shortcut="e" /> <bookmark category="android.intent.category.APP_CALENDAR" - shortcut="c" /> + shortcut="k" /> <bookmark category="android.intent.category.APP_MAPS" shortcut="m" /> <bookmark + category="android.intent.category.APP_MUSIC" + shortcut="p" /> + <bookmark + role="android.app.role.SMS" + shortcut="s" /> + <bookmark category="android.intent.category.APP_CALCULATOR" shortcut="u" /> @@ -43,7 +49,7 @@ <bookmark category="android.intent.category.APP_CONTACTS" - shortcut="p" + shortcut="c" shift="true" /> <bookmark @@ -51,4 +57,4 @@ class="com.test.BookmarkTest" shortcut="j" shift="true" /> -</bookmarks> +</bookmarks>
\ No newline at end of file diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 662b8e562126..36a89f95aa6f 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -542,9 +542,9 @@ class KeyGestureControllerTests { ), TestData( "META + C -> Launch Default Contacts", - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P), + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C), KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, - intArrayOf(KeyEvent.KEYCODE_P), + intArrayOf(KeyEvent.KEYCODE_C), KeyEvent.META_META_ON, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS) @@ -560,9 +560,9 @@ class KeyGestureControllerTests { ), TestData( "META + K -> Launch Default Calendar", - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C), + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_K), KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, - intArrayOf(KeyEvent.KEYCODE_C), + intArrayOf(KeyEvent.KEYCODE_K), KeyEvent.META_META_ON, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR) @@ -577,6 +577,24 @@ class KeyGestureControllerTests { AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MAPS) ), TestData( + "META + P -> Launch Default Music", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_P), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC) + ), + TestData( + "META + S -> Launch Default SMS", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_S), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_S), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_SMS) + ), + TestData( "META + U -> Launch Default Calculator", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_U), KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, @@ -821,10 +839,10 @@ class KeyGestureControllerTests { AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER) ), TestData( - "META + P -> Launch Default Contacts", - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P), + "META + C -> Launch Default Contacts", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C), KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, - intArrayOf(KeyEvent.KEYCODE_P), + intArrayOf(KeyEvent.KEYCODE_C), KeyEvent.META_META_ON, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS) @@ -839,10 +857,10 @@ class KeyGestureControllerTests { AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL) ), TestData( - "META + C -> Launch Default Calendar", - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C), + "META + K -> Launch Default Calendar", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_K), KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, - intArrayOf(KeyEvent.KEYCODE_C), + intArrayOf(KeyEvent.KEYCODE_K), KeyEvent.META_META_ON, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR) @@ -857,6 +875,24 @@ class KeyGestureControllerTests { AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MAPS) ), TestData( + "META + P -> Launch Default Music", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_P), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC) + ), + TestData( + "META + S -> Launch Default SMS", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_S), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_S), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_SMS) + ), + TestData( "META + U -> Launch Default Calculator", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_U), KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, @@ -879,14 +915,14 @@ class KeyGestureControllerTests { AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER) ), TestData( - "META + SHIFT + P -> Launch Default Contacts", + "META + SHIFT + C -> Launch Default Contacts", intArrayOf( KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_SHIFT_LEFT, - KeyEvent.KEYCODE_P + KeyEvent.KEYCODE_C ), KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, - intArrayOf(KeyEvent.KEYCODE_P), + intArrayOf(KeyEvent.KEYCODE_C), KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS) @@ -1591,4 +1627,4 @@ class KeyGestureControllerTests { return true } } -} +}
\ No newline at end of file diff --git a/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java b/tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java index e9e70783ebe9..47638b002f37 100644 --- a/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java +++ b/tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.vcn.util; +package android.net.vcn.util; import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC; import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12; @@ -22,10 +22,10 @@ import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16; import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8; import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128; import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256; +import static android.net.vcn.util.MtuUtils.getMtu; import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU; import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; -import static com.android.server.vcn.util.MtuUtils.getMtu; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java b/tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java index 9c6d85238b77..c84e60086b37 100644 --- a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java +++ b/tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.vcn.util; +package android.net.vcn.util; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 4ab8e6abbbef..26a2a0636792 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -77,6 +77,8 @@ import android.net.vcn.VcnConfigTest; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.net.vcn.VcnManager; import android.net.vcn.VcnUnderlyingNetworkPolicy; +import android.net.vcn.util.PersistableBundleUtils; +import android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.os.IBinder; import android.os.ParcelUuid; import android.os.PersistableBundle; @@ -99,8 +101,6 @@ import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; import com.android.server.vcn.VcnNetworkProvider; -import com.android.server.vcn.util.PersistableBundleUtils; -import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import org.junit.Before; import org.junit.Rule; diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java index f1f74bca2ef9..b999475cba38 100644 --- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -19,6 +19,7 @@ package com.android.server.vcn; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY; +import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; @@ -26,7 +27,6 @@ import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; -import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 20b7f1f14691..76be232c2fe3 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -69,6 +69,7 @@ import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.net.vcn.VcnManager.VcnErrorCode; import android.net.vcn.VcnTransportInfo; +import android.net.vcn.util.MtuUtils; import android.os.PersistableBundle; import androidx.test.filters.SmallTest; @@ -79,7 +80,6 @@ import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration; import com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent; import com.android.server.vcn.routeselection.UnderlyingNetworkRecord; -import com.android.server.vcn.util.MtuUtils; import org.junit.Before; import org.junit.Test; diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java index 613b92616707..b9fe76a24d20 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -25,13 +25,13 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY; +import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR; import static com.android.server.vcn.VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS; import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent; -import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java index 441a4ae6d9b6..5db02e376f3d 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java @@ -19,11 +19,11 @@ package com.android.server.vcn.routeselection; import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY; import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY; import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY; +import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR; import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.MIN_VALID_EXPECTED_RX_PACKET_NUM; import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.getMaxSeqNumIncreasePerSecond; -import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java index d85c5150f53f..4f34f9f8f74c 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java @@ -23,13 +23,13 @@ import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTR import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS; import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS; import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS; +import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_FALLBACK; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesCellPriorityRule; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule; -import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java index 1d6872195e81..a315b0690ec5 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java @@ -17,9 +17,9 @@ package com.android.server.vcn.routeselection; import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY; +import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID; -import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; |