Merge "Update App Function documentation" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 56831d7..8a69e86 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -652,6 +652,8 @@
min_sdk_version: "30",
apex_available: [
"//apex_available:platform",
+ "com.android.art",
+ "com.android.art.debug",
"com.android.permission",
],
}
@@ -1205,6 +1207,7 @@
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 {
+ 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 424a4a71..42028e0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -83,7 +83,6 @@
":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 @@
":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 @@
"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 @@
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 6aef9dd..4a1628e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -20768,6 +20768,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
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 1a949d84..d42201d40 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -129,6 +129,7 @@
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 @@
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 83699ac..7bfa878 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3543,6 +3543,7 @@
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 @@
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 @@
}
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 @@
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 d1eb8e8..4bf87f91 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -141,6 +141,14 @@
}
/**
+ * @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 0668958..5048661 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -1028,4 +1028,14 @@
@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 e2d20cb..0268b5b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -821,14 +821,14 @@
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 @@
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 @@
// 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 @@
}
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 @@
}
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 @@
}
}
+ 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 @@
}
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 @@
.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 581efa5..686c830 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -387,6 +387,7 @@
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/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 367f1af..f8ac27d 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -90,6 +90,12 @@
*/
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 767f52a..448793d 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
@@ -63,4 +63,11 @@
* @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 d63a443..42c7441 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -166,6 +166,20 @@
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 @@
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 6ea7834..b3f09a9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -1288,6 +1288,17 @@
@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 cc57dc0..e271cf4 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -946,6 +946,34 @@
}
/**
+ * 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 02eed1a..d766017 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -7970,6 +7970,24 @@
}
/**
+ * 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 @@
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 @@
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 @@
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 23d3693..a06eb1c 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.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.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 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 @@
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 23daaf2..e4fbd1f 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 @@
* 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 @@
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 0000000..a81e07e
--- /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 ba089f7..35e5c44 100644
--- a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
@@ -53,7 +53,14 @@
* 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 92d1d9e..e4cf55d 100644
--- a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
+++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
@@ -24,7 +24,7 @@
*
* {@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 @@
* 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 833260a..d351ebc 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -363,6 +363,17 @@
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/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 1e66bee..a31b87f 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1507,13 +1507,6 @@
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 0000000..f5d3743
--- /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 9cc0364..c3490d1 100644
--- a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
+++ b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
@@ -38,9 +38,4 @@
* 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 3b573ea..32b6405 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 @@
* 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 57d9d28..eceaa8f 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -18,11 +18,13 @@
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 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 @@
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 @@
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 @@
mDisplayCutout = displayCutout;
mIgnoreActivitySizeRestrictions = ignoreActivitySizeRestrictions;
mDefaultBrightness = defaultBrightness;
+ mBrightnessListener = brightnessListener;
}
/**
@@ -167,14 +173,20 @@
* 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 @@
DisplayCutout.ParcelableWrapper.writeCutoutToParcel(mDisplayCutout, dest, flags);
dest.writeBoolean(mIgnoreActivitySizeRestrictions);
dest.writeFloat(mDefaultBrightness);
+ dest.writeStrongBinder(mBrightnessListener != null ? mBrightnessListener.asBinder() : null);
}
@Override
@@ -294,7 +307,9 @@
&& 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 @@
mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId,
mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories,
mRequestedRefreshRate, mIsHomeSupported, mDisplayCutout,
- mIgnoreActivitySizeRestrictions, mDefaultBrightness);
+ mIgnoreActivitySizeRestrictions, mDefaultBrightness, mBrightnessListener);
return hashCode;
}
@@ -345,6 +360,43 @@
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 @@
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 @@
* 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 @@
* <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 @@
}
/**
+ * 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 @@
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 c0398ce..ded9415 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_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 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 a27e923..0d0efb2 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -18,10 +18,10 @@
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.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 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 b270062..067144e 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -32,12 +32,12 @@
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 c7b2f18..770a8c1 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -17,22 +17,22 @@
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 ce5ec75..48c1b25 100644
--- a/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
@@ -20,10 +20,10 @@
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 853a52d..dc1ee36 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.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 6acb34e..6e8616f 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.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 1459671..b590148 100644
--- a/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
@@ -20,10 +20,10 @@
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 d1531a1..aefac2e8 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.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 0c9ee84..469966a 100644
--- a/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
+++ b/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
@@ -18,11 +18,10 @@
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 e62acac..3f4ba34 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.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
similarity index 96%
rename from services/core/java/com/android/server/vcn/util/LogUtils.java
rename to core/java/android/net/vcn/util/LogUtils.java
index 93728ce..7f7f852 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
similarity index 99%
rename from services/core/java/com/android/server/vcn/util/MtuUtils.java
rename to core/java/android/net/vcn/util/MtuUtils.java
index 356c71f..c3123bc 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
similarity index 96%
rename from services/core/java/com/android/server/vcn/util/OneWayBoolean.java
rename to core/java/android/net/vcn/util/OneWayBoolean.java
index e79bb2d..a7ef67b 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
similarity index 99%
rename from services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
rename to core/java/android/net/vcn/util/PersistableBundleUtils.java
index d6761a2..4dc42c7 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 8e78b7e..072c012 100644
--- a/core/java/android/os/CpuHeadroomParams.java
+++ b/core/java/android/os/CpuHeadroomParams.java
@@ -56,15 +56,9 @@
*/
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 @@
* 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 @@
/**
* 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 @@
* 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 4dc9826..126ee8c 100644
--- a/core/java/android/os/GpuHeadroomParams.java
+++ b/core/java/android/os/GpuHeadroomParams.java
@@ -54,15 +54,8 @@
*/
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 @@
/**
* 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 @@
* 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 @@
* <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 f9789c1..bfcc5cc 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -93,10 +93,9 @@
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 a1ede5f..7e73a5d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3846,7 +3846,7 @@
}
/**
- * 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 a719583..b5139b5 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -445,3 +445,21 @@
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 f6bdc18..e4a3c9f 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.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 @@
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 @@
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 @@
showBreadCrumbs(mActivityTitle, null);
}
getListView().clearChoices();
- } else {
+ } else if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) {
super.onBackPressed();
}
+ updateBackCallbackRegistrationState();
}
/**
@@ -1221,6 +1244,7 @@
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 99ff38b..8e379e8 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -10859,6 +10859,28 @@
"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 ddac8ed..bfb7e16 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 1dd9d46..f8737a5 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -75,7 +75,7 @@
@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 45dbe43..21b969c 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -60,5 +60,5 @@
* 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 e5be531..6d85e75 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -62,6 +62,7 @@
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 @@
* 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 dd32d57..4d354e0 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -222,6 +222,8 @@
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 @@
* 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 6303c76..5dd29b2 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2626,10 +2626,12 @@
// 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/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 7faa5d7..65e5679 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 {
- 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 0c56c67..6ad7fef 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_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 @@
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 @@
}
}
+ /**
+ * 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 eb6a810..429a6a2 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -89,6 +89,8 @@
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 @@
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 fafa085..cd17ed8 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -201,6 +201,9 @@
*/
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 2bfbf84..4b90420 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 @@
}
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 301dc39..cac2024 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.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 @@
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 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 7ad18b8..b2eeff3 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 @@
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 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 @@
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 @@
}
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 284c299..aeaeeca 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 @@
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 @@
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 0000000..5c0e5f6
--- /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 a790e5d..09c02c9 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 06f5f06..614444d 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 427c4e4..f539105 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 f0e4c0f..ddf3ebc 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 0000000..18bafe0
--- /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 0000000..9ff141b
--- /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 0000000..9fb44ccc
--- /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 0000000..578a0b2
--- /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 0000000..5b58726
--- /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 0000000..afa4bc6
--- /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 ffa3b9c..7232786 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 7da7435..20f4095 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 c8df662..f53acbf 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 4f029cd..748f4b3 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 17860ef..e735784 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 7bc4abd..fdfb0c3 100644
--- a/core/tests/coretests/src/android/content/IntentTest.java
+++ b/core/tests/coretests/src/android/content/IntentTest.java
@@ -19,8 +19,9 @@
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.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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
/**
* Build/Install/Run:
@@ -51,7 +54,6 @@
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 @@
}
}
+ @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 b60d614..c55008e 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 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 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 549e666..30f6636 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.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 e4e965f..b8d1979 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.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.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 @@
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 58a434a..a04a662 100644
--- a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
+++ b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
@@ -18,12 +18,10 @@
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.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 dcaab8e..897fc54 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -607,8 +607,6 @@
<!-- 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 6339a87..087378b 100644
--- a/framework-jarjar-rules.txt
+++ b/framework-jarjar-rules.txt
@@ -4,7 +4,6 @@
# 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/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 38087c0..38b8592 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 @@
@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 @@
// 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 @@
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 @@
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 c4c177c..c45f09b 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 @@
}
}
- 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 @@
}
@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 @@
* 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 c99d9ba8..9d4b4bb 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.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.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 @@
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 @@
@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 @@
mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis(
DISAPPEAR_DELAY_MS, flags);
mCompatUIStatusManager = compatUIStatusManager;
- mInDesktopModePredicate = isDesktopModeEnablePredicate;
+ mDesktopUserRepositories = desktopUserRepositories;
shellInit.addInitCallback(this::onInit, this);
}
@@ -267,7 +269,6 @@
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 @@
}
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 b50716a..8b830e7 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.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 @@
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 @@
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 cb9c20e..47084e1 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.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.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 @@
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 @@
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 @@
compatUIShellCommandHandler.get(),
accessibilityManager.get(),
compatUIStatusManager,
- inDesktopModePredicate));
+ desktopUserRepositories));
}
@WMSingleton
@@ -704,14 +699,14 @@
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 @@
@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 9745353..0bf3d9b 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.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.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.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 @@
return new AdditionalSystemViewContainer.Factory();
}
+ @WMSingleton
+ @Provides
+ static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier(
+ @ShellMainThread @NonNull CoroutineScope mainScope) {
+ return new DefaultWindowDecorViewHostSupplier(mainScope);
+ }
+
//
// Freeform
//
@@ -362,7 +373,7 @@
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
Optional<DesktopTasksController> desktopTasksController,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorViewModel,
@@ -374,7 +385,7 @@
context,
init,
shellTaskOrganizer,
- desktopRepository,
+ desktopUserRepositories,
desktopTasksController,
launchAdjacentController,
windowDecorViewModel,
@@ -690,7 +701,7 @@
DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
- @DynamicOverride DesktopRepository desktopRepository,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
Optional<DesktopImmersiveController> desktopImmersiveController,
DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
@@ -726,7 +737,7 @@
toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler,
desktopImmersiveController.get(),
- desktopRepository,
+ desktopUserRepositories,
recentsTransitionHandler,
multiInstanceHelper,
mainExecutor,
@@ -749,7 +760,7 @@
ShellTaskOrganizer shellTaskOrganizer,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
ReturnToDragStartAnimator returnToDragStartAnimator,
- @DynamicOverride DesktopRepository desktopRepository,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
DesktopModeEventLogger desktopModeEventLogger) {
return new DesktopTilingDecorViewModel(
context,
@@ -760,7 +771,7 @@
shellTaskOrganizer,
toggleResizeDesktopTaskTransitionHandler,
returnToDragStartAnimator,
- desktopRepository,
+ desktopUserRepositories,
desktopModeEventLogger
);
}
@@ -768,10 +779,10 @@
@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 @@
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 @@
return Optional.of(
new DesktopTasksLimiter(
transitions,
- desktopRepository,
+ desktopUserRepositories,
shellTaskOrganizer,
maxTaskLimit,
interactionJankMonitor,
@@ -808,7 +819,7 @@
Context context,
ShellInit shellInit,
Transitions transitions,
- @DynamicOverride DesktopRepository desktopRepository,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
DisplayController displayController,
ShellTaskOrganizer shellTaskOrganizer,
ShellCommandHandler shellCommandHandler) {
@@ -817,7 +828,7 @@
new DesktopImmersiveController(
shellInit,
transitions,
- desktopRepository,
+ desktopUserRepositories,
displayController,
shellTaskOrganizer,
shellCommandHandler));
@@ -856,14 +867,16 @@
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 @@
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
- @DynamicOverride DesktopRepository desktopRepository,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -907,7 +920,7 @@
}
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 @@
@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 @@
return Optional.of(
new SystemModalsTransitionHandler(
context, mainExecutor, animExecutor, shellInit, transitions,
- desktopRepository));
+ desktopUserRepositories));
}
@WMSingleton
@@ -993,16 +1006,17 @@
@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 @@
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 @@
shellTaskOrganizer,
taskStackListener,
toggleResizeDesktopTaskTransitionHandler,
- desktopRepository));
+ desktopUserRepositories));
}
return Optional.empty();
}
@@ -1031,12 +1045,12 @@
@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 @@
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 @@
new DesktopMixedTransitionHandler(
context,
transitions,
- desktopRepository,
+ desktopUserRepositories,
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
desktopImmersiveController.get(),
@@ -1316,9 +1330,11 @@
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 3cd5df3..cfdfe3d 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.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 @@
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 @@
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 7507e04..3a99619 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.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 @@
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 606aa6c..c0bf40b 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 @@
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 @@
) {
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 @@
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 dd95273d..79be698 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 82c2ebc..96bbd58 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 @@
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 @@
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 43544f6..250e177 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 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 @@
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 @@
}
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 2c432bc..3821998 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 @@
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 @@
@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 c7cf310..42c3b1c 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.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 @@
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 7fcb767..ca3dc2d 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.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 @@
/** 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 @@
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 @@
* 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 @@
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 @@
)
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 94ac2e6..80d8bfb 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 @@
/** 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 @@
}
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 @@
}
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 @@
}
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 c479ab3..5cb94f8 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.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 @@
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 @@
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 @@
shellInit.addInitCallback({ onInit() }, this)
}
userId = ActivityManager.getCurrentUser()
+ taskRepository = userRepositories.getProfile(userId)
}
private fun onInit() {
@@ -795,32 +798,24 @@
*/
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 @@
}
}
} 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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
/* requestCode= */ 0,
intent,
PendingIntent.FLAG_IMMUTABLE,
- /* bundle= */ null,
+ /* options= */ null,
userHandle
)
wct.sendPendingIntent(pendingIntent, intent, options.toBundle())
@@ -1739,8 +1726,7 @@
/** 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 @@
)
)
}
+
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
doesAnyTaskRequireTaskbarRounding(
task.displayId,
@@ -2342,7 +2329,9 @@
// 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 @@
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 @@
displayId: Int,
transitionSource: DesktopModeTransitionSource
) {
+ logV("moveFocusedTaskToDesktop")
mainExecutor.execute {
this@DesktopTasksController.moveFocusedTaskToDesktop(displayId, transitionSource)
}
@@ -2402,12 +2393,14 @@
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 77af627..62b200a 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.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 @@
*/
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 @@
@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 @@
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 @@
): 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 @@
}
@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 @@
}
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 @@
}
}
}
+
+ 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 @@
*/
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 @@
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 c39c715..d6bb7f9 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 @@
*/
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 @@
) : 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 @@
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 @@
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 @@
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 @@
// 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 @@
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 0000000..1e5a1b2
--- /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 0000000..7afd8d7
--- /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 826de08..a428ce1 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.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 @@
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 @@
}
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 bfe1b12..ac0a627 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 @@
}
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 9e646f4..b7de1f8 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 @@
* 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 @@
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 @@
/** 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 @@
) {
// 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 @@
)
.updateZOrder(freeformTasksInZOrder)
- desktopPersistentRepositories
+ persistentRepositories
.toBuilder()
.putDesktopRepoByUser(
userId,
@@ -135,7 +143,7 @@
.build())
.build()
}
- } catch (exception: IOException) {
+ } catch (exception: Exception) {
Log.e(
TAG,
"Error in updating desktop mode related data, data is " +
@@ -154,7 +162,6 @@
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 771c3d1..a26ebbf 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 d815656..9539cbe 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 @@
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 @@
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 cd20d97..a17d55f 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.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 @@
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 @@
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
Optional<DesktopTasksController> desktopTasksController,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorationViewModel,
@@ -69,7 +70,7 @@
mContext = context;
mShellTaskOrganizer = shellTaskOrganizer;
mWindowDecorationViewModel = windowDecorationViewModel;
- mDesktopRepository = desktopRepository;
+ mDesktopUserRepositories = desktopUserRepositories;
mDesktopTasksController = desktopTasksController;
mLaunchAdjacentController = launchAdjacentController;
mTaskChangeListener = taskChangeListener;
@@ -99,8 +100,9 @@
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 @@
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 @@
// 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 @@
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 fb4afe4..af18768 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.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 @@
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 @@
@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 @@
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
mSplitScreenOptional = splitScreenOptional;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
- mDesktopRepositoryOptional = desktopRepositoryOptional;
+ mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mDisplayController = displayController;
mTaskOrganizer = shellTaskOrganizer;
@@ -764,7 +765,7 @@
// 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 @@
/** 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 7145e06..4461a5c 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.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 @@
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 @@
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 @@
/** 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 8f02c1b..b171db2 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 @@
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 @@
"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 363c95f..441f967 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.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.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 @@
*/
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 @@
// 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 @@
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
TaskStackTransitionObserver taskStackTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor
) {
@@ -141,7 +146,7 @@
return null;
}
return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
- taskStackListener, activityTaskManager, desktopRepository,
+ taskStackListener, activityTaskManager, desktopUserRepositories,
taskStackTransitionObserver, mainExecutor);
}
@@ -151,7 +156,7 @@
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
TaskStackTransitionObserver taskStackTransitionObserver,
ShellExecutor mainExecutor) {
mContext = context;
@@ -160,7 +165,7 @@
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 @@
}
@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 @@
*/
@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 @@
// 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 @@
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 @@
}
}
+ @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 4ea4613..d8884f6 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 @@
@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 @@
@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 @@
mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null;
mAnimClipRect = mClipRect != null ? new Rect() : null;
mCornerRadius = cornerRadius;
- mIsActivity = isActivity;
}
@Override
@@ -160,10 +157,6 @@
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 9fcf98b..e80016d 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 @@
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 @@
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 @@
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 @@
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 6f3aa11..aa42b7f 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 @@
@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 c9f2d2e..885f3db 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.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 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 101467d..ff52a45 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.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 @@
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 @@
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 04ef7c1..e8b02dc 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.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 @@
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 @@
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
- DesktopRepository desktopRepository,
+ DesktopUserRepositories desktopUserRepositories,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -275,7 +279,7 @@
shellCommandHandler,
windowManager,
taskOrganizer,
- desktopRepository,
+ desktopUserRepositories,
displayController,
shellController,
displayInsetsController,
@@ -315,7 +319,7 @@
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
- DesktopRepository desktopRepository,
+ DesktopUserRepositories desktopUserRepositories,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -349,7 +353,7 @@
mBgExecutor = bgExecutor;
mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
mTaskOrganizer = taskOrganizer;
- mDesktopRepository = desktopRepository;
+ mDesktopUserRepositories = desktopUserRepositories;
mShellController = shellController;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
@@ -580,34 +584,59 @@
>= 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 @@
private boolean mIsResizeGesture;
private boolean mIsDragging;
private boolean mTouchscreenInUse;
- private boolean mHasLongClicked;
private int mDragPointerId = -1;
private MotionEvent mMotionEvent;
@@ -868,12 +896,12 @@
&& 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
&& 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 @@
mContext.createContextAsUser(UserHandle.of(taskInfo.userId), 0 /* flags */),
mDisplayController,
mSplitScreenController,
- mDesktopRepository,
+ mDesktopUserRepositories,
mTaskOrganizer,
taskInfo,
taskSurface,
@@ -1608,12 +1642,13 @@
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 5eb0312..bd84ccc 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.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 @@
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 @@
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 @@
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopRepository desktopRepository,
+ DesktopUserRepositories desktopUserRepositories,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -287,7 +287,7 @@
mMultiInstanceHelper = multiInstanceHelper;
mWindowManagerWrapper = windowManagerWrapper;
mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
- mDesktopRepository = desktopRepository;
+ mDesktopUserRepositories = desktopUserRepositories;
}
/**
@@ -437,7 +437,7 @@
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 @@
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 @@
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 @@
&& 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 @@
/* openInBrowserClickListener= */ (intent) -> {
mOpenInBrowserClickListener.accept(intent);
onCapturedLinkExpired();
- if (Flags.enableDesktopWindowingAppToWebEducation()) {
+ if (Flags.enableDesktopWindowingAppToWebEducationIntegration()) {
mWindowDecorCaptionHandleRepository.onAppToWebUsage();
}
return Unit.INSTANCE;
@@ -1474,7 +1476,7 @@
mDisplayController,
mRootTaskDisplayAreaOrganizer,
mContext,
- mDesktopRepository,
+ mDesktopUserRepositories,
mSurfaceControlBuilderSupplier,
mSurfaceControlTransactionSupplier,
snapshotList,
@@ -1686,7 +1688,7 @@
/** 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 @@
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 @@
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 @@
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopRepository desktopRepository,
+ DesktopUserRepositories desktopUserRepositories,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -1840,7 +1844,7 @@
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 0000000..c470eef
--- /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 0000000..27ffd6c
--- /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 0000000..7c1479e
--- /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 0000000..00e29ec
--- /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 0e40a53..9db69d5 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.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 @@
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 @@
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 3b5c6ca..7ceac52 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.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 @@
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 @@
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 d9c36cc..f6d2cc0 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 @@
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 @@
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 @@
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 706c632..1de47df 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 7df1675..34d001c 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 7df1675..34d001c 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 d87c179..9c1a8f1 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 99969e7..02b2cec 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 19c3e40..a136936 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 7505860..34e4e74 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 fd4328d..609a284 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.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 @@
@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 d4ad4ef8..5698023 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.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 @@
@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 53725fa..880e4cd 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.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 @@
@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 a1551b7..4399a23 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.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 @@
@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 ea5b3e5..49efd1d 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 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 @@
@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 a109c4b..97cc9d2 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.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 @@
@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 14ec303..b5b7847 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.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 @@
@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 8a34b5e..f9a9df4 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.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 @@
@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 12e2328..79e2e4e 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.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 @@
@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 04016a9..14ae93a 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.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 @@
@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 6bcaabc..81162c6 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 @@
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 d82bfdd..6118d73 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 @@
// 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 dbc97d0..61c59cc 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.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 @@
@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 65b60ce..0867f65 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 @@
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.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 @@
instrumentation.uiAutomation.adoptShellPermissionIdentity()
for (permission in permissions) {
instrumentation.uiAutomation.grantRuntimePermission(
- standardAppHelper.packageName,
+ pipApp.packageName,
permission
)
}
@@ -48,18 +45,18 @@
}
}
- /** 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 @@
}
/**
- * 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 @@
}
}
- /** 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 @@
}
/**
- * 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 7b04b76..651c923 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.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 @@
@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 @@
// 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 @@
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 @@
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 6911946..be4cd78 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 @@
@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 @@
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 @@
// 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 @@
// 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 5e54f30..3e4ff30 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 @@
@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 159cba4..2c6cb503 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 @@
@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 @@
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 @@
// 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 @@
// 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 6dd3a17..a72de0f 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 @@
@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 @@
@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 c37bf35..7b6625d 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.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 @@
@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 @@
}
}
+ /**
+ * 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 @@
/** 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 c4954f9..feb3edc 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.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 @@
wmHelper: WindowManagerStateHelper,
tapl: LauncherInstrumentation,
device: UiDevice,
- primaryApp: StandardAppHelper,
- secondaryApp: StandardAppHelper,
+ primaryApp: IStandardAppHelper,
+ secondaryApp: IStandardAppHelper,
rotation: Rotation
) {
primaryApp.launchViaIntent(wmHelper)
@@ -117,8 +118,8 @@
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 0373bbd..6f3a3ec 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 @@
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/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index d5287e7..67573da 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.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.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 @@
private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
@Mock
private AccessibilityManager mAccessibilityManager;
+ @Mock
+ private DesktopUserRepositories mDesktopUserRepositories;
+ @Mock
+ private DesktopRepository mDesktopRepository;
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -137,7 +146,6 @@
@NonNull
private CompatUIStatusManager mCompatUIStatusManager;
- private boolean mInDesktopModePredicateResult;
@Before
public void setUp() {
@@ -152,6 +160,8 @@
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 @@
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 @@
@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 @@
@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 9c6afcb..07bfefe 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.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.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,7 +158,26 @@
}
@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)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `When closing change and recents are running letterbox surfaces are not destroyed`() {
runTestScenario { r ->
executeTransitionObserverTest(observerFactory = r.observerFactory) {
r.invokeShellInit()
@@ -162,13 +185,11 @@
inputBuilder {
buildTransitionInfo()
r.createClosingChange(inputBuilder = this)
+ r.configureRecentsState(running = true)
}
validateOutput {
- r.destroyEventDetected(expected = true)
- r.creationEventDetected(expected = false)
- r.visibilityEventDetected(expected = false, visible = false)
- r.updateSurfaceBoundsEventDetected(expected = false)
+ r.destroyEventDetected(expected = false)
}
}
}
@@ -197,6 +218,7 @@
private val transitions: Transitions
private val letterboxController: LetterboxController
private val letterboxObserver: LetterboxTransitionObserver
+ private val transitionStateHolder: TransitionStateHolder
val observerFactory: () -> LetterboxTransitionObserver
@@ -205,8 +227,16 @@
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 @@
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 2ea0379..41a594a 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_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 @@
@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 @@
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 @@
)
handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
- taskStackListener, resizeTransitionHandler, taskRepository)
+ taskStackListener, resizeTransitionHandler, userRepositories)
shellInit.init()
}
@@ -156,7 +159,7 @@
clearInvocations(shellInit)
handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
- taskStackListener, resizeTransitionHandler, taskRepository)
+ taskStackListener, resizeTransitionHandler, userRepositories)
verify(shellInit, never()).addInitCallback(any(),
any<DesktopActivityOrientationChangeHandler>())
@@ -180,7 +183,7 @@
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 @@
@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 @@
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 b57c55c..db4c746 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 @@
@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 @@
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 62717a3..2d55445 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.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 @@
@Mock
lateinit var transitions: Transitions
@Mock
- lateinit var desktopRepository: DesktopRepository
+ lateinit var userRepositories: DesktopUserRepositories
@Mock
lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
@Mock
@@ -103,16 +104,21 @@
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 6050695..dc7fb5f 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 @@
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 7f790d5..9059d7d 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.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 @@
@Mock private lateinit var testExecutor: ShellExecutor
@Mock private lateinit var persistentRepository: DesktopPersistentRepository
- @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
@Before
fun setUp() {
@@ -80,11 +78,9 @@
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 8e323ac..b4daa66 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.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.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 @@
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 5c00272..c10434a 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.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.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 @@
@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 @@
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 @@
controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ taskRepository = userRepositories.current
}
private fun createController(): DesktopTasksController {
@@ -338,7 +350,7 @@
toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler,
mMockDesktopImmersiveController,
- taskRepository,
+ userRepositories,
recentsTransitionHandler,
multiInstanceHelper,
shellExecutor,
@@ -377,7 +389,14 @@
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 @@
}
@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 @@
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 @@
// 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 @@
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 @@
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 @@
}
// 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 @@
}
// 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 @@
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 797b125..0712d58 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.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.verify
import org.mockito.quality.Strictness
-
/**
* Test class for {@link DesktopTasksLimiter}
*
@@ -95,9 +95,11 @@
@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 @@
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 @@
@Test
fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() {
assertFailsWith<IllegalArgumentException> {
- DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, 0,
+ DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, 0,
interactionJankMonitor, mContext, handler)
}
}
@@ -141,7 +145,7 @@
@Test
fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() {
assertFailsWith<IllegalArgumentException> {
- DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, -5,
+ DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, -5,
interactionJankMonitor, mContext, handler)
}
}
@@ -411,7 +415,7 @@
@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 7f1c1db..238483d 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.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 @@
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 @@
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 226e974..b9d7bbf 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 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 @@
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 @@
@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 @@
animExecutor,
shellInit,
transitions,
- desktopRepository,
+ desktopUserRepositories,
)
@Test
@@ -79,7 +82,7 @@
@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 8495580..4f7e80c 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 @@
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 @@
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 @@
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 9753429..1c88a29 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.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 @@
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)
- fun initWithPersistence_multipleTasks_addedCorrectly() =
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
+ fun initWithPersistence_multipleUsers_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())
+ 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(desktopRepository)
+ repositoryInitializer.initialize(desktopUserRepositories)
- verify(persistentRepository).readDesktop(any(), any())
- assertThat(desktopRepository.getActiveTasks(DEFAULT_DISPLAY))
- .containsExactly(1, 2, 3)
+ // 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(desktopRepository.getExpandedTasksOrdered(DEFAULT_DISPLAY))
- .containsExactly(1, 2)
+ assertThat(
+ desktopUserRepositories.getProfile(USER_ID_1)
+ .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+ )
+ .containsExactly(5, 1)
.inOrder()
- assertThat(desktopRepository.getMinimizedTasks(DEFAULT_DISPLAY)).containsExactly(3)
+ 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_singleUser_addedCorrectly() =
+ runTest(StandardTestDispatcher()) {
+ 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(
+ desktopUserRepositories.getProfile(USER_ID_1)
+ .getMinimizedTasks(DEFAULT_DISPLAY)
+ )
+ .containsExactly(3, 4)
+ .inOrder()
}
@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 b504a88..794ba48 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_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.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 @@
@Mock
private SurfaceControl mMockSurfaceControl;
@Mock
+ private DesktopUserRepositories mDesktopUserRepositories;
+ @Mock
private DesktopRepository mDesktopRepository;
@Mock
private DesktopTasksController mDesktopTasksController;
@@ -101,13 +105,14 @@
.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 @@
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 @@
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 @@
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 @@
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 @@
mFreeformTaskListener.onFocusTaskChanged(fullscreenTask);
- verify(mDesktopRepository, never())
+ verify(mDesktopUserRepositories.getCurrent(), never())
.addTask(fullscreenTask.displayId, fullscreenTask.taskId, fullscreenTask.isVisible);
}
@@ -214,7 +223,7 @@
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 @@
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 @@
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 @@
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 @@
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 289fd2d..2eb2c3b 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.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 @@
@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 @@
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 57b6d7f..3fe8c10 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 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 @@
@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 @@
.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 68c8aab..95f371f 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.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.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.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 @@
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private DesktopRepository mDesktopRepository;
- @Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
private DisplayInsetsController mDisplayInsetsController;
@@ -122,6 +124,10 @@
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 @@
.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 @@
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 @@
}
@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 @@
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 @@
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 @@
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 @@
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 @@
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 f0f5fe1..894d238 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.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 @@
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private DesktopRepository mDesktopRepository;
+ private DesktopUserRepositories mDesktopUserRepositories;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -112,6 +113,8 @@
@Mock
private Transitions mTransitions;
+ @Mock private DesktopRepository mDesktopRepository;
+
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -131,6 +134,7 @@
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 @@
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 0e15668..a328b5b 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 @@
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 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 1215c52..4403558 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.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 @@
@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 @@
}
@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 @@
displayController = mock(),
rootTdaOrganizer = mock(),
context = context,
- desktopRepository = desktopRepository,
+ desktopUserRepositories = userRepositories,
surfaceControlBuilderSupplier = { SurfaceControl.Builder() },
surfaceControlTransactionSupplier = { SurfaceControl.Transaction() },
snapshotList = emptyList(),
@@ -109,4 +111,8 @@
.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 153be07..a4e3af4 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.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 @@
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 @@
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 080f496..afd4607 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.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 @@
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 @@
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 @@
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 @@
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 03c7c98..61f3755 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.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 @@
@Mock
private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock
- private DesktopRepository mMockDesktopRepository;
+ private DesktopUserRepositories mMockDesktopUserRepositories;
@Mock
private Choreographer mMockChoreographer;
@Mock
@@ -214,6 +215,8 @@
private WindowDecorCaptionHandleRepository mMockCaptionHandleRepository;
@Mock
private DesktopModeEventLogger mDesktopModeEventLogger;
+ @Mock
+ private DesktopRepository mDesktopRepository;
@Captor
private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
@Captor
@@ -270,6 +273,8 @@
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 @@
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 @@
@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 @@
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 0000000..2f223de
--- /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 d290021..193c2c2 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.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 @@
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 @@
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 3b39f1e..95e2151 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.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 @@
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 @@
shellTaskOrganizer,
toggleResizeDesktopTaskTransitionHandler,
returnToDragStartAnimator,
- desktopRepository,
+ userRepositories,
desktopModeEventLogger,
)
whenever(context.createContextAsUser(any(), any())).thenReturn(context)
+ whenever(userRepositories.current).thenReturn(desktopRepository)
}
@Test
@@ -275,8 +278,8 @@
}
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 @@
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 @@
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 @@
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 e5fb755..7b45070 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -48,7 +48,14 @@
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 1ecba31..3efb5f9 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -1010,17 +1010,17 @@
* 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 0902278..d433ec87 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -21,6 +21,7 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
&& 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 @@
mName,
mFeatures,
mType,
+ mRoutingTypeFlags,
mIsSystem,
mIconUri,
mDescription,
@@ -994,6 +1053,8 @@
.append(getName())
.append(", type=")
.append(getDeviceTypeString(getType()))
+ .append(", routingTypes=")
+ .append(getRoutingTypeFlagsString(getSupportedRoutingTypes()))
.append(", isSystem=")
.append(isSystemRoute())
.append(", features=")
@@ -1035,6 +1096,7 @@
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 @@
}
}
+ /** 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 @@
@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 @@
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 @@
}
/**
+ * 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 b7e75b7..dc3fbf6 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -30,42 +30,42 @@
* @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 5005597..d4de99a 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -47,6 +47,7 @@
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 MediaQualityManager(Context context, IMediaQualityManager service) {
mContext = context;
+ mUserId = context.getUserId();
mService = service;
IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() {
@Override
@@ -219,7 +221,7 @@
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 @@
@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 @@
@NonNull
public List<PictureProfile> getAvailablePictureProfiles() {
try {
- return mService.getAvailablePictureProfiles();
+ return mService.getAvailablePictureProfiles(mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -265,7 +267,7 @@
@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 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 void createPictureProfile(@NonNull PictureProfile pp) {
try {
- mService.createPictureProfile(pp);
+ mService.createPictureProfile(pp, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -303,7 +305,7 @@
*/
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 void removePictureProfile(@NonNull String profileId) {
try {
- mService.removePictureProfile(profileId);
+ mService.removePictureProfile(profileId, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -362,7 +364,7 @@
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 @@
@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 @@
@NonNull
public List<SoundProfile> getAvailableSoundProfiles() {
try {
- return mService.getAvailableSoundProfiles();
+ return mService.getAvailableSoundProfiles(mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -409,7 +411,7 @@
@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 void createSoundProfile(@NonNull SoundProfile sp) {
try {
- mService.createSoundProfile(sp);
+ mService.createSoundProfile(sp, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -436,7 +438,7 @@
*/
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 void removeSoundProfile(@NonNull String profileId) {
try {
- mService.removeSoundProfile(profileId);
+ mService.removeSoundProfile(profileId, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -460,7 +462,7 @@
@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 @@
@NonNull
public List<String> getPictureProfileAllowList() {
try {
- return mService.getPictureProfileAllowList();
+ return mService.getPictureProfileAllowList(mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -492,7 +494,7 @@
@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 @@
@NonNull
public List<String> getSoundProfileAllowList() {
try {
- return mService.getSoundProfileAllowList();
+ return mService.getSoundProfileAllowList(mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -524,7 +526,7 @@
@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 boolean isSupported() {
try {
- return mService.isSupported();
+ return mService.isSupported(mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -554,7 +556,7 @@
@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 boolean isAutoPictureQualityEnabled() {
try {
- return mService.isAutoPictureQualityEnabled();
+ return mService.isAutoPictureQualityEnabled(mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -582,7 +584,7 @@
@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 boolean isSuperResolutionEnabled() {
try {
- return mService.isSuperResolutionEnabled();
+ return mService.isSuperResolutionEnabled(mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -611,7 +613,7 @@
@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 boolean isAutoSoundQualityEnabled() {
try {
- return mService.isAutoSoundQualityEnabled();
+ return mService.isAutoSoundQualityEnabled(mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -668,7 +670,7 @@
@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 boolean isAmbientBacklightEnabled() {
try {
- return mService.isAmbientBacklightEnabled();
+ return mService.isAmbientBacklightEnabled(mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -692,7 +694,7 @@
*/
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 572db97..3451dfc 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 @@
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 b1adb77..7f7a239 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -757,14 +757,14 @@
* 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 be65ad9..2ed642e 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.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 @@
* 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 c57be1b0..50f9fe5 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 @@
* 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 9a3527d..f0db2ea 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -31,3 +31,4 @@
# 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 cb38b53..645b275 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 e03afec..0ec5571 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 a1f0c14..38f0998 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 @@
) {
// 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 @@
// If a [controller.windowAnimatorState] exists, treat this like a takeover.
takeOverAnimationInternal(
window,
- startWindowState = null,
+ startWindowStates = null,
startTransaction = null,
callback,
)
@@ -1165,23 +1162,22 @@
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 @@
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 @@
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 @@
}
}
+ // 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 @@
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 @@
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 4e889e9..e2bc409 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.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 @@
@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 @@
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 @@
* 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 @@
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 @@
fadeWindowBackgroundLayer,
drawHole,
startVelocity,
- startFrameTime,
)
.apply { start() }
}
@@ -558,7 +515,6 @@
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 @@
startState,
endState,
startVelocity,
- startFrameTime,
windowBackgroundLayer,
transitionContainer,
transitionContainerOverlay,
@@ -767,7 +722,6 @@
startState: State,
endState: State,
startVelocity: PointF,
- startFrameTime: Long,
windowBackgroundLayer: GradientDrawable,
transitionContainer: View,
transitionContainerOverlay: ViewGroupOverlay,
@@ -958,7 +912,7 @@
}
}
- 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/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index a266e7e..c3dc84d 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.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 @@
sceneByKey: Map<SceneKey, Scene>,
overlayByKey: Map<OverlayKey, Overlay>,
initialSceneKey: SceneKey,
+ sceneTransitions: SceneTransitions,
dataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
modifier: Modifier = Modifier,
@@ -87,7 +89,7 @@
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 eb2a016..e819bfd 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 509a16c..17510c7 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 @@
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 59ac68b..501fbb0 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 @@
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 @@
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 d3ddb50..759100b 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.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 @@
swipeDetector,
transitionInterceptionThreshold,
onLayoutImpl = null,
- builder,
+ builder = builder,
)
}
@@ -261,8 +262,21 @@
* 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 @@
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 @@
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = builder,
animationScope = animationScope,
+ elements = sharedElementMap,
+ ancestorContentKeys = ancestorContentKeys,
+ lookaheadScope = lookaheadScope,
)
.also { onLayoutImpl?.invoke(it) }
}
@@ -706,6 +726,24 @@
" 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 b916b0b..bdc1461 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 @@
* 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 @@
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 @@
_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 @@
.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 0000000..599a152a
--- /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 255a16c..8c4cd8c 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.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.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 @@
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 0000000..1b5341b
--- /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 f08a180..ca50e77 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 @@
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 @@
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 @@
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 174ad30..2b76d7b 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 0000000..c6ef8cf
--- /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 2e3a934..47c10f5 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 @@
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 @@
},
) {
before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
- at(0) {
+ atAllFrames(4) {
onElement(TestElements.Foo, scene = TestScenes.SceneA)
- .assertPositionInRootIsEqualTo(10.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(interpolate(10.dp, 0.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) {
- onElement(TestElements.Foo, scene = TestScenes.SceneA)
- .assertPositionInRootIsEqualTo(2.5.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 0000000..d7a9b90
--- /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 0d2fcfc..124b61e 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.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 @@
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 @@
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 @@
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 @@
transitions { from(fromScene, to = toScene, builder = transition) },
)
},
- to = toScene,
+ changeState = changeState,
transitionLayout = { state ->
SceneTransitionLayout(state, layoutModifier) {
scene(fromScene, content = fromSceneContent)
@@ -293,13 +321,30 @@
) {
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 @@
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 @@
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 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 34c4dfb..48af2d9 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.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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
put(Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID, slotId)
put(
Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID,
- affordanceId
+ affordanceId,
)
- }
+ },
)
}
}
@@ -221,13 +223,7 @@
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 @@
}
}
}
- }
- ?: 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 @@
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 @@
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 @@
}
}
}
- }
- ?: emptyList()
+ } ?: emptyList()
}
override fun observeSelections(): Flow<List<CustomizationProviderClient.Selection>> {
@@ -454,34 +461,24 @@
}
}
- 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 @@
}
}
- 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 @@
.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 8721c78..cb167ed 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 @@
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 f5a955d..47c5bda 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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
}
}
+ 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 f1401f1..73efea7 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 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 @@
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 @@
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 21c6583..aeea99b 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 @@
}
@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 af8c357..b33a83c 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.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 @@
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 @@
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 cecb525..01baadd 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 @@
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 @@
}
@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 b8745b3..2eb4590 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.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 @@
@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 @@
@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 32ef501..09ea767 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 @@
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 67a4219..c9be7e3 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 @@
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 @@
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 266a60d..665f55b 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 @@
}
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 5270403..478050b 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 2863e29..a9133e4 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 @@
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 @@
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 @@
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 2593ceb..51eb139 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.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.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 @@
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 7fe0032..82bce0b 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 @@
}
/**
+ * 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 @@
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 @@
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 @@
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
+ mSeekByTouch = true;
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onStartTrackingTouch(seekBar);
}
@@ -322,6 +341,7 @@
@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 44dd34a..8ddd1ed 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.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 @@
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 a94df09..7a72732 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ 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 @@
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 0000000..3067ccb
--- /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 4806c3f..8f870d4 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 @@
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.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.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 @@
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 @@
defaultsRepository.requestNewDefaults(
user,
tileSpec.componentName,
- true,
+ true
)
}
.launchIn(this)
@@ -99,7 +99,7 @@
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 f9a1ad5..ab3862b 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.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.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 @@
@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 @@
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 e441a23..e36e40d 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.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 @@
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 4beec10..fe01452 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.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 @@
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 16ed59f4..c1646b8 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.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 @@
// List SceneResolver modules for supported SceneFamilies
HomeSceneFamilyResolverModule::class,
- ],
+ ]
)
object ShadelessSceneContainerFrameworkModule {
@@ -46,20 +47,12 @@
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 2311e47..ce7be83 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 @@
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 @@
*/
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 @@
* 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 1e3a233..1c15c74 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 @@
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 8c004c4..6844f05 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.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 @@
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 @@
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 @@
@Override
protected void onStart() {
super.onStart();
- if (!QSComposeFragment.isEnabled()) {
+ if (!QsInCompose.isEnabled()) {
mBrightnessController.registerCallbacks();
}
MetricsLogger.visible(this, MetricsEvent.BRIGHTNESS_DIALOG);
@@ -241,7 +241,7 @@
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 6fd2d3f..c997ac5 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 @@
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 @@
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 b67092c..0569074 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 b7ab996..7ad65fc 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.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.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 @@
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 31e4d2c..043d64e 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 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.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 @@
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 @@
}
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 5c0cc81..39b434ad 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.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 @@
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 b912361..094ec39 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 javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.coroutineScope
class VolumeDialogPlugin
@Inject
@@ -38,7 +38,7 @@
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 fb15795..434f6b5 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.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 @@
@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 7fd177d..e033624 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 @@
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 @@
@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 1c231b5..f9334df 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.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.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 @@
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 1b2b4ff..242845a 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.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 144c75d..9126f45 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 cf04d45..6dd5b63 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.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 @@
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 @@
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 d197223..d8e6aec 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.map
import kotlinx.coroutines.flow.stateIn
+@VolumeDialogScope
class VolumeDialogSlidersViewModel
-@AssistedInject
+@Inject
constructor(
@VolumeDialog coroutineScope: CoroutineScope,
private val slidersInteractor: VolumeDialogSlidersInteractor,
@@ -47,12 +48,6 @@
}
.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 f6c1743..a3166a9 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.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 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 @@
@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 @@
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)
+ animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
- launch { root.viewTreeObserver.computeInternalInsetsListener(root) }
-
- 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 e858cfe..ff525f46 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 @@
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 @@
.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 @@
}
}
- 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 0352799..7a6ede4 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.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.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.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 @@
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 fa88f62..ad5f960 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 @@
}
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 7c0c5c2..4553f98 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.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.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@SmallTest
@@ -544,9 +546,10 @@
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 929b0aa..67e03e4 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 @@
"${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 f7059e2..a64ff321 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.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 @@
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/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
index e2fc44f..eb0aee4 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 @@
}
/**
+ * 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 de9f629..a908765 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 @@
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 @@
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 3300c96..0eca818 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.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 @@
SceneContainerConfig(
sceneKeys = sceneKeys,
initialSceneKey = initialSceneKey,
+ transitions = SceneContainerTransitions,
overlayKeys = overlayKeys,
navigationDistances = navigationDistances,
)
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 0c2ce8d..66c8d0f 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -282,20 +282,12 @@
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 @@
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 e730a29..172cec3 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.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 @@
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 @@
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 @@
// 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 c545baa..99b38ed 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 @@
}
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 @@
if (root.startsWith("soc.")) return true;
if (root.startsWith("system.")) return true;
- // For PropertyInvalidatedCache
- if (root.startsWith("cache_key.")) return true;
+ // All core values should be readable
+ if (sDefaultValues.containsKey(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;
+ // 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
similarity index 62%
rename from ravenwood/runtime-helper-src/framework/android/util/Log_host.java
rename to ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
index c85bd23..7b26fe5 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 @@
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 @@
* 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 @@
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 @@
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 @@
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 9a78989..acbcdf1 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 @@
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 89fb7c3..dbbc345 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 @@
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 c1993f6..bab4c7e 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -174,6 +174,9 @@
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 @@
{ "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 a78aa8d..0000000
--- 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 c29fb7f..6d82a74 100644
--- a/ravenwood/test-authors.md
+++ b/ravenwood/test-authors.md
@@ -106,45 +106,6 @@
> **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 71a0fc4..ab556b3 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 @@
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 444844121..5703633 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -31,6 +31,14 @@
}
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 fcb7934..28d3cae 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -69,3 +69,11 @@
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 4b9065b..6069e34 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 @@
/** 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 @@
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 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 d4beb019..a1d621d 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.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 @@
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 @@
}
}
+ @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 @@
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 f88aa9b..6d60164 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -178,7 +178,6 @@
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 @@
"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 2012f56..a45b715 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.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.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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 01daceb..78dee31 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 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 @@
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 @@
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 @@
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 a32d3cb..05aeb42 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 @@
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 @@
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 @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 7660c15..e838a8d 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 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 @@
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 b70de29..709e4c2 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 e1bb8a1..4c5f652 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.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.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.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 2513443..8b9c664 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 @@
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.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.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.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 @@
@NonNull
private final BinderService mBinderService;
@NonNull
- private final HalService mHalService;
- @NonNull
private final OverrideRequestController mOverrideRequestController;
@NonNull
private final DeviceStateProviderListener mDeviceStateProviderListener;
@@ -158,7 +139,7 @@
// 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 @@
@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 @@
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 @@
@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 @@
return mBinderService;
}
- @VisibleForTesting
- IDeviceStateService getHalBinderService() {
- return mHalService;
- }
-
private void updateSupportedStates(DeviceState[] supportedDeviceStates,
@DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
synchronized (mLock) {
@@ -1311,124 +1282,6 @@
}
}
- 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 3871f2a..c3cb913 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 @@
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 dabef84..6ae58c4 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 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 @@
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 @@
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 @@
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 @@
}
}
if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+ && mBrightnessListener != null
&& BrightnessUtils.isValidBrightnessValue(brightnessState)
&& brightnessState != mCurrentBrightness) {
mCurrentBrightness = brightnessState;
@@ -661,10 +668,13 @@
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 @@
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 6e98bff..f049ef3 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 @@
}
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 @@
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 af32907..65d0ab3 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 @@
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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 42b8dd7..13aab11 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 @@
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 @@
}
IDependencyInstallerCallback serviceCallback =
- new DependencyInstallerCallbackCallOnce(handler, callback);
+ new DependencyInstallerCallbackCallOnce(handler, callback, userId);
boolean scheduleSuccess;
synchronized (mRemoteServiceLock) {
scheduleSuccess = mRemoteService.run(service -> {
@@ -125,7 +125,7 @@
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 @@
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);
}
-
- 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 && !sessionInfo.isSessionApplied
- && !sessionInfo.isSessionFailed) {
- continue;
- }
-
- if (DEBUG) {
- Slog.i(TAG, "onAllDependenciesResolved cleaning up finished"
- + " session: " + 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;
-
- notifySessionComplete(sessionId, /*success=*/success);
- }
- });
+ }
}
@Override
public void onFailureToResolveAllDependencies() throws RemoteException {
synchronized (this) {
- if (mCalled) {
- return;
+ if (mDependencyInstallerCallbackInvoked) {
+ throw new IllegalStateException(
+ "Callback is being or has been already processed");
}
- onError(mCallback, "Failed to resolve all dependencies automatically");
- mCalled = true;
+ mDependencyInstallerCallbackInvoked = true;
}
+ onError(mCallback, "Failed to resolve all dependencies automatically");
+ }
+
+ private ArraySet<Integer> validateSessionIds(int[] sessionIds) {
+ // Before creating any tracker, validate the arguments
+ ArraySet<Integer> validSessionIds = new ArraySet<>();
+
+ 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);
+ }
+
+ // 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);
+ }
+
+ // An applied session found. No need to check historical session anymore.
+ continue;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "onAllDependenciesResolved cleaning up finished"
+ + " session: " + sessionId);
+ }
+
+ 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;
+ }
+
+ private void throwValidationError(String msg) {
+ // Allow client to invoke callback again.
+ synchronized (this) {
+ mDependencyInstallerCallbackInvoked = false;
+ }
+ throw new IllegalArgumentException(msg);
}
}
@@ -377,6 +428,7 @@
// 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 @@
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 @@
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 e5e2744..8168c54 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 @@
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 @@
AppDataHelper appDataHelper,
RemovePackageHelper removePackageHelper,
DeletePackageHelper deletePackageHelper,
- BroadcastHelper broadcastHelper,
- InstallDependencyHelper installDependencyHelper) {
+ BroadcastHelper broadcastHelper) {
mPm = pm;
mInjector = pm.mInjector;
mAppDataHelper = appDataHelper;
@@ -255,7 +253,6 @@
mPackageAbiHelper = pm.mInjector.getAbiHelper();
mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
- mInstallDependencyHelper = installDependencyHelper;
}
/**
@@ -1367,10 +1364,6 @@
}
}
}
-
- 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 c66a9e9..0930299 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 @@
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 java.io.File;
-
-
// TODO: Move to .parsing sub-package
@VisibleForTesting
public interface PackageAbiHelper {
@@ -79,6 +78,23 @@
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 9db4d33..7229f07 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 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 @@
}
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 47b7850..ceb9314 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 @@
}
}
+ 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 8f8802e..891d66a 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 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.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 @@
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 @@
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 @@
}
@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 @@
}
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
}
@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 @@
&& 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 @@
}
@GuardedBy("mLock")
- private void inheritFileLocked(File origFile) {
+ private void inheritFileLocked(File origFile, List<String> artManagedFilePaths) {
mResolvedInheritedFiles.add(origFile);
maybeInheritFsveritySignatureLocked(origFile);
@@ -4111,12 +4161,20 @@
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 040b194..ab26f02 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 @@
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 0802e9e..a317e16 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 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.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 @@
* 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 @@
+ " 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 2cc08c3..a153607 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 @@
}
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 38bc026..e191ff2 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 @@
* 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 @@
* 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 @@
* 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 @@
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 @@
/**
* 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 5ae8c11..bb192c0 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 @@
}
@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 @@
|| ((requestClient.getPriority() == currentLowestPriority)
&& isRequestFromSameProcess
&& !(setResourceHolderRetain()
- && requestClient.shouldResourceHolderRetain())))) {
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
frontendHandle[0] = inUseLowestPriorityFrontend.getHandle();
reclaimOwnerId[0] = inUseLowestPriorityFrontend.getOwnerClientId();
return true;
@@ -1265,7 +1266,8 @@
|| ((requestClient.getPriority() == currentLowestPriority)
&& isRequestFromSameProcess
&& !(setResourceHolderRetain()
- && requestClient.shouldResourceHolderRetain())))) {
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
lnbHandle[0] = inUseLowestPriorityLnb.getHandle();
reclaimOwnerId[0] = inUseLowestPriorityLnb.getOwnerClientId();
return true;
@@ -1352,7 +1354,8 @@
|| ((requestClient.getPriority() == currentLowestPriority)
&& isRequestFromSameProcess
&& !(setResourceHolderRetain()
- && requestClient.shouldResourceHolderRetain())))) {
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
casSessionHandle[0] = cas.getHandle();
reclaimOwnerId[0] = lowestPriorityOwnerId;
return true;
@@ -1439,7 +1442,8 @@
|| ((requestClient.getPriority() == currentLowestPriority)
&& isRequestFromSameProcess
&& !(setResourceHolderRetain()
- && requestClient.shouldResourceHolderRetain())))) {
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
ciCamHandle[0] = ciCam.getHandle();
reclaimOwnerId[0] = lowestPriorityOwnerId;
return true;
@@ -1677,7 +1681,8 @@
|| ((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 ab5da3e..0000000
--- 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 3392d03..154897e 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.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 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 1fba297..95acb10 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.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.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 2325f35..9ccf040 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.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.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.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 16ab51e..e6a1ff9 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 0d4c373..86cee55 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 d32e5cc..79c4116 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_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 3eeeece..f7a564a 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 @@
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.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.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 08be11e..30f4ed1 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/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2e2ca14..90d3834 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 @@
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 fcaab2c..601b17c 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.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.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 @@
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 @@
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 @@
}
}
+ 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 @@
showUnsupportedDisplaySizeDialogIfNeeded(r);
showDeprecatedTargetDialogIfNeeded(r);
showDeprecatedAbiDialogIfNeeded(r);
+ if (Flags.appCompatOption16kb()) {
+ showPageSizeMismatchDialogIfNeeded(r);
+ }
}
/**
@@ -457,6 +478,41 @@
}
}
+ @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 @@
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 @@
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 @@
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 @@
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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9a33df1..fc08a91 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 @@
}
@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/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 48e1c06..59a9e85 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.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 @@
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 @@
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 @@
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 @@
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 7043aacf..cee4967 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 @@
/**
* @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 c7667b4..98521d3 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -20,6 +20,7 @@
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 0000000..8c50913
--- /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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a0c0b98..7a53ccf 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 @@
@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 112414e..fde6ce2 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 @@
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 9457205..114fe32 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 9772ef9..5db6a8f 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.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.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 47e96d3..1b56b3f 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.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 @@
}
/**
- * 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 @@
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 @@
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 @@
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 @@
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 e0b0fec..3ac7fb0 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 @@
@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 d9332ec0..6defadf 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.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.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 @@
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 @@
& 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 405024c..769f071 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 @@
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 005ceee..c7fad76 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.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 @@
@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 ef0b570..1ff347f 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.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 @@
@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 0c058df..009ce88 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -897,16 +897,6 @@
}
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 27de764..08fdaf4 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 @@
}
@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 @@
}
@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 @@
}
@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 1a593dd..42b7f4b 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 @@
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 @@
.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 @@
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 5127b2d..ab5a5a9 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 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.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 @@
}
@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 5852af7..d20f73d 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 @@
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 @@
.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 @@
.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 @@
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 @@
.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 @@
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 @@
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 @@
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 863f42f..90bf1d3 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 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 @@
@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 @@
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 3fc7c76..787f4e8 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 35b077e..cf5323e 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_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 @@
// 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 @@
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/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 757c358..78e6cbf 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 @@
}
@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 5a34b00..b96b6c5 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -221,6 +221,14 @@
/**
* 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 @@
@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 aabb058..1c59449 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSessionStats.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSessionStats.java
@@ -19,23 +19,38 @@
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 @@
mCountOfTimedOutUserMessagesWaitingForAck =
builder.mCountOfTimedOutUserMessagesWaitingForAck;
mCountOfUserMessagesInQueueToBeSent = builder.mCountOfUserMessagesInQueueToBeSent;
+ mLatencyOfSuccessfulUserMessages = builder.mLatencyOfSuccessfulUserMessages;
}
private SatelliteSessionStats(Parcel in) {
@@ -63,6 +79,19 @@
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 @@
@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 @@
sb.append("countOfUserMessagesInQueueToBeSent:");
sb.append(mCountOfUserMessagesInQueueToBeSent);
- return sb.toString();
}
@Override
@@ -110,49 +172,176 @@
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 @@
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 @@
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 8b65efd..685ae9a 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 3382c1e..5f92d7f 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 e941e79..1b90e99 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 4e06dca..ffdbb02 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 0cadd68..12670cd 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 f32e8bed..e2ac5a9 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 68ae4f1..1a4feb6 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 ec186723..481a8bb 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 eeee7b4..0000000
--- 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 fd13d14..d5334cb 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.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 @@
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 db4838e..de17bf4 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 @@
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 @@
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 @@
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 @@
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 68ec123..a4c898d 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 78cc48b..8bacf49 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 662b8e5..36a89f9 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 @@
),
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 @@
),
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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
similarity index 97%
rename from tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java
rename to tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java
index e9e7078..47638b0 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_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
similarity index 99%
rename from tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java
rename to tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java
index 9c6d852..c84e600 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 4ab8e6a..26a2a06 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.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.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 f1f74bc..b999475 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 @@
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 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 20b7f1f..76be232 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.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.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 613b926..b9fe76a 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.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 441a4ae..5db02e3 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 @@
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 d85c515..4f34f9f 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_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 1d68721..a315b069 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;