Merge "[Flexiglass] Fold NotificationsHeadsUpRefactor flag into SceneContainerFlag" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index edb119e..6f8a189 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -438,10 +438,23 @@
name: "android.companion.virtualdevice.flags-aconfig",
package: "android.companion.virtualdevice.flags",
container: "system",
+ exportable: true,
srcs: ["core/java/android/companion/virtual/flags/*.aconfig"],
}
java_aconfig_library {
+ name: "android.companion.virtualdevice.flags-aconfig-java-export",
+ aconfig_declarations: "android.companion.virtualdevice.flags-aconfig",
+ 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: "android.companion.virtual.flags-aconfig-java",
aconfig_declarations: "android.companion.virtual.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index d92351d..c9d3407 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1989,10 +1989,8 @@
mAdminProtectedPackages.put(userId, packageNames);
}
}
- if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
- if (!Flags.avoidIdleCheck() || mInjector.getBootPhase() >= PHASE_BOOT_COMPLETED) {
- postCheckIdleStates(userId);
- }
+ if (!Flags.avoidIdleCheck() || mInjector.getBootPhase() >= PHASE_BOOT_COMPLETED) {
+ postCheckIdleStates(userId);
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 861be40..ed8ab06 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -141,7 +141,7 @@
field public static final String MANAGE_DEVICE_POLICY_APPS_CONTROL = "android.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL";
field public static final String MANAGE_DEVICE_POLICY_APP_RESTRICTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS";
field public static final String MANAGE_DEVICE_POLICY_APP_USER_DATA = "android.permission.MANAGE_DEVICE_POLICY_APP_USER_DATA";
- field @FlaggedApi("android.app.admin.flags.assist_content_user_restriction_enabled") public static final String MANAGE_DEVICE_POLICY_ASSIST_CONTENT = "android.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT";
+ field public static final String MANAGE_DEVICE_POLICY_ASSIST_CONTENT = "android.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT";
field public static final String MANAGE_DEVICE_POLICY_AUDIO_OUTPUT = "android.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT";
field public static final String MANAGE_DEVICE_POLICY_AUTOFILL = "android.permission.MANAGE_DEVICE_POLICY_AUTOFILL";
field public static final String MANAGE_DEVICE_POLICY_BACKUP_SERVICE = "android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE";
@@ -169,7 +169,7 @@
field public static final String MANAGE_DEVICE_POLICY_LOCK = "android.permission.MANAGE_DEVICE_POLICY_LOCK";
field public static final String MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS = "android.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS";
field public static final String MANAGE_DEVICE_POLICY_LOCK_TASK = "android.permission.MANAGE_DEVICE_POLICY_LOCK_TASK";
- field @FlaggedApi("android.app.admin.flags.esim_management_enabled") public static final String MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS = "android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS";
+ field public static final String MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS = "android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS";
field public static final String MANAGE_DEVICE_POLICY_METERED_DATA = "android.permission.MANAGE_DEVICE_POLICY_METERED_DATA";
field public static final String MANAGE_DEVICE_POLICY_MICROPHONE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE";
field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE";
@@ -7875,7 +7875,7 @@
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DeviceAdminInfo> CREATOR;
field public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1; // 0x1
- field @FlaggedApi("android.app.admin.flags.headless_device_owner_single_user_enabled") public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2; // 0x2
+ field public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2; // 0x2
field public static final int HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED = 0; // 0x0
field public static final int USES_ENCRYPTED_STORAGE = 7; // 0x7
field public static final int USES_POLICY_DISABLE_CAMERA = 8; // 0x8
@@ -8081,7 +8081,7 @@
method public CharSequence getStartUserSessionMessage(@NonNull android.content.ComponentName);
method @Deprecated public boolean getStorageEncryption(@Nullable android.content.ComponentName);
method public int getStorageEncryptionStatus();
- method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionIds();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionIds();
method @Nullable public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
method @Nullable public android.os.PersistableBundle getTransferOwnershipBundle();
method @Nullable public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(@Nullable android.content.ComponentName, @NonNull android.content.ComponentName);
@@ -34116,7 +34116,7 @@
field public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
field public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
field public static final String DISALLOW_APPS_CONTROL = "no_control_apps";
- field @FlaggedApi("android.app.admin.flags.assist_content_user_restriction_enabled") public static final String DISALLOW_ASSIST_CONTENT = "no_assist_content";
+ field public static final String DISALLOW_ASSIST_CONTENT = "no_assist_content";
field public static final String DISALLOW_AUTOFILL = "no_autofill";
field public static final String DISALLOW_BLUETOOTH = "no_bluetooth";
field public static final String DISALLOW_BLUETOOTH_SHARING = "no_bluetooth_sharing";
@@ -34166,7 +34166,7 @@
field public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile";
field public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi";
- field @FlaggedApi("android.app.admin.flags.esim_management_enabled") public static final String DISALLOW_SIM_GLOBALLY = "no_sim_globally";
+ field public static final String DISALLOW_SIM_GLOBALLY = "no_sim_globally";
field public static final String DISALLOW_SMS = "no_sms";
field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
field @FlaggedApi("com.android.net.thread.platform.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
@@ -43968,8 +43968,11 @@
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ESOS_INACTIVITY_TIMEOUT_SEC_INT = "satellite_esos_inactivity_timeout_sec_int";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool";
- field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT = "satellite_screen_off_inactivity_timeout_duration_sec_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT = "satellite_p2p_sms_inactivity_timeout_sec_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL = "satellite_roaming_p2p_sms_supported_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT = "satellite_screen_off_inactivity_timeout_sec_int";
field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e87bc50..f26522b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1422,7 +1422,7 @@
field public static final int STATUS_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
field public static final int STATUS_HAS_DEVICE_OWNER = 1; // 0x1
field public static final int STATUS_HAS_PAIRED = 8; // 0x8
- field @FlaggedApi("android.app.admin.flags.headless_device_owner_single_user_enabled") public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17; // 0x11
+ field public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17; // 0x11
field public static final int STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED = 16; // 0x10
field public static final int STATUS_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
field public static final int STATUS_NONSYSTEM_USER_EXISTS = 5; // 0x5
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0a35c5a..009d082 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3138,6 +3138,7 @@
public abstract class DreamOverlayService extends android.app.Service {
ctor public DreamOverlayService();
+ method @FlaggedApi("android.service.dreams.publish_preview_state_to_overlay") public final boolean isDreamInPreviewMode();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public void onEndDream();
method public abstract void onStartDream(@NonNull android.view.WindowManager.LayoutParams);
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 14195c4..63e03914 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -1326,6 +1326,7 @@
private boolean mClock;
private boolean mNotificationIcons;
private boolean mRotationSuggestion;
+ private boolean mQuickSettings;
/** @hide */
public DisableInfo(int flags1, int flags2) {
@@ -1338,6 +1339,7 @@
mClock = (flags1 & DISABLE_CLOCK) != 0;
mNotificationIcons = (flags1 & DISABLE_NOTIFICATION_ICONS) != 0;
mRotationSuggestion = (flags2 & DISABLE2_ROTATE_SUGGESTIONS) != 0;
+ mQuickSettings = (flags2 & DISABLE2_QUICK_SETTINGS) != 0;
}
/** @hide */
@@ -1471,6 +1473,20 @@
}
/**
+ * @hide
+ */
+ public void setQuickSettingsDisabled(boolean disabled) {
+ mQuickSettings = disabled;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isQuickSettingsDisabled() {
+ return mQuickSettings;
+ }
+
+ /**
* @return {@code true} if no components are disabled (default state)
* @hide
*/
@@ -1478,7 +1494,7 @@
public boolean areAllComponentsEnabled() {
return !mStatusBarExpansion && !mNavigateHome && !mNotificationPeeking && !mRecents
&& !mSearch && !mSystemIcons && !mClock && !mNotificationIcons
- && !mRotationSuggestion;
+ && !mRotationSuggestion && !mQuickSettings;
}
/** @hide */
@@ -1492,6 +1508,7 @@
mClock = false;
mNotificationIcons = false;
mRotationSuggestion = false;
+ mQuickSettings = false;
}
/**
@@ -1502,7 +1519,7 @@
public boolean areAllComponentsDisabled() {
return mStatusBarExpansion && mNavigateHome && mNotificationPeeking
&& mRecents && mSearch && mSystemIcons && mClock && mNotificationIcons
- && mRotationSuggestion;
+ && mRotationSuggestion && mQuickSettings;
}
/** @hide */
@@ -1516,6 +1533,7 @@
mClock = true;
mNotificationIcons = true;
mRotationSuggestion = true;
+ mQuickSettings = true;
}
@NonNull
@@ -1533,6 +1551,7 @@
sb.append(" mClock=").append(mClock ? "disabled" : "enabled");
sb.append(" mNotificationIcons=").append(mNotificationIcons ? "disabled" : "enabled");
sb.append(" mRotationSuggestion=").append(mRotationSuggestion ? "disabled" : "enabled");
+ sb.append(" mQuickSettings=").append(mQuickSettings ? "disabled" : "enabled");
return sb.toString();
@@ -1557,6 +1576,7 @@
if (mClock) disable1 |= DISABLE_CLOCK;
if (mNotificationIcons) disable1 |= DISABLE_NOTIFICATION_ICONS;
if (mRotationSuggestion) disable2 |= DISABLE2_ROTATE_SUGGESTIONS;
+ if (mQuickSettings) disable2 |= DISABLE2_QUICK_SETTINGS;
return new Pair<Integer, Integer>(disable1, disable2);
}
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 46c9e78..4f2efa4 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -16,9 +16,6 @@
package android.app.admin;
-import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
-
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.admin.flags.Flags;
@@ -195,7 +192,6 @@
* DPCs should set the value of attribute "headless-device-owner-mode" inside the
* "headless-system-user" tag as "single_user".
*/
- @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 5088ea6..d31d8f2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -56,10 +56,8 @@
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
-import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_PROVISIONING_FIX_ENABLED;
-import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import static android.app.admin.flags.Flags.onboardingConsentlessBugreports;
import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED;
@@ -2988,7 +2986,6 @@
* @hide
*/
@SystemApi
- @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17;
/**
@@ -17744,7 +17741,6 @@
* @throws SecurityException if the caller is not authorized to call this method.
* @return ids of all managed subscriptions currently downloaded by an admin on the device.
*/
- @FlaggedApi(FLAG_ESIM_MANAGEMENT_ENABLED)
@RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS)
@NonNull
public Set<Integer> getSubscriptionIds() {
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 56f4792..29a5048 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -125,13 +125,6 @@
}
flag {
- name: "dumpsys_policy_engine_migration_enabled"
- namespace: "enterprise"
- description: "Update DumpSys to include information about migrated APIs in DPE"
- bug: "304999634"
-}
-
-flag {
name: "allow_querying_profile_type"
is_exported: true
namespace: "enterprise"
@@ -146,6 +139,7 @@
bug: "293441361"
}
+# Fully rolled out and must not be used.
flag {
name: "assist_content_user_restriction_enabled"
is_exported: true
@@ -172,6 +166,7 @@
bug: "304999634"
}
+# Fully rolled out and must not be used.
flag {
name: "esim_management_enabled"
is_exported: true
@@ -180,6 +175,7 @@
bug: "295301164"
}
+# Fully rolled out and must not be used.
flag {
name: "headless_device_owner_single_user_enabled"
is_exported: true
@@ -207,26 +203,6 @@
}
flag {
- name: "power_exemption_bg_usage_fix"
- namespace: "enterprise"
- description: "Ensure aps with EXEMPT_FROM_POWER_RESTRICTIONS can execute in the background"
- bug: "333379020"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- name: "disallow_user_control_bg_usage_fix"
- namespace: "enterprise"
- description: "Make DPM.setUserControlDisabledPackages() ensure background usage is allowed"
- bug: "326031059"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "disallow_user_control_stopped_state_fix"
namespace: "enterprise"
description: "Ensure DPM.setUserControlDisabledPackages() clears FLAG_STOPPED for the app"
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index b4c36e1..22a9ccf 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -50,6 +50,7 @@
name: "activity_control_api"
description: "Enable APIs for fine grained activity policy, fallback and callbacks"
bug: "333443509"
+ is_exported: true
}
flag {
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 7fcfbbc..d7a517a 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -655,6 +655,7 @@
mAttributionSourceState.token = current.getToken();
mAttributionSourceState.renouncedPermissions =
current.mAttributionSourceState.renouncedPermissions;
+ mBuilderFieldsSet |= 0x2 | 0x4 | 0x8 | 0x10;
}
/**
diff --git a/core/java/android/hardware/biometrics/AuthenticateOptions.java b/core/java/android/hardware/biometrics/AuthenticateOptions.java
index 7766071..4dc6ea19 100644
--- a/core/java/android/hardware/biometrics/AuthenticateOptions.java
+++ b/core/java/android/hardware/biometrics/AuthenticateOptions.java
@@ -74,4 +74,7 @@
/** The attribution tag, if any. */
@Nullable String getAttributionTag();
+
+ /** If the authentication is requested due to mandatory biometrics being active. */
+ boolean isMandatoryBiometrics();
}
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index 17cd18c..b195225 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -49,7 +49,7 @@
void prepareForAuthentication(boolean requireConfirmation, IBinder token, long operationId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
long requestId, int cookie, boolean allowBackgroundAuthentication,
- boolean isForLegacyFingerprintManager);
+ boolean isForLegacyFingerprintManager, boolean isMandatoryBiometrics);
// Starts authentication with the previously prepared client.
void startPreparedClient(int cookie);
diff --git a/core/java/android/hardware/face/FaceAuthenticateOptions.java b/core/java/android/hardware/face/FaceAuthenticateOptions.java
index 518f902a..8babbfa 100644
--- a/core/java/android/hardware/face/FaceAuthenticateOptions.java
+++ b/core/java/android/hardware/face/FaceAuthenticateOptions.java
@@ -120,6 +120,8 @@
}
+ /** If the authentication is requested due to mandatory biometrics being active. */
+ private boolean mIsMandatoryBiometrics;
// Code below generated by codegen v1.0.23.
//
@@ -188,7 +190,8 @@
@AuthenticateReason int authenticateReason,
@PowerManager.WakeReason int wakeReason,
@NonNull String opPackageName,
- @Nullable String attributionTag) {
+ @Nullable String attributionTag,
+ boolean isMandatoryBiometrics) {
this.mUserId = userId;
this.mSensorId = sensorId;
this.mDisplayState = displayState;
@@ -229,6 +232,7 @@
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mOpPackageName);
this.mAttributionTag = attributionTag;
+ this.mIsMandatoryBiometrics = isMandatoryBiometrics;
// onConstructed(); // You can define this method to get a callback
}
@@ -261,7 +265,7 @@
* The reason for this operation when requested by the system (sysui),
* otherwise AUTHENTICATE_REASON_UNKNOWN.
*
- * See packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
+ * See frameworks/base/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
* for more details about each reason.
*/
@DataClass.Generated.Member
@@ -299,6 +303,14 @@
}
/**
+ * If the authentication is requested due to mandatory biometrics being active.
+ */
+ @DataClass.Generated.Member
+ public boolean isMandatoryBiometrics() {
+ return mIsMandatoryBiometrics;
+ }
+
+ /**
* The sensor id for this operation.
*/
@DataClass.Generated.Member
@@ -332,6 +344,15 @@
return this;
}
+ /**
+ * If the authentication is requested due to mandatory biometrics being active.
+ */
+ @DataClass.Generated.Member
+ public @NonNull FaceAuthenticateOptions setIsMandatoryBiometrics( boolean value) {
+ mIsMandatoryBiometrics = value;
+ return this;
+ }
+
@Override
@DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
@@ -351,7 +372,8 @@
&& mAuthenticateReason == that.mAuthenticateReason
&& mWakeReason == that.mWakeReason
&& java.util.Objects.equals(mOpPackageName, that.mOpPackageName)
- && java.util.Objects.equals(mAttributionTag, that.mAttributionTag);
+ && java.util.Objects.equals(mAttributionTag, that.mAttributionTag)
+ && mIsMandatoryBiometrics == that.mIsMandatoryBiometrics;
}
@Override
@@ -368,6 +390,7 @@
_hash = 31 * _hash + mWakeReason;
_hash = 31 * _hash + java.util.Objects.hashCode(mOpPackageName);
_hash = 31 * _hash + java.util.Objects.hashCode(mAttributionTag);
+ _hash = 31 * _hash + Boolean.hashCode(mIsMandatoryBiometrics);
return _hash;
}
@@ -377,9 +400,10 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
- byte flg = 0;
+ int flg = 0;
+ if (mIsMandatoryBiometrics) flg |= 0x80;
if (mAttributionTag != null) flg |= 0x40;
- dest.writeByte(flg);
+ dest.writeInt(flg);
dest.writeInt(mUserId);
dest.writeInt(mSensorId);
dest.writeInt(mDisplayState);
@@ -400,7 +424,8 @@
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
- byte flg = in.readByte();
+ int flg = in.readInt();
+ boolean isMandatoryBiometrics = (flg & 0x80) != 0;
int userId = in.readInt();
int sensorId = in.readInt();
int displayState = in.readInt();
@@ -449,6 +474,7 @@
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mOpPackageName);
this.mAttributionTag = attributionTag;
+ this.mIsMandatoryBiometrics = isMandatoryBiometrics;
// onConstructed(); // You can define this method to get a callback
}
@@ -481,6 +507,7 @@
private @PowerManager.WakeReason int mWakeReason;
private @NonNull String mOpPackageName;
private @Nullable String mAttributionTag;
+ private boolean mIsMandatoryBiometrics;
private long mBuilderFieldsSet = 0L;
@@ -524,7 +551,7 @@
* The reason for this operation when requested by the system (sysui),
* otherwise AUTHENTICATE_REASON_UNKNOWN.
*
- * See packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
+ * See frameworks/base/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
* for more details about each reason.
*/
@DataClass.Generated.Member
@@ -573,10 +600,21 @@
return this;
}
+ /**
+ * If the authentication is requested due to mandatory biometrics being active.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setIsMandatoryBiometrics(boolean value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x80;
+ mIsMandatoryBiometrics = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull FaceAuthenticateOptions build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x80; // Mark builder used
+ mBuilderFieldsSet |= 0x100; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mUserId = defaultUserId();
@@ -606,12 +644,13 @@
mAuthenticateReason,
mWakeReason,
mOpPackageName,
- mAttributionTag);
+ mAttributionTag,
+ mIsMandatoryBiometrics);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x80) != 0) {
+ if ((mBuilderFieldsSet & 0x100) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -619,10 +658,10 @@
}
@DataClass.Generated(
- time = 1677119626034L,
+ time = 1723436679828L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/hardware/face/FaceAuthenticateOptions.java",
- inputSignatures = "private final int mUserId\nprivate int mSensorId\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\npublic static final int AUTHENTICATE_REASON_UNKNOWN\npublic static final int AUTHENTICATE_REASON_STARTED_WAKING_UP\npublic static final int AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN\npublic static final int AUTHENTICATE_REASON_ASSISTANT_VISIBLE\npublic static final int AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN\npublic static final int AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED\npublic static final int AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED\npublic static final int AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED\npublic static final int AUTHENTICATE_REASON_QS_EXPANDED\npublic static final int AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER\npublic static final int AUTHENTICATE_REASON_UDFPS_POINTER_DOWN\nprivate final @android.hardware.face.FaceAuthenticateOptions.AuthenticateReason int mAuthenticateReason\nprivate final @android.os.PowerManager.WakeReason int mWakeReason\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static int defaultUserId()\nprivate static int defaultSensorId()\nprivate static int defaultDisplayState()\nprivate static int defaultAuthenticateReason()\nprivate static int defaultWakeReason()\nprivate static java.lang.String defaultOpPackageName()\nprivate static java.lang.String defaultAttributionTag()\nclass FaceAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
+ inputSignatures = "private final int mUserId\nprivate int mSensorId\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\npublic static final int AUTHENTICATE_REASON_UNKNOWN\npublic static final int AUTHENTICATE_REASON_STARTED_WAKING_UP\npublic static final int AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN\npublic static final int AUTHENTICATE_REASON_ASSISTANT_VISIBLE\npublic static final int AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN\npublic static final int AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED\npublic static final int AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED\npublic static final int AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED\npublic static final int AUTHENTICATE_REASON_QS_EXPANDED\npublic static final int AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER\npublic static final int AUTHENTICATE_REASON_UDFPS_POINTER_DOWN\nprivate final @android.hardware.face.FaceAuthenticateOptions.AuthenticateReason int mAuthenticateReason\nprivate final @android.os.PowerManager.WakeReason int mWakeReason\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate boolean mIsMandatoryBiometrics\nprivate static int defaultUserId()\nprivate static int defaultSensorId()\nprivate static int defaultDisplayState()\nprivate static int defaultAuthenticateReason()\nprivate static int defaultWakeReason()\nprivate static java.lang.String defaultOpPackageName()\nprivate static java.lang.String defaultAttributionTag()\nclass FaceAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java b/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
index dc66542..ddf1e5b 100644
--- a/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
+++ b/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
@@ -97,6 +97,11 @@
return null;
}
+ /**
+ * If the authentication is requested due to mandatory biometrics being active.
+ */
+ private boolean mIsMandatoryBiometrics;
+
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
@@ -118,7 +123,8 @@
@AuthenticateOptions.DisplayState int displayState,
@NonNull String opPackageName,
@Nullable String attributionTag,
- @Nullable AuthenticateReason.Vendor vendorReason) {
+ @Nullable AuthenticateReason.Vendor vendorReason,
+ boolean isMandatoryBiometrics) {
this.mUserId = userId;
this.mSensorId = sensorId;
this.mIgnoreEnrollmentState = ignoreEnrollmentState;
@@ -130,6 +136,7 @@
NonNull.class, null, mOpPackageName);
this.mAttributionTag = attributionTag;
this.mVendorReason = vendorReason;
+ this.mIsMandatoryBiometrics = isMandatoryBiometrics;
// onConstructed(); // You can define this method to get a callback
}
@@ -199,6 +206,14 @@
}
/**
+ * If the authentication is requested due to mandatory biometrics being active.
+ */
+ @DataClass.Generated.Member
+ public boolean isMandatoryBiometrics() {
+ return mIsMandatoryBiometrics;
+ }
+
+ /**
* The sensor id for this operation.
*/
@DataClass.Generated.Member
@@ -244,6 +259,15 @@
return this;
}
+ /**
+ * If the authentication is requested due to mandatory biometrics being active.
+ */
+ @DataClass.Generated.Member
+ public @NonNull FingerprintAuthenticateOptions setIsMandatoryBiometrics( boolean value) {
+ mIsMandatoryBiometrics = value;
+ return this;
+ }
+
@Override
@DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
@@ -263,7 +287,8 @@
&& mDisplayState == that.mDisplayState
&& java.util.Objects.equals(mOpPackageName, that.mOpPackageName)
&& java.util.Objects.equals(mAttributionTag, that.mAttributionTag)
- && java.util.Objects.equals(mVendorReason, that.mVendorReason);
+ && java.util.Objects.equals(mVendorReason, that.mVendorReason)
+ && mIsMandatoryBiometrics == that.mIsMandatoryBiometrics;
}
@Override
@@ -280,6 +305,7 @@
_hash = 31 * _hash + java.util.Objects.hashCode(mOpPackageName);
_hash = 31 * _hash + java.util.Objects.hashCode(mAttributionTag);
_hash = 31 * _hash + java.util.Objects.hashCode(mVendorReason);
+ _hash = 31 * _hash + Boolean.hashCode(mIsMandatoryBiometrics);
return _hash;
}
@@ -289,11 +315,12 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
- byte flg = 0;
+ int flg = 0;
if (mIgnoreEnrollmentState) flg |= 0x4;
+ if (mIsMandatoryBiometrics) flg |= 0x80;
if (mAttributionTag != null) flg |= 0x20;
if (mVendorReason != null) flg |= 0x40;
- dest.writeByte(flg);
+ dest.writeInt(flg);
dest.writeInt(mUserId);
dest.writeInt(mSensorId);
dest.writeInt(mDisplayState);
@@ -313,8 +340,9 @@
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
- byte flg = in.readByte();
+ int flg = in.readInt();
boolean ignoreEnrollmentState = (flg & 0x4) != 0;
+ boolean isMandatoryBiometrics = (flg & 0x80) != 0;
int userId = in.readInt();
int sensorId = in.readInt();
int displayState = in.readInt();
@@ -333,6 +361,7 @@
NonNull.class, null, mOpPackageName);
this.mAttributionTag = attributionTag;
this.mVendorReason = vendorReason;
+ this.mIsMandatoryBiometrics = isMandatoryBiometrics;
// onConstructed(); // You can define this method to get a callback
}
@@ -365,6 +394,7 @@
private @NonNull String mOpPackageName;
private @Nullable String mAttributionTag;
private @Nullable AuthenticateReason.Vendor mVendorReason;
+ private boolean mIsMandatoryBiometrics;
private long mBuilderFieldsSet = 0L;
@@ -456,10 +486,21 @@
return this;
}
+ /**
+ * If the authentication is requested due to mandatory biometrics being active.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setIsMandatoryBiometrics(boolean value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x80;
+ mIsMandatoryBiometrics = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull FingerprintAuthenticateOptions build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x80; // Mark builder used
+ mBuilderFieldsSet |= 0x100; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mUserId = defaultUserId();
@@ -489,12 +530,13 @@
mDisplayState,
mOpPackageName,
mAttributionTag,
- mVendorReason);
+ mVendorReason,
+ mIsMandatoryBiometrics);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x80) != 0) {
+ if ((mBuilderFieldsSet & 0x100) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -502,10 +544,10 @@
}
@DataClass.Generated(
- time = 1689703591032L,
+ time = 1723436831455L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java",
- inputSignatures = "private final int mUserId\nprivate int mSensorId\nprivate final boolean mIgnoreEnrollmentState\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate @android.annotation.Nullable android.hardware.biometrics.common.AuthenticateReason.Vendor mVendorReason\nprivate static int defaultUserId()\nprivate static int defaultSensorId()\nprivate static boolean defaultIgnoreEnrollmentState()\nprivate static int defaultDisplayState()\nprivate static java.lang.String defaultOpPackageName()\nprivate static java.lang.String defaultAttributionTag()\nprivate static android.hardware.biometrics.common.AuthenticateReason.Vendor defaultVendorReason()\nclass FingerprintAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
+ inputSignatures = "private final int mUserId\nprivate int mSensorId\nprivate final boolean mIgnoreEnrollmentState\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate @android.annotation.Nullable android.hardware.biometrics.common.AuthenticateReason.Vendor mVendorReason\nprivate boolean mIsMandatoryBiometrics\nprivate static int defaultUserId()\nprivate static int defaultSensorId()\nprivate static boolean defaultIgnoreEnrollmentState()\nprivate static int defaultDisplayState()\nprivate static java.lang.String defaultOpPackageName()\nprivate static java.lang.String defaultAttributionTag()\nprivate static android.hardware.biometrics.common.AuthenticateReason.Vendor defaultVendorReason()\nclass FingerprintAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index 23bd30a..0ed1ab6 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -314,6 +314,15 @@
}
/**
+ * @see #currentNetworkTimeMillis(ITimeDetectorService)
+ * @hide
+ */
+ public static long currentNetworkTimeMillis() {
+ return currentNetworkTimeMillis(ITimeDetectorService.Stub
+ .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE)));
+ }
+
+ /**
* Returns milliseconds since January 1, 1970 00:00:00.0 UTC, synchronized
* using a remote network source outside the device.
* <p>
@@ -331,14 +340,14 @@
* at any time. Due to network delays, variations between servers, or local
* (client side) clock drift, the accuracy of the returned times cannot be
* guaranteed. In extreme cases, consecutive calls to {@link
- * #currentNetworkTimeMillis()} could return times that are out of order.
+ * #currentNetworkTimeMillis(ITimeDetectorService)} could return times that
+ * are out of order.
*
* @throws DateTimeException when no network time can be provided.
* @hide
*/
- public static long currentNetworkTimeMillis() {
- ITimeDetectorService timeDetectorService = ITimeDetectorService.Stub
- .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE));
+ public static long currentNetworkTimeMillis(
+ ITimeDetectorService timeDetectorService) {
if (timeDetectorService != null) {
UnixEpochTime time;
try {
@@ -380,16 +389,21 @@
* at any time. Due to network delays, variations between servers, or local
* (client side) clock drift, the accuracy of the returned times cannot be
* guaranteed. In extreme cases, consecutive calls to {@link
- * Clock#millis()} on the returned {@link Clock}could return times that are
+ * Clock#millis()} on the returned {@link Clock} could return times that are
* out of order.
*
* @throws DateTimeException when no network time can be provided.
*/
public static @NonNull Clock currentNetworkTimeClock() {
return new SimpleClock(ZoneOffset.UTC) {
+ private ITimeDetectorService mSvc;
@Override
public long millis() {
- return SystemClock.currentNetworkTimeMillis();
+ if (mSvc == null) {
+ mSvc = ITimeDetectorService.Stub
+ .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE));
+ }
+ return SystemClock.currentNetworkTimeMillis(mSvc);
}
};
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 536eca6..f1ec0e4e 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1978,7 +1978,6 @@
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
- @FlaggedApi(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED)
public static final String DISALLOW_SIM_GLOBALLY =
"no_sim_globally";
@@ -1999,7 +1998,6 @@
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
- @FlaggedApi(android.app.admin.flags.Flags.FLAG_ASSIST_CONTENT_USER_RESTRICTION_ENABLED)
public static final String DISALLOW_ASSIST_CONTENT = "no_assist_content";
/**
diff --git a/core/java/android/permission/TEST_MAPPING b/core/java/android/permission/TEST_MAPPING
index a15d9bc..b317b80 100644
--- a/core/java/android/permission/TEST_MAPPING
+++ b/core/java/android/permission/TEST_MAPPING
@@ -1,15 +1,7 @@
{
"presubmit": [
{
- "name": "CtsPermissionTestCases",
- "options": [
- {
- "include-filter": "android.permission.cts.PermissionControllerTest"
- },
- {
- "include-filter": "android.permission.cts.RuntimePermissionPresentationInfoTest"
- }
- ]
+ "name": "CtsPermissionTestCases_Platform"
}
],
"postsubmit": [
@@ -36,4 +28,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 85d2325..24f52d0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -19666,6 +19666,8 @@
public static final int PAIRED_DEVICE_OS_TYPE_ANDROID = 1;
/** @hide */
public static final int PAIRED_DEVICE_OS_TYPE_IOS = 2;
+ /** @hide */
+ public static final int PAIRED_DEVICE_OS_TYPE_NONE = 3;
/**
* The bluetooth settings selected BLE role for the companion.
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 013ec5f..711c414 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -51,11 +51,14 @@
*/
private Executor mExecutor;
+ private Boolean mCurrentRedirectToWake;
+
// An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
// requests to the {@link DreamOverlayService}
private static class OverlayClient extends IDreamOverlayClient.Stub {
private final WeakReference<DreamOverlayService> mService;
private boolean mShowComplications;
+ private boolean mIsPreview;
private ComponentName mDreamComponent;
IDreamOverlayCallback mDreamOverlayCallback;
@@ -73,9 +76,11 @@
@Override
public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
- String dreamComponent, boolean shouldShowComplications) throws RemoteException {
+ String dreamComponent, boolean isPreview, boolean shouldShowComplications)
+ throws RemoteException {
mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
mShowComplications = shouldShowComplications;
+ mIsPreview = isPreview;
mDreamOverlayCallback = callback;
applyToDream(dreamOverlayService -> dreamOverlayService.startDream(this, params));
}
@@ -122,6 +127,10 @@
return mShowComplications;
}
+ private boolean isDreamInPreviewMode() {
+ return mIsPreview;
+ }
+
private ComponentName getComponent() {
return mDreamComponent;
}
@@ -132,6 +141,10 @@
mExecutor.execute(() -> {
endDreamInternal(mCurrentClient);
mCurrentClient = client;
+ if (Flags.dreamWakeRedirect() && mCurrentRedirectToWake != null) {
+ mCurrentClient.redirectWake(mCurrentRedirectToWake);
+ }
+
onStartDream(params);
});
}
@@ -282,8 +295,10 @@
return;
}
+ mCurrentRedirectToWake = redirect;
+
if (mCurrentClient == null) {
- throw new IllegalStateException("redirected wake with no dream present");
+ return;
}
mCurrentClient.redirectWake(redirect);
@@ -295,7 +310,6 @@
*
* @hide
*/
- @FlaggedApi(Flags.FLAG_DREAM_WAKE_REDIRECT)
public void onWakeRequested() {
}
@@ -312,6 +326,19 @@
}
/**
+ * Returns whether dream is in preview mode.
+ */
+ @FlaggedApi(Flags.FLAG_PUBLISH_PREVIEW_STATE_TO_OVERLAY)
+ public final boolean isDreamInPreviewMode() {
+ if (mCurrentClient == null) {
+ throw new IllegalStateException(
+ "requested if preview when no dream active");
+ }
+
+ return mCurrentClient.isDreamInPreviewMode();
+ }
+
+ /**
* Returns the active dream component.
* @hide
*/
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index c3585e3..ce31e1e 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1701,6 +1701,7 @@
try {
overlay.startDream(mWindow.getAttributes(), mOverlayCallback,
mDreamComponent.flattenToString(),
+ mPreviewMode,
mShouldShowComplications);
} catch (RemoteException e) {
Log.e(mTag, "could not send window attributes:" + e);
diff --git a/core/java/android/service/dreams/IDreamOverlayClient.aidl b/core/java/android/service/dreams/IDreamOverlayClient.aidl
index 0eb15a0..e9df402 100644
--- a/core/java/android/service/dreams/IDreamOverlayClient.aidl
+++ b/core/java/android/service/dreams/IDreamOverlayClient.aidl
@@ -31,11 +31,12 @@
* @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
* dream.
* @param dreamComponent The component name of the dream service requesting overlay.
+ * @param isPreview Whether the dream is in preview mode.
* @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
* and weather.
*/
void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
- in String dreamComponent, in boolean shouldShowComplications);
+ in String dreamComponent, in boolean isPreview, in boolean shouldShowComplications);
/** Called when the dream is waking, to do any exit animations */
void wakeUp();
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index 83e0adf..72f2de8 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -57,3 +57,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "publish_preview_state_to_overlay"
+ namespace: "systemui"
+ description: "send preview information from dream to overlay"
+ bug: "333734282"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index b1df51f..7c8cd93 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -488,7 +488,7 @@
@Override
public Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme) {
if ((mRequestedTypes & ime()) != 0) {
- if (mHasAnimationCallbacks && hasZeroInsetsIme) {
+ if (mHasAnimationCallbacks && !hasZeroInsetsIme) {
return SYNC_IME_INTERPOLATOR;
} else if (mShow) {
return LINEAR_OUT_SLOW_IN_INTERPOLATOR;
@@ -536,7 +536,7 @@
@Override
public long getDurationMs(boolean hasZeroInsetsIme) {
if ((mRequestedTypes & ime()) != 0) {
- if (mHasAnimationCallbacks && hasZeroInsetsIme) {
+ if (mHasAnimationCallbacks && !hasZeroInsetsIme) {
return ANIMATION_DURATION_SYNC_IME_MS;
} else {
return ANIMATION_DURATION_UNSYNC_IME_MS;
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 1083f64..ec79f94 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -1141,6 +1141,7 @@
// Customize activity transition animation
private CustomActivityTransition mCustomActivityOpenTransition;
private CustomActivityTransition mCustomActivityCloseTransition;
+ private int mUserId;
private AnimationOptions(int type) {
mType = type;
@@ -1159,6 +1160,7 @@
mAnimations = in.readInt();
mCustomActivityOpenTransition = in.readTypedObject(CustomActivityTransition.CREATOR);
mCustomActivityCloseTransition = in.readTypedObject(CustomActivityTransition.CREATOR);
+ mUserId = in.readInt();
}
/** Make basic customized animation for a package */
@@ -1283,6 +1285,14 @@
return options;
}
+ public void setUserId(int userId) {
+ mUserId = userId;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
public int getType() {
return mType;
}
@@ -1349,6 +1359,7 @@
dest.writeInt(mAnimations);
dest.writeTypedObject(mCustomActivityOpenTransition, flags);
dest.writeTypedObject(mCustomActivityCloseTransition, flags);
+ dest.writeInt(mUserId);
}
@NonNull
@@ -1406,6 +1417,7 @@
if (mExitResId != DEFAULT_ANIMATION_RESOURCES_ID) {
sb.append(" exitResId=").append(mExitResId);
}
+ sb.append(" mUserId=").append(mUserId);
sb.append('}');
return sb.toString();
}
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index 8c6721a..efacc34 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -49,3 +49,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "avoid_rebinding_intentionally_disconnected_wallpaper"
+ namespace: "systemui"
+ description: "Prevents rebinding with intentionally disconnected wallpaper services."
+ bug: "332871851"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 67fc270..a786fc2 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -246,3 +246,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "always_capture_activity_snapshot"
+ namespace: "windowing_frontend"
+ description: "Always capture activity snapshot regardless predictive back status"
+ bug: "362183912"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING
index e4550c0..35f0553 100644
--- a/core/java/com/android/internal/infra/TEST_MAPPING
+++ b/core/java/com/android/internal/infra/TEST_MAPPING
@@ -9,15 +9,7 @@
]
},
{
- "name": "CtsPermissionTestCases",
- "options": [
- {
- "include-filter": "android.permission.cts.PermissionControllerTest"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- }
- ]
+ "name": "CtsPermissionTestCases_Platform"
},
{
"name": "FrameworksCoreTests_internal_infra"
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 238e6f5..201f267 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -49,7 +49,7 @@
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
-import android.os.SystemProperties;
+import android.os.UserHandle;
import android.util.Slog;
import android.view.InflateException;
import android.view.SurfaceControl;
@@ -187,23 +187,44 @@
return createHiddenByKeyguardExit(mContext, mInterpolator, onWallpaper, toShade, subtle);
}
+ /** Load keyguard unocclude animation for user. */
+ @Nullable
+ public Animation loadKeyguardUnoccludeAnimation(int userId) {
+ return loadDefaultAnimationRes(com.android.internal.R.anim.wallpaper_open_exit, userId);
+ }
+
+ /** Same as {@code loadKeyguardUnoccludeAnimation} for current user. */
@Nullable
public Animation loadKeyguardUnoccludeAnimation() {
- return loadDefaultAnimationRes(com.android.internal.R.anim.wallpaper_open_exit);
+ return loadKeyguardUnoccludeAnimation(UserHandle.USER_CURRENT);
}
+ /** Load voice activity open animation for user. */
@Nullable
- public Animation loadVoiceActivityOpenAnimation(boolean enter) {
+ public Animation loadVoiceActivityOpenAnimation(boolean enter, int userId) {
return loadDefaultAnimationRes(enter
? com.android.internal.R.anim.voice_activity_open_enter
- : com.android.internal.R.anim.voice_activity_open_exit);
+ : com.android.internal.R.anim.voice_activity_open_exit, userId);
}
+ /** Same as {@code loadVoiceActivityOpenAnimation} for current user. */
@Nullable
- public Animation loadVoiceActivityExitAnimation(boolean enter) {
+ public Animation loadVoiceActivityOpenAnimation(boolean enter) {
+ return loadVoiceActivityOpenAnimation(enter, UserHandle.USER_CURRENT);
+ }
+
+ /** Load voice activity exit animation for user. */
+ @Nullable
+ public Animation loadVoiceActivityExitAnimation(boolean enter, int userId) {
return loadDefaultAnimationRes(enter
? com.android.internal.R.anim.voice_activity_close_enter
- : com.android.internal.R.anim.voice_activity_close_exit);
+ : com.android.internal.R.anim.voice_activity_close_exit, userId);
+ }
+
+ /** Same as {@code loadVoiceActivityExitAnimation} for current user. */
+ @Nullable
+ public Animation loadVoiceActivityExitAnimation(boolean enter) {
+ return loadVoiceActivityExitAnimation(enter, UserHandle.USER_CURRENT);
}
@Nullable
@@ -211,10 +232,17 @@
return loadAnimationRes(packageName, resId);
}
+ /** Load cross profile app enter animation for user. */
+ @Nullable
+ public Animation loadCrossProfileAppEnterAnimation(int userId) {
+ return loadAnimationRes(DEFAULT_PACKAGE,
+ com.android.internal.R.anim.task_open_enter_cross_profile_apps, userId);
+ }
+
+ /** Same as {@code loadCrossProfileAppEnterAnimation} for current user. */
@Nullable
public Animation loadCrossProfileAppEnterAnimation() {
- return loadAnimationRes(DEFAULT_PACKAGE,
- com.android.internal.R.anim.task_open_enter_cross_profile_apps);
+ return loadCrossProfileAppEnterAnimation(UserHandle.USER_CURRENT);
}
@Nullable
@@ -230,11 +258,11 @@
appRect.height(), 0, null);
}
- /** Load animation by resource Id from specific package. */
+ /** Load animation by resource Id from specific package for user. */
@Nullable
- public Animation loadAnimationRes(String packageName, int resId) {
+ public Animation loadAnimationRes(String packageName, int resId, int userId) {
if (ResourceId.isValid(resId)) {
- AttributeCache.Entry ent = getCachedAnimations(packageName, resId);
+ AttributeCache.Entry ent = getCachedAnimations(packageName, resId, userId);
if (ent != null) {
return loadAnimationSafely(ent.context, resId, mTag);
}
@@ -242,10 +270,22 @@
return null;
}
- /** Load animation by resource Id from android package. */
+ /** Same as {@code loadAnimationRes} for current user. */
+ @Nullable
+ public Animation loadAnimationRes(String packageName, int resId) {
+ return loadAnimationRes(packageName, resId, UserHandle.USER_CURRENT);
+ }
+
+ /** Load animation by resource Id from android package for user. */
+ @Nullable
+ public Animation loadDefaultAnimationRes(int resId, int userId) {
+ return loadAnimationRes(DEFAULT_PACKAGE, resId, userId);
+ }
+
+ /** Same as {@code loadDefaultAnimationRes} for current user. */
@Nullable
public Animation loadDefaultAnimationRes(int resId) {
- return loadAnimationRes(DEFAULT_PACKAGE, resId);
+ return loadAnimationRes(DEFAULT_PACKAGE, resId, UserHandle.USER_CURRENT);
}
/** Load animation by attribute Id from specific LayoutParams */
@@ -378,10 +418,10 @@
}
@Nullable
- private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
+ private AttributeCache.Entry getCachedAnimations(String packageName, int resId, int userId) {
if (mDebug) {
- Slog.v(mTag, "Loading animations: package="
- + packageName + " resId=0x" + Integer.toHexString(resId));
+ Slog.v(mTag, "Loading animations: package=" + packageName + " resId=0x"
+ + Integer.toHexString(resId) + " for user=" + userId);
}
if (packageName != null) {
if ((resId & 0xFF000000) == 0x01000000) {
@@ -392,11 +432,16 @@
+ packageName);
}
return AttributeCache.instance().get(packageName, resId,
- com.android.internal.R.styleable.WindowAnimation);
+ com.android.internal.R.styleable.WindowAnimation, userId);
}
return null;
}
+ @Nullable
+ private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
+ return getCachedAnimations(packageName, resId, UserHandle.USER_CURRENT);
+ }
+
/** Returns window animation style ID from {@link LayoutParams} or from system in some cases */
public int getAnimationStyleResId(@NonNull LayoutParams lp) {
int resId = lp.windowAnimations;
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index 5c06b87..ef6bece 100644
--- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java
+++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
@@ -195,7 +195,7 @@
int defaultLogFromLevelInt = configStream.readInt(DEFAULT_LOG_FROM_LEVEL);
if (defaultLogFromLevelInt < defaultLogFromLevel.ordinal()) {
defaultLogFromLevel =
- logLevelFromInt(configStream.readInt(DEFAULT_LOG_FROM_LEVEL));
+ logLevelFromInt(defaultLogFromLevelInt);
}
break;
case (int) TRACING_MODE:
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 2068bd7..46b4695 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -466,10 +466,25 @@
public:
sp<JavaBBinder> get(JNIEnv* env, jobject obj)
{
- AutoMutex _l(mLock);
- sp<JavaBBinder> b = mBinder.promote();
- if (b == NULL) {
- b = new JavaBBinder(env, obj);
+ sp<JavaBBinder> b;
+ {
+ AutoMutex _l(mLock);
+ // must take lock to promote because we set the same wp<>
+ // on another thread.
+ b = mBinder.promote();
+ }
+
+ if (b) return b;
+
+ // b/360067751: constructor may trigger GC, so call outside lock
+ b = new JavaBBinder(env, obj);
+
+ {
+ AutoMutex _l(mLock);
+ // if it was constructed on another thread in the meantime,
+ // return that. 'b' will just get destructed.
+ if (sp<JavaBBinder> b2 = mBinder.promote(); b2) return b2;
+
if (mVintf) {
::android::internal::Stability::markVintf(b.get());
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 91c3370..17ff2eb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -843,6 +843,7 @@
<protected-broadcast android:name="android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED" />
<protected-broadcast android:name="android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED" />
<protected-broadcast android:name="com.android.uwb.uwbcountrycode.GEOCODE_RETRY" />
+ <protected-broadcast android:name="android.telephony.action.ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -3765,7 +3766,6 @@
privileged app such as the Assistant app.
<p>Protection level: internal|role
<p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
- @FlaggedApi("android.app.admin.flags.assist_content_user_restriction_enabled")
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT"
android:protectionLevel="internal|role" />
@@ -4026,7 +4026,6 @@
APIs protected by this permission on users different to the calling user.
<p>Protection level: internal|role
<p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
- @FlaggedApi("android.app.admin.flags.esim_management_enabled")
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS"
android:protectionLevel="internal|role" />
diff --git a/core/res/res/drawable/ic_zen_priority_modes.xml b/core/res/res/drawable/ic_zen_priority_modes.xml
new file mode 100644
index 0000000..98de27b
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_priority_modes.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:pathData="M160,480v-80h320v80L160,480ZM480,880q-80,0 -153.5,-29.5T196,764l56,-56q47,44 106,68t122,24q133,0 226.5,-93.5T800,480q0,-133 -93.5,-226.5T480,160v-80q83,0 155.5,31.5t127,86q54.5,54.5 86,127T880,480q0,82 -31.5,155t-86,127.5q-54.5,54.5 -127,86T480,880Z"
+ android:fillColor="@android:color/white"/>
+</vector>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index a7240ff..69437b4 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -440,10 +440,6 @@
<string name="config_satellite_carrier_roaming_esos_provisioned_class" translatable="false"></string>
<java-symbol type="string" name="config_satellite_carrier_roaming_esos_provisioned_class" />
- <!-- Telephony satellite gateway intent for handling carrier roaming to satellite is using ESOS messaging. -->
- <string name="config_satellite_carrier_roaming_esos_provisioned_intent_action" translatable="false"></string>
- <java-symbol type="string" name="config_satellite_carrier_roaming_esos_provisioned_intent_action" />
-
<!-- The time duration in minutes to wait before retry validating a possible change
in satellite allowed region. The default value is 10 minutes. -->
<integer name="config_satellite_delay_minutes_before_retry_validating_possible_change_in_allowed_region">10</integer>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index e1394bc..dc99634 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1504,26 +1504,26 @@
<!-- @hide -->
<style name="PointerIconVectorStyleFillGreen">
- <item name="pointerIconVectorFill">#6DD58C</item>
- <item name="pointerIconVectorFillInverse">#6DD58C</item>
+ <item name="pointerIconVectorFill">#1AA64A</item>
+ <item name="pointerIconVectorFillInverse">#1AA64A</item>
</style>
<!-- @hide -->
<style name="PointerIconVectorStyleFillYellow">
- <item name="pointerIconVectorFill">#FDD663</item>
- <item name="pointerIconVectorFillInverse">#FDD663</item>
+ <item name="pointerIconVectorFill">#F55E57</item>
+ <item name="pointerIconVectorFillInverse">#F55E57</item>
</style>
<!-- @hide -->
<style name="PointerIconVectorStyleFillPink">
- <item name="pointerIconVectorFill">#F2B8B5</item>
- <item name="pointerIconVectorFillInverse">#F2B8B5</item>
+ <item name="pointerIconVectorFill">#F94AAB</item>
+ <item name="pointerIconVectorFillInverse">#F94AAB</item>
</style>
<!-- @hide -->
<style name="PointerIconVectorStyleFillBlue">
- <item name="pointerIconVectorFill">#8AB4F8</item>
- <item name="pointerIconVectorFillInverse">#8AB4F8</item>
+ <item name="pointerIconVectorFill">#009DC9</item>
+ <item name="pointerIconVectorFillInverse">#009DC9</item>
</style>
<!-- @hide -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bbe661e..74922ac 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5566,6 +5566,7 @@
<java-symbol type="string" name="recs_notification_channel_label"/>
<!-- Priority Modes icons -->
+ <java-symbol type="drawable" name="ic_zen_priority_modes" />
<java-symbol type="drawable" name="ic_zen_mode_type_bedtime" />
<java-symbol type="drawable" name="ic_zen_mode_type_driving" />
<java-symbol type="drawable" name="ic_zen_mode_type_immersive" />
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
index 69a68c8..0726624 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -24,6 +24,7 @@
import androidx.annotation.NonNull;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -80,9 +81,14 @@
}
if (DEBUG) Log.d(TAG, "Start to back up " + taskContainers);
+ final List<ParcelableTaskContainerData> parcelableTaskContainerDataList = new ArrayList<>(
+ taskContainers.size());
+ for (TaskContainer taskContainer : taskContainers) {
+ parcelableTaskContainerDataList.add(taskContainer.getParcelableData());
+ }
final Bundle state = new Bundle();
- state.setClassLoader(TaskContainer.class.getClassLoader());
- state.putParcelableList(KEY_TASK_CONTAINERS, taskContainers);
+ state.setClassLoader(ParcelableTaskContainerData.class.getClassLoader());
+ state.putParcelableList(KEY_TASK_CONTAINERS, parcelableTaskContainerDataList);
mController.setSavedState(state);
}
@@ -91,10 +97,12 @@
return;
}
- final List<TaskContainer> taskContainers = savedState.getParcelableArrayList(
- KEY_TASK_CONTAINERS, TaskContainer.class);
- for (TaskContainer taskContainer : taskContainers) {
- if (DEBUG) Log.d(TAG, "restore task " + taskContainer.getTaskId());
+ final List<ParcelableTaskContainerData> parcelableTaskContainerDataList =
+ savedState.getParcelableArrayList(KEY_TASK_CONTAINERS,
+ ParcelableTaskContainerData.class);
+ for (ParcelableTaskContainerData data : parcelableTaskContainerDataList) {
+ final TaskContainer taskContainer = new TaskContainer(data, mController);
+ if (DEBUG) Log.d(TAG, "Restoring task " + taskContainer.getTaskId());
// TODO(b/289875940): implement the TaskContainer restoration.
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
new file mode 100644
index 0000000..817cfce
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
@@ -0,0 +1,116 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * This class holds the Parcelable data of a {@link SplitContainer}.
+ */
+class ParcelableSplitContainerData implements Parcelable {
+
+ /**
+ * A reference to the target {@link SplitContainer} that owns the data. This will not be
+ * parcelled and will be {@code null} when the data is created from a parcel.
+ */
+ @Nullable
+ final SplitContainer mSplitContainer;
+
+ @NonNull
+ final IBinder mToken;
+
+ @NonNull
+ private final IBinder mPrimaryContainerToken;
+
+ @NonNull
+ private final IBinder mSecondaryContainerToken;
+
+ // TODO(b/289875940): making this as non-null once the tag can be auto-generated from the rule.
+ @Nullable
+ final String mSplitRuleTag;
+
+ /**
+ * Whether the selection of which container is primary can be changed at runtime. Runtime
+ * updates is currently possible only for {@link SplitPinContainer}
+ *
+ * @see SplitPinContainer
+ */
+ final boolean mIsPrimaryContainerMutable;
+
+ ParcelableSplitContainerData(@NonNull SplitContainer splitContainer, @NonNull IBinder token,
+ @NonNull IBinder primaryContainerToken, @NonNull IBinder secondaryContainerToken,
+ @Nullable String splitRuleTag, boolean isPrimaryContainerMutable) {
+ mSplitContainer = splitContainer;
+ mToken = token;
+ mPrimaryContainerToken = primaryContainerToken;
+ mSecondaryContainerToken = secondaryContainerToken;
+ mSplitRuleTag = splitRuleTag;
+ mIsPrimaryContainerMutable = isPrimaryContainerMutable;
+ }
+
+ private ParcelableSplitContainerData(Parcel in) {
+ mSplitContainer = null;
+ mToken = in.readStrongBinder();
+ mPrimaryContainerToken = in.readStrongBinder();
+ mSecondaryContainerToken = in.readStrongBinder();
+ mSplitRuleTag = in.readString();
+ mIsPrimaryContainerMutable = in.readBoolean();
+ }
+
+ public static final Creator<ParcelableSplitContainerData> CREATOR = new Creator<>() {
+ @Override
+ public ParcelableSplitContainerData createFromParcel(Parcel in) {
+ return new ParcelableSplitContainerData(in);
+ }
+
+ @Override
+ public ParcelableSplitContainerData[] newArray(int size) {
+ return new ParcelableSplitContainerData[size];
+ }
+ };
+
+ @NonNull
+ private IBinder getPrimaryContainerToken() {
+ return mSplitContainer != null ? mSplitContainer.getPrimaryContainer().getToken()
+ : mPrimaryContainerToken;
+ }
+
+ @NonNull
+ private IBinder getSecondaryContainerToken() {
+ return mSplitContainer != null ? mSplitContainer.getSecondaryContainer().getToken()
+ : mSecondaryContainerToken;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mToken);
+ dest.writeStrongBinder(getPrimaryContainerToken());
+ dest.writeStrongBinder(getSecondaryContainerToken());
+ dest.writeString(mSplitRuleTag);
+ dest.writeBoolean(mIsPrimaryContainerMutable);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskContainerData.java
new file mode 100644
index 0000000..7377d00
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskContainerData.java
@@ -0,0 +1,127 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class holds the Parcelable data of a {@link TaskContainer}.
+ */
+class ParcelableTaskContainerData implements Parcelable {
+
+ /**
+ * A reference to the target {@link TaskContainer} that owns the data. This will not be
+ * parcelled and will be {@code null} when the data is created from a parcel.
+ */
+ @Nullable
+ final TaskContainer mTaskContainer;
+
+ /**
+ * The unique task id.
+ */
+ final int mTaskId;
+
+ /**
+ * The parcelable data of the active TaskFragmentContainers in this Task.
+ * Note that this will only be populated before parcelling, and will not be copied when
+ * making a new instance copy.
+ */
+ @NonNull
+ private final List<ParcelableTaskFragmentContainerData>
+ mParcelableTaskFragmentContainerDataList = new ArrayList<>();
+
+ /**
+ * The parcelable data of the SplitContainers in this Task.
+ * Note that this will only be populated before parcelling, and will not be copied when
+ * making a new instance copy.
+ */
+ @NonNull
+ private final List<ParcelableSplitContainerData> mParcelableSplitContainerDataList =
+ new ArrayList<>();
+
+ ParcelableTaskContainerData(int taskId, @NonNull TaskContainer taskContainer) {
+ if (taskId == INVALID_TASK_ID) {
+ throw new IllegalArgumentException("Invalid Task id");
+ }
+
+ mTaskId = taskId;
+ mTaskContainer = taskContainer;
+ }
+
+ ParcelableTaskContainerData(@NonNull ParcelableTaskContainerData data,
+ @NonNull TaskContainer taskContainer) {
+ mTaskId = data.mTaskId;
+ mTaskContainer = taskContainer;
+ }
+
+ private ParcelableTaskContainerData(Parcel in) {
+ mTaskId = in.readInt();
+ mTaskContainer = null;
+ in.readParcelableList(mParcelableTaskFragmentContainerDataList,
+ ParcelableTaskFragmentContainerData.class.getClassLoader(),
+ ParcelableTaskFragmentContainerData.class);
+ in.readParcelableList(mParcelableSplitContainerDataList,
+ ParcelableSplitContainerData.class.getClassLoader(),
+ ParcelableSplitContainerData.class);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTaskId);
+ dest.writeParcelableList(getParcelableTaskFragmentContainerDataList(), flags);
+ dest.writeParcelableList(getParcelableSplitContainerDataList(), flags);
+ }
+
+ @NonNull
+ List<? extends ParcelableTaskFragmentContainerData>
+ getParcelableTaskFragmentContainerDataList() {
+ return mTaskContainer != null ? mTaskContainer.getParcelableTaskFragmentContainerDataList()
+ : mParcelableTaskFragmentContainerDataList;
+ }
+
+ @NonNull
+ List<? extends ParcelableSplitContainerData> getParcelableSplitContainerDataList() {
+ return mTaskContainer != null ? mTaskContainer.getParcelableSplitContainerDataList()
+ : mParcelableSplitContainerDataList;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<ParcelableTaskContainerData> CREATOR = new Creator<>() {
+ @Override
+ public ParcelableTaskContainerData createFromParcel(Parcel in) {
+ return new ParcelableTaskContainerData(in);
+ }
+
+ @Override
+ public ParcelableTaskContainerData[] newArray(int size) {
+ return new ParcelableTaskContainerData[size];
+ }
+ };
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
new file mode 100644
index 0000000..a79a89a
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
@@ -0,0 +1,105 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * This class holds the Parcelable data of a {@link TaskFragmentContainer}.
+ */
+class ParcelableTaskFragmentContainerData implements Parcelable {
+
+ /**
+ * Client-created token that uniquely identifies the task fragment container instance.
+ */
+ @NonNull
+ final IBinder mToken;
+
+ /**
+ * The tag specified in launch options. {@code null} if this taskFragment container is not an
+ * overlay container.
+ */
+ @Nullable
+ final String mOverlayTag;
+
+ /**
+ * The associated {@link Activity#getActivityToken()} of the overlay container.
+ * Must be {@code null} for non-overlay container.
+ * <p>
+ * If an overlay container is associated with an activity, this overlay container will be
+ * dismissed when the associated activity is destroyed. If the overlay container is visible,
+ * activity will be launched on top of the overlay container and expanded to fill the parent
+ * container.
+ */
+ @Nullable
+ final IBinder mAssociatedActivityToken;
+
+ /**
+ * Bounds that were requested last via {@link android.window.WindowContainerTransaction}.
+ */
+ @NonNull
+ final Rect mLastRequestedBounds;
+
+ ParcelableTaskFragmentContainerData(@NonNull IBinder token, @Nullable String overlayTag,
+ @Nullable IBinder associatedActivityToken) {
+ mToken = token;
+ mOverlayTag = overlayTag;
+ mAssociatedActivityToken = associatedActivityToken;
+ mLastRequestedBounds = new Rect();
+ }
+
+ private ParcelableTaskFragmentContainerData(Parcel in) {
+ mToken = in.readStrongBinder();
+ mOverlayTag = in.readString();
+ mAssociatedActivityToken = in.readStrongBinder();
+ mLastRequestedBounds = in.readTypedObject(Rect.CREATOR);
+ }
+
+ public static final Creator<ParcelableTaskFragmentContainerData> CREATOR = new Creator<>() {
+ @Override
+ public ParcelableTaskFragmentContainerData createFromParcel(Parcel in) {
+ return new ParcelableTaskFragmentContainerData(in);
+ }
+
+ @Override
+ public ParcelableTaskFragmentContainerData[] newArray(int size) {
+ return new ParcelableTaskFragmentContainerData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mToken);
+ dest.writeString(mOverlayTag);
+ dest.writeStrongBinder(mAssociatedActivityToken);
+ dest.writeTypedObject(mLastRequestedBounds, flags);
+ }
+
+}
+
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 39cface..6d436ec 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -33,6 +33,8 @@
*/
class SplitContainer {
@NonNull
+ private final ParcelableSplitContainerData mParcelableData;
+ @NonNull
private TaskFragmentContainer mPrimaryContainer;
@NonNull
private final TaskFragmentContainer mSecondaryContainer;
@@ -44,16 +46,6 @@
/** @see SplitContainer#getDefaultSplitAttributes() */
@NonNull
private SplitAttributes mDefaultSplitAttributes;
- @NonNull
- private final IBinder mToken;
-
- /**
- * Whether the selection of which container is primary can be changed at runtime. Runtime
- * updates is currently possible only for {@link SplitPinContainer}
- *
- * @see SplitPinContainer
- */
- private final boolean mIsPrimaryContainerMutable;
SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
@NonNull Activity primaryActivity,
@@ -69,13 +61,14 @@
@NonNull TaskFragmentContainer secondaryContainer,
@NonNull SplitRule splitRule,
@NonNull SplitAttributes splitAttributes, boolean isPrimaryContainerMutable) {
+ mParcelableData = new ParcelableSplitContainerData(this, new Binder("SplitContainer"),
+ primaryContainer.getToken(), secondaryContainer.getToken(), splitRule.getTag(),
+ isPrimaryContainerMutable);
mPrimaryContainer = primaryContainer;
mSecondaryContainer = secondaryContainer;
mSplitRule = splitRule;
mDefaultSplitAttributes = splitRule.getDefaultSplitAttributes();
mCurrentSplitAttributes = splitAttributes;
- mToken = new Binder("SplitContainer");
- mIsPrimaryContainerMutable = isPrimaryContainerMutable;
if (shouldFinishPrimaryWithSecondary(splitRule)) {
if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -94,7 +87,7 @@
}
void setPrimaryContainer(@NonNull TaskFragmentContainer primaryContainer) {
- if (!mIsPrimaryContainerMutable) {
+ if (!mParcelableData.mIsPrimaryContainerMutable) {
throw new IllegalStateException("Cannot update primary TaskFragmentContainer");
}
mPrimaryContainer = primaryContainer;
@@ -150,7 +143,12 @@
@NonNull
IBinder getToken() {
- return mToken;
+ return mParcelableData.mToken;
+ }
+
+ @NonNull
+ ParcelableSplitContainerData getParcelableData() {
+ return mParcelableData;
}
/**
@@ -201,7 +199,7 @@
return null;
}
return new SplitInfo(primaryActivityStack, secondaryActivityStack,
- mCurrentSplitAttributes, SplitInfo.Token.createFromBinder(mToken));
+ mCurrentSplitAttributes, SplitInfo.Token.createFromBinder(mParcelableData.mToken));
}
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index fb8efc4..5637320 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -169,7 +169,7 @@
mController = controller;
final Bundle outSavedState = new Bundle();
if (Flags.aeBackStackRestore()) {
- outSavedState.setClassLoader(TaskContainer.class.getClassLoader());
+ outSavedState.setClassLoader(ParcelableTaskContainerData.class.getClassLoader());
registerOrganizer(false /* isSystemOrganizer */, outSavedState);
} else {
registerOrganizer();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 5795e8d..82dfda5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -16,7 +16,6 @@
package androidx.window.extensions.embedding;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -32,8 +31,6 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -57,11 +54,12 @@
import java.util.Set;
/** Represents TaskFragments and split pairs below a Task. */
-class TaskContainer implements Parcelable {
+class TaskContainer {
private static final String TAG = TaskContainer.class.getSimpleName();
- /** The unique task id. */
- private final int mTaskId;
+ /** Parcelable data of this TaskContainer. */
+ @NonNull
+ private final ParcelableTaskContainerData mParcelableTaskContainerData;
/** Active TaskFragments in this Task. */
@NonNull
@@ -130,11 +128,9 @@
* @param splitController The {@link SplitController}.
*/
TaskContainer(int taskId, @NonNull Activity activityInTask,
- @Nullable SplitController splitController) {
- if (taskId == INVALID_TASK_ID) {
- throw new IllegalArgumentException("Invalid Task id");
- }
- mTaskId = taskId;
+ @NonNull SplitController splitController) {
+ mParcelableTaskContainerData = new ParcelableTaskContainerData(taskId, this);
+
final TaskProperties taskProperties = TaskProperties
.getTaskPropertiesFromActivity(activityInTask);
mInfo = new TaskFragmentParentInfo(
@@ -148,8 +144,44 @@
mSplitController = splitController;
}
+ /** This is only used when restoring it from a {@link ParcelableTaskContainerData}. */
+ TaskContainer(@NonNull ParcelableTaskContainerData data,
+ @NonNull SplitController splitController) {
+ mParcelableTaskContainerData = new ParcelableTaskContainerData(data, this);
+ mSplitController = splitController;
+ for (ParcelableTaskFragmentContainerData tfData :
+ data.getParcelableTaskFragmentContainerDataList()) {
+ final TaskFragmentContainer container =
+ new TaskFragmentContainer(tfData, splitController, this);
+ mContainers.add(container);
+ }
+ }
+
+ @NonNull
+ ParcelableTaskContainerData getParcelableData() {
+ return mParcelableTaskContainerData;
+ }
+
+ @NonNull
+ List<ParcelableTaskFragmentContainerData> getParcelableTaskFragmentContainerDataList() {
+ final List<ParcelableTaskFragmentContainerData> data = new ArrayList<>(mContainers.size());
+ for (TaskFragmentContainer container : mContainers) {
+ data.add(container.getParcelableData());
+ }
+ return data;
+ }
+
+ @NonNull
+ List<ParcelableSplitContainerData> getParcelableSplitContainerDataList() {
+ final List<ParcelableSplitContainerData> data = new ArrayList<>(mSplitContainers.size());
+ for (SplitContainer splitContainer : mSplitContainers) {
+ data.add(splitContainer.getParcelableData());
+ }
+ return data;
+ }
+
int getTaskId() {
- return mTaskId;
+ return mParcelableTaskContainerData.mTaskId;
}
int getDisplayId() {
@@ -680,34 +712,6 @@
return activityStacks;
}
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mTaskId);
- // TODO(b/289875940)
- }
-
- protected TaskContainer(Parcel in) {
- mTaskId = in.readInt();
- // TODO(b/289875940)
- }
-
- public static final Creator<TaskContainer> CREATOR = new Creator<>() {
- @Override
- public TaskContainer createFromParcel(Parcel in) {
- return new TaskContainer(in);
- }
-
- @Override
- public TaskContainer[] newArray(int size) {
- return new TaskContainer[size];
- }
- };
-
/** A wrapper class which contains the information of {@link TaskContainer} */
static final class TaskProperties {
private final int mDisplayId;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index dc6506b..dc1d983 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -53,15 +53,13 @@
class TaskFragmentContainer {
private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000;
+ /** Parcelable data of this TaskFragmentContainer. */
+ @NonNull
+ private final ParcelableTaskFragmentContainerData mParcelableData;
+
@NonNull
private final SplitController mController;
- /**
- * Client-created token that uniquely identifies the task fragment container instance.
- */
- @NonNull
- private final IBinder mToken;
-
/** Parent leaf Task. */
@NonNull
private final TaskContainer mTaskContainer;
@@ -103,9 +101,6 @@
*/
private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
- @Nullable
- private final String mOverlayTag;
-
/**
* The launch options that was used to create this container. Must not {@link Bundle#isEmpty()}
* for {@link #isOverlay()} container.
@@ -113,29 +108,13 @@
@NonNull
private final Bundle mLaunchOptions = new Bundle();
- /**
- * The associated {@link Activity#getActivityToken()} of the overlay container.
- * Must be {@code null} for non-overlay container.
- * <p>
- * If an overlay container is associated with an activity, this overlay container will be
- * dismissed when the associated activity is destroyed. If the overlay container is visible,
- * activity will be launched on top of the overlay container and expanded to fill the parent
- * container.
- */
- @Nullable
- private final IBinder mAssociatedActivityToken;
-
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
/**
- * Bounds that were requested last via {@link android.window.WindowContainerTransaction}.
- */
- private final Rect mLastRequestedBounds = new Rect();
-
- /**
* Windowing mode that was requested last via {@link android.window.WindowContainerTransaction}.
*/
+ // TODO(b/289875940): review this and other field that might need to be moved in the base class.
@WindowingMode
private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;
@@ -208,17 +187,17 @@
@NonNull SplitController controller,
@Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
@Nullable Bundle launchOptions, @Nullable Activity associatedActivity) {
+ mParcelableData = new ParcelableTaskFragmentContainerData(
+ new Binder("TaskFragmentContainer"), overlayTag,
+ associatedActivity != null ? associatedActivity.getActivityToken() : null);
+
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
"One and only one of pending activity and intent must be non-null");
}
mController = controller;
- mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
- mOverlayTag = overlayTag;
- mAssociatedActivityToken = associatedActivity != null
- ? associatedActivity.getActivityToken() : null;
if (launchOptions != null) {
mLaunchOptions.putAll(launchOptions);
@@ -259,18 +238,26 @@
if (overlayTag != null && pendingAppearedIntent != null
&& associatedActivity != null && !associatedActivity.isFinishing()) {
final IBinder associatedActivityToken = associatedActivity.getActivityToken();
- final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(mToken,
- launchOptions, pendingAppearedIntent);
+ final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(
+ mParcelableData.mToken, launchOptions, pendingAppearedIntent);
mController.mOverlayRestoreParams.put(associatedActivityToken, params);
}
}
+ /** This is only used when restoring it from a {@link ParcelableTaskFragmentContainerData}. */
+ TaskFragmentContainer(@NonNull ParcelableTaskFragmentContainerData data,
+ @NonNull SplitController splitController, @NonNull TaskContainer taskContainer) {
+ mParcelableData = data;
+ mController = splitController;
+ mTaskContainer = taskContainer;
+ }
+
/**
* Returns the client-created token that uniquely identifies this container.
*/
@NonNull
IBinder getTaskFragmentToken() {
- return mToken;
+ return mParcelableData.mToken;
}
/** List of non-finishing activities that belong to this container and live in this process. */
@@ -389,7 +376,8 @@
return null;
}
return new ActivityStack(activities, isEmpty(),
- ActivityStack.Token.createFromBinder(mToken), mOverlayTag);
+ ActivityStack.Token.createFromBinder(mParcelableData.mToken),
+ mParcelableData.mOverlayTag);
}
/** Adds the activity that will be reparented to this container. */
@@ -413,7 +401,7 @@
final ActivityThread.ActivityClientRecord record = ActivityThread
.currentActivityThread().getActivityClient(activityToken);
if (record != null) {
- record.mTaskFragmentToken = mToken;
+ record.mTaskFragmentToken = mParcelableData.mToken;
}
}
@@ -469,7 +457,7 @@
if (!isOverlayWithActivityAssociation()) {
return;
}
- if (mAssociatedActivityToken == activityToken) {
+ if (mParcelableData.mAssociatedActivityToken == activityToken) {
// If the associated activity is destroyed, also finish this overlay container.
mController.mPresenter.cleanupContainer(wct, this, false /* shouldFinishDependent */);
}
@@ -776,8 +764,8 @@
* @see WindowContainerTransaction#setRelativeBounds
*/
boolean areLastRequestedBoundsEqual(@Nullable Rect relBounds) {
- return (relBounds == null && mLastRequestedBounds.isEmpty())
- || mLastRequestedBounds.equals(relBounds);
+ return (relBounds == null && mParcelableData.mLastRequestedBounds.isEmpty())
+ || mParcelableData.mLastRequestedBounds.equals(relBounds);
}
/**
@@ -787,14 +775,14 @@
*/
void setLastRequestedBounds(@Nullable Rect relBounds) {
if (relBounds == null) {
- mLastRequestedBounds.setEmpty();
+ mParcelableData.mLastRequestedBounds.setEmpty();
} else {
- mLastRequestedBounds.set(relBounds);
+ mParcelableData.mLastRequestedBounds.set(relBounds);
}
}
@NonNull Rect getLastRequestedBounds() {
- return mLastRequestedBounds;
+ return mParcelableData.mLastRequestedBounds;
}
/**
@@ -965,6 +953,16 @@
return mTaskContainer.getTaskId();
}
+ @NonNull
+ IBinder getToken() {
+ return mParcelableData.mToken;
+ }
+
+ @NonNull
+ ParcelableTaskFragmentContainerData getParcelableData() {
+ return mParcelableData;
+ }
+
/** Gets the parent Task. */
@NonNull
TaskContainer getTaskContainer() {
@@ -1011,7 +1009,7 @@
/** Returns whether this taskFragment container is an overlay container. */
boolean isOverlay() {
- return mOverlayTag != null;
+ return mParcelableData.mOverlayTag != null;
}
/**
@@ -1020,7 +1018,7 @@
*/
@Nullable
String getOverlayTag() {
- return mOverlayTag;
+ return mParcelableData.mOverlayTag;
}
/**
@@ -1045,7 +1043,7 @@
*/
@Nullable
IBinder getAssociatedActivityToken() {
- return mAssociatedActivityToken;
+ return mParcelableData.mAssociatedActivityToken;
}
/**
@@ -1053,11 +1051,11 @@
* a non-fill-parent overlay without activity association.
*/
boolean isAlwaysOnTopOverlay() {
- return isOverlay() && mAssociatedActivityToken == null;
+ return isOverlay() && mParcelableData.mAssociatedActivityToken == null;
}
boolean isOverlayWithActivityAssociation() {
- return isOverlay() && mAssociatedActivityToken != null;
+ return isOverlay() && mParcelableData.mAssociatedActivityToken != null;
}
@Override
@@ -1074,13 +1072,13 @@
private String toString(boolean includeContainersToFinishOnExit) {
return "TaskFragmentContainer{"
+ " parentTaskId=" + getTaskId()
- + " token=" + mToken
+ + " token=" + mParcelableData.mToken
+ " topNonFinishingActivity=" + getTopNonFinishingActivity()
+ " runningActivityCount=" + getRunningActivityCount()
+ " isFinished=" + mIsFinished
- + " overlayTag=" + mOverlayTag
- + " associatedActivityToken=" + mAssociatedActivityToken
- + " lastRequestedBounds=" + mLastRequestedBounds
+ + " overlayTag=" + mParcelableData.mOverlayTag
+ + " associatedActivityToken=" + mParcelableData.mAssociatedActivityToken
+ + " lastRequestedBounds=" + mParcelableData.mLastRequestedBounds
+ " pendingAppearedActivities=" + mPendingAppearedActivities
+ (includeContainersToFinishOnExit ? " containersToFinishOnExit="
+ containersToFinishOnExitToString() : "")
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
index 423fe57..cc47dbb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
@@ -61,20 +61,20 @@
@WMSingleton
@ShellMainThread
fun provideApplicationScope(
- @ShellMainThread applicationDispatcher: CoroutineDispatcher,
+ @ShellMainThread applicationDispatcher: MainCoroutineDispatcher,
): CoroutineScope = CoroutineScope(applicationDispatcher)
@Provides
@WMSingleton
@ShellBackgroundThread
fun provideBackgroundCoroutineScope(
- @ShellBackgroundThread backgroundDispatcher: CoroutineDispatcher,
+ @ShellBackgroundThread backgroundDispatcher: MainCoroutineDispatcher,
): CoroutineScope = CoroutineScope(backgroundDispatcher)
@Provides
@WMSingleton
@ShellBackgroundThread
fun provideBackgroundCoroutineContext(
- @ShellBackgroundThread backgroundDispatcher: CoroutineDispatcher
+ @ShellBackgroundThread backgroundDispatcher: MainCoroutineDispatcher
): CoroutineContext = backgroundDispatcher + SupervisorJob()
}
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 b68b436..c8ffe28 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
@@ -171,6 +171,18 @@
minOf(appBounds.height(), appBounds.width()).toFloat()
}
+/** 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() ||
+ taskBounds.height() == stableBounds.height()
+}
+
+/** Returns true if task bound is equal to stable bounds else returns false. */
+fun isTaskBoundsEqual(taskBounds: Rect, stableBounds: Rect): Boolean {
+ return taskBounds.width() == stableBounds.width() &&
+ taskBounds.height() == stableBounds.height()
+}
+
/**
* Calculates the desired initial bounds for applications in desktop windowing. This is done as a
* scale of the screen bounds.
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 2852631..90f8276 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
@@ -169,6 +169,9 @@
}
}
+ @VisibleForTesting
+ var taskbarDesktopTaskListener: TaskbarDesktopTaskListener? = null
+
/** Task id of the task currently being dragged from fullscreen/split. */
val draggingTaskId
get() = dragToDesktopTransitionHandler.draggingTaskId
@@ -610,13 +613,10 @@
val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
val destinationBounds = Rect()
- val isMaximized = if (taskInfo.isResizeable) {
- currentTaskBounds == stableBounds
- } else {
- currentTaskBounds.width() == stableBounds.width()
- || currentTaskBounds.height() == stableBounds.height()
- }
-
+ val isMaximized = isTaskMaximized(taskInfo, stableBounds)
+ // 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
if (isMaximized) {
// 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.
@@ -651,6 +651,18 @@
}
}
+
+
+ val shouldRestoreToSnap =
+ isMaximized && isTaskSnappedToHalfScreen(taskInfo, destinationBounds)
+
+ logD("willMaximize = %s", willMaximize)
+ logD("shouldRestoreToSnap = %s", shouldRestoreToSnap)
+
+ val doesAnyTaskRequireTaskbarRounding = willMaximize || shouldRestoreToSnap ||
+ doesAnyTaskRequireTaskbarRounding(taskInfo.displayId, taskInfo.taskId)
+
+ taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding)
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
@@ -659,6 +671,65 @@
}
}
+ 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
+ ): Boolean {
+ val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
+ return isTaskBoundsEqual(currentTaskBounds, stableBounds)
+ }
+
+ /** Returns if current task bound is snapped to half screen */
+ private fun isTaskSnappedToHalfScreen(
+ taskInfo: RunningTaskInfo,
+ taskBounds: Rect = taskInfo.configuration.windowConfiguration.bounds
+ ): Boolean =
+ getSnapBounds(taskInfo, SnapPosition.LEFT) == taskBounds ||
+ getSnapBounds(taskInfo, SnapPosition.RIGHT) == taskBounds
+
+ @VisibleForTesting
+ fun doesAnyTaskRequireTaskbarRounding(
+ displayId: Int,
+ excludeTaskId: Int? = null,
+ ): Boolean {
+ val doesAnyTaskRequireTaskbarRounding =
+ taskRepository.getActiveNonMinimizedOrderedTasks(displayId)
+ // exclude current task since maximize/restore transition has not taken place yet.
+ .filterNot { taskId -> taskId == excludeTaskId }
+ .any { taskId ->
+ val taskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId)!!
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
+ val stableBounds = Rect().apply { displayLayout?.getStableBounds(this) }
+ logD("taskInfo = %s", taskInfo)
+ logD(
+ "isTaskSnappedToHalfScreen(taskInfo) = %s",
+ isTaskSnappedToHalfScreen(taskInfo)
+ )
+ logD(
+ "isMaximizedToStableBoundsEdges(taskInfo, stableBounds) = %s",
+ isMaximizedToStableBoundsEdges(taskInfo, stableBounds)
+ )
+ isTaskSnappedToHalfScreen(taskInfo)
+ || isMaximizedToStableBoundsEdges(taskInfo, stableBounds)
+ }
+
+ logD("doesAnyTaskRequireTaskbarRounding = %s", doesAnyTaskRequireTaskbarRounding)
+ return doesAnyTaskRequireTaskbarRounding
+ }
+
/**
* Quick-resize to the right or left half of the stable bounds.
*
@@ -673,9 +744,9 @@
position: SnapPosition
) {
val destinationBounds = getSnapBounds(taskInfo, position)
-
if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
+ taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(true)
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentDragBounds)
@@ -806,6 +877,10 @@
.mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
.reversed() // Start from the back so the front task is brought forward last
.forEach { task -> wct.reorder(task.token, /* onTop= */ true) }
+
+ taskbarDesktopTaskListener?.
+ onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(displayId))
+
return taskToMinimize
}
@@ -1156,6 +1231,12 @@
) {
wct.removeTask(task.token)
}
+ taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
+ doesAnyTaskRequireTaskbarRounding(
+ task.displayId,
+ task.id
+ )
+ )
return if (wct.isEmpty) null else wct
}
@@ -1450,6 +1531,8 @@
}
// A freeform drag-move ended, remove the indicator immediately.
releaseVisualIndicator()
+ taskbarDesktopTaskListener
+ ?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(taskInfo.displayId))
}
/**
@@ -1686,17 +1769,39 @@
}
}
+ private val mTaskbarDesktopTaskListener: TaskbarDesktopTaskListener =
+ object : TaskbarDesktopTaskListener {
+ override fun onTaskbarCornerRoundingUpdate(
+ hasTasksRequiringTaskbarRounding: Boolean) {
+ ProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "IDesktopModeImpl: onTaskbarCornerRoundingUpdate " +
+ "doesAnyTaskRequireTaskbarRounding=%s",
+ hasTasksRequiringTaskbarRounding
+ )
+
+ remoteListener.call { l ->
+ l.onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding)
+ }
+ }
+ }
+
init {
remoteListener =
SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>(
controller,
{ c ->
- c.taskRepository.addVisibleTasksListener(
- listener,
- c.mainExecutor
- )
+ run {
+ c.taskRepository.addVisibleTasksListener(listener, c.mainExecutor)
+ c.taskbarDesktopTaskListener = mTaskbarDesktopTaskListener
+ }
},
- { c -> c.taskRepository.removeVisibleTasksListener(listener) }
+ { c ->
+ run {
+ c.taskRepository.removeVisibleTasksListener(listener)
+ c.taskbarDesktopTaskListener = null
+ }
+ }
)
}
@@ -1779,6 +1884,16 @@
private const val TAG = "DesktopTasksController"
}
+ /** Defines interface for classes that can listen to changes for task resize. */
+ // TODO(b/343931111): Migrate to using TransitionObservers when ready
+ interface TaskbarDesktopTaskListener {
+ /**
+ * [hasTasksRequiringTaskbarRounding] is true when a task is either maximized or snapped
+ * left/right and rounded corners are enabled.
+ */
+ fun onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding: Boolean)
+ }
+
/** The positions on a screen that a task can snap to. */
enum class SnapPosition {
RIGHT,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
index 8ebdfdc..c2acb87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -27,4 +27,10 @@
/** @deprecated this is no longer supported. */
oneway void onStashedChanged(int displayId, boolean stashed);
+
+ /**
+ * Shows taskbar corner radius when running desktop tasks are updated if
+ * [hasTasksRequiringTaskbarRounding] is true.
+ */
+ oneway void onTaskbarCornerRoundingUpdate(boolean hasTasksRequiringTaskbarRounding);
}
\ No newline at end of file
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 4fc6c44..9b0fb20 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
@@ -18,8 +18,8 @@
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
-import static android.app.ActivityOptions.ANIM_NONE;
import static android.app.ActivityOptions.ANIM_FROM_STYLE;
+import static android.app.ActivityOptions.ANIM_NONE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
@@ -473,7 +473,7 @@
change.getLeash(),
startTransaction);
} else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType())
- && TransitionUtil.isClosingType(mode)) {
+ && TransitionUtil.isClosingType(mode)) {
// If there is a closing translucent task in an OPENING transition, we will
// actually select a CLOSING animation, so move the closing task into
// the animating part of the z-order.
@@ -767,12 +767,12 @@
a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
(changeFlags & FLAG_SHOW_WALLPAPER) != 0);
} else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) {
- a = mTransitionAnimation.loadKeyguardUnoccludeAnimation();
+ a = mTransitionAnimation.loadKeyguardUnoccludeAnimation(options.getUserId());
} else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
if (isOpeningType) {
- a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
+ a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter, options.getUserId());
} else {
- a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
+ a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter, options.getUserId());
}
} else if (changeMode == TRANSIT_CHANGE) {
// In the absence of a specific adapter, we just want to keep everything stationary.
@@ -783,9 +783,9 @@
} else if (overrideType == ANIM_CUSTOM
&& (!isTask || options.getOverrideTaskTransition())) {
a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
- ? options.getEnterResId() : options.getExitResId());
+ ? options.getEnterResId() : options.getExitResId(), options.getUserId());
} else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) {
- a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
+ a = mTransitionAnimation.loadCrossProfileAppEnterAnimation(options.getUserId());
} else if (overrideType == ANIM_CLIP_REVEAL) {
a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter,
endBounds, endBounds, options.getTransitionBounds());
@@ -905,9 +905,9 @@
final Rect bounds = change.getEndAbsBounds();
// Show the right drawable depending on the user we're transitioning to.
final Drawable thumbnailDrawable = change.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL)
- ? mContext.getDrawable(R.drawable.ic_account_circle)
- : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL)
- ? mEnterpriseThumbnailDrawable : null;
+ ? mContext.getDrawable(R.drawable.ic_account_circle)
+ : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL)
+ ? mEnterpriseThumbnailDrawable : null;
if (thumbnailDrawable == null) {
return;
}
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 61fc09b..ac354593 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
@@ -505,16 +505,16 @@
if (decoration == null) {
return;
}
- openInBrowser(uri);
+ openInBrowser(uri, decoration.getUser());
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
}
- private void openInBrowser(Uri uri) {
+ private void openInBrowser(Uri uri, @NonNull UserHandle userHandle) {
final Intent intent = Intent.makeMainSelectorActivity(ACTION_MAIN, CATEGORY_APP_BROWSER)
.setData(uri)
.addFlags(FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
+ mContext.startActivityAsUser(intent, userHandle);
}
private void onToDesktop(int taskId, DesktopModeTransitionSource source) {
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 2bec3fa..81251b8 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
@@ -54,6 +54,7 @@
import android.net.Uri;
import android.os.Handler;
import android.os.Trace;
+import android.os.UserHandle;
import android.util.Size;
import android.util.Slog;
import android.view.Choreographer;
@@ -480,6 +481,10 @@
return mGenericLink;
}
+ UserHandle getUser() {
+ return mUserContext.getUser();
+ }
+
private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
if (!isDragResizable(mTaskInfo)) {
if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
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 1b885aa..507ad64 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.flicker
import android.tools.flicker.AssertionInvocationGroup
+import android.tools.flicker.assertors.assertions.AppLayerIncreasesInSize
import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
@@ -24,6 +25,9 @@
import android.tools.flicker.assertors.assertions.AppWindowCoversLeftHalfScreenAtEnd
import android.tools.flicker.assertors.assertions.AppWindowCoversRightHalfScreenAtEnd
import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
+import android.tools.flicker.assertors.assertions.AppWindowHasMaxBoundsInOnlyOneDimension
+import android.tools.flicker.assertors.assertions.AppWindowHasMaxDisplayHeight
+import android.tools.flicker.assertors.assertions.AppWindowHasMaxDisplayWidth
import android.tools.flicker.assertors.assertions.AppWindowHasSizeOfAtLeast
import android.tools.flicker.assertors.assertions.AppWindowIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways
@@ -243,5 +247,42 @@
AppWindowReturnsToStartBoundsAndPosition(NON_RESIZABLE_APP)
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+
+ val MAXIMIZE_APP =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("MAXIMIZE_APP"),
+ extractor =
+ TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+ )
+ .build(),
+ assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+ listOf(
+ AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+ AppWindowHasMaxDisplayHeight(DESKTOP_MODE_APP),
+ AppWindowHasMaxDisplayWidth(DESKTOP_MODE_APP)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
+
+ val MAXIMIZE_APP_NON_RESIZABLE =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("MAXIMIZE_APP_NON_RESIZABLE"),
+ extractor =
+ TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+ )
+ .build(),
+ assertions =
+ AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+ listOf(
+ AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+ AppWindowMaintainsAspectRatioAlways(DESKTOP_MODE_APP),
+ AppWindowHasMaxBoundsInOnlyOneDimension(DESKTOP_MODE_APP)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppLandscape.kt
new file mode 100644
index 0000000..2179566
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppLandscape.kt
@@ -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 com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by pressing the maximize button on the app header.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppLandscape : MaximizeAppWindow(rotation = ROTATION_90) {
+ @ExpectedScenarios(["MAXIMIZE_APP"])
+ @Test
+ override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppNonResizableLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppNonResizableLandscape.kt
new file mode 100644
index 0000000..b173a60
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppNonResizableLandscape.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP_NON_RESIZABLE
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize non-resizable app window by pressing the maximize button on the app header.
+ *
+ * Assert that the app window keeps the same increases in size, maintaining its aspect ratio, until
+ * filling the vertical or horizontal stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppNonResizableLandscape : MaximizeAppWindow(
+ rotation = ROTATION_90,
+ isResizable = false
+) {
+ @ExpectedScenarios(["MAXIMIZE_APP_NON_RESIZABLE"])
+ @Test
+ override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP_NON_RESIZABLE)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppNonResizablePortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppNonResizablePortrait.kt
new file mode 100644
index 0000000..88888ee
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppNonResizablePortrait.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP_NON_RESIZABLE
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize non-resizable app window by pressing the maximize button on the app header.
+ *
+ * Assert that the app window keeps the same increases in size, maintaining its aspect ratio, until
+ * filling the vertical or horizontal stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppNonResizablePortrait : MaximizeAppWindow(isResizable = false) {
+ @ExpectedScenarios(["MAXIMIZE_APP_NON_RESIZABLE"])
+ @Test
+ override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP_NON_RESIZABLE)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppPortrait.kt
new file mode 100644
index 0000000..b79fd203
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppPortrait.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by pressing the maximize button on the app header.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppPortrait : MaximizeAppWindow() {
+ @ExpectedScenarios(["MAXIMIZE_APP"])
+ @Test
+ override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUtilsTest.kt
new file mode 100644
index 0000000..8fab410
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUtilsTest.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.graphics.Rect
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeUtilsTest {
+ @Test
+ fun isTaskBoundsEqual_stableBoundsAreEqual_returnTrue() {
+ assertThat(isTaskBoundsEqual(task2Bounds, stableBounds)).isTrue()
+ }
+
+ @Test
+ fun isTaskBoundsEqual_stableBoundsAreNotEqual_returnFalse() {
+ assertThat(isTaskBoundsEqual(task4Bounds, stableBounds)).isFalse()
+ }
+
+ @Test
+ fun isTaskWidthOrHeightEqual_stableBoundsAreEqual_returnTrue() {
+ assertThat(isTaskWidthOrHeightEqual(task2Bounds, stableBounds)).isTrue()
+ }
+
+ @Test
+ fun isTaskWidthOrHeightEqual_stableBoundWidthIsEquals_returnTrue() {
+ assertThat(isTaskWidthOrHeightEqual(task3Bounds, stableBounds)).isTrue()
+ }
+
+ @Test
+ fun isTaskWidthOrHeightEqual_stableBoundHeightIsEquals_returnTrue() {
+ assertThat(isTaskWidthOrHeightEqual(task3Bounds, stableBounds)).isTrue()
+ }
+
+ @Test
+ fun isTaskWidthOrHeightEqual_stableBoundsWidthOrHeightAreNotEquals_returnFalse() {
+ assertThat(isTaskWidthOrHeightEqual(task1Bounds, stableBounds)).isTrue()
+ }
+
+ private companion object {
+ val task1Bounds = Rect(0, 0, 0, 0)
+ val task2Bounds = Rect(1, 1, 1, 1)
+ val task3Bounds = Rect(0, 1, 0, 1)
+ val task4Bounds = Rect(1, 2, 2, 1)
+ val stableBounds = Rect(1, 1, 1, 1)
+ }
+}
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 d248720..5474e53 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
@@ -84,6 +84,7 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
+import com.android.wm.shell.desktopmode.DesktopTasksController.TaskbarDesktopTaskListener
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
@@ -175,6 +176,7 @@
@Mock
private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
@Mock private lateinit var mockSurface: SurfaceControl
+ @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -237,6 +239,8 @@
val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
recentsTransitionStateListener = captor.value
+
+ controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
}
private fun createController(): DesktopTasksController {
@@ -282,6 +286,52 @@
}
@Test
+ fun doesAnyTaskRequireTaskbarRounding_onlyFreeFormTaskIsRunning_returnFalse() {
+ setUpFreeformTask()
+
+ assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isFalse()
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFreeFormTask_returnTrue() {
+ val task1 = setUpFreeformTask()
+
+ val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ controller.toggleDesktopTaskSize(task1)
+ verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+
+ assertThat(argumentCaptor.value).isTrue()
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_fullScreenTaskIsRunning_returnTrue() {
+ val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+ setUpFreeformTask(bounds = stableBounds, active = true)
+ assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue()
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFullScreenTask_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)
+ verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+
+ assertThat(argumentCaptor.value).isFalse()
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_splitScreenTaskIsRunning_returnTrue() {
+ val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+ setUpFreeformTask(bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom))
+
+ assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue()
+ }
+
+
+ @Test
fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() {
whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
clearInvocations(shellInit)
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 1ea8e8a..0b5c678 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
@@ -34,6 +34,7 @@
import android.hardware.input.InputManager
import android.net.Uri
import android.os.Handler
+import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.CheckFlagsRule
@@ -163,6 +164,7 @@
@Mock private lateinit var mockWindowManager: IWindowManager
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
@Mock private lateinit var mockGenericLinksParser: AppToWebGenericLinksParser
+ @Mock private lateinit var mockUserHandle: UserHandle
@Mock private lateinit var mockToast: Toast
private val bgExecutor = TestShellExecutor()
@Mock private lateinit var mockMultiInstanceHelper: MultiInstanceHelper
@@ -892,12 +894,12 @@
openInBrowserListenerCaptor.value.accept(uri)
- verify(spyContext).startActivity(argThat { intent ->
+ verify(spyContext).startActivityAsUser(argThat { intent ->
intent.data == uri
&& ((intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK) != 0)
&& intent.categories.contains(Intent.CATEGORY_LAUNCHER)
&& intent.action == Intent.ACTION_MAIN
- })
+ }, eq(mockUserHandle))
}
@Test
@@ -1108,6 +1110,7 @@
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.isFocused).thenReturn(task.isFocused)
+ whenever(decoration.user).thenReturn(mockUserHandle)
if (task.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
whenever(mockSplitScreenController.isTaskInSplitScreen(task.taskId))
.thenReturn(true)
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index d14275f..92f6eaf 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -2533,16 +2533,19 @@
/**
* Request a frontend by frontend type.
*
- * <p> This API is used if the applications want to select a frontend with desired type when
- * there are multiple frontends of the same type is there before {@link tune}. The applied
- * frontend will be one of the not in-use frontend. If all frontends are in-use, this API will
- * reclaim and apply the frontend owned by the lowest priority client if current client has
- * higher priority. Otherwise, this API will not apply any frontend and return
- * {@link #RESULT_UNAVAILABLE}.
+ * <p> This API is used (before {@link #tune(FrontendSettings)}) if the applications want to
+ * select a frontend of a particular type for {@link #tune(FrontendSettings)} when there are
+ * multiple frontends of the same type present, allowing the system to select which one is
+ * applied. The applied frontend will be one of the not-in-use frontends. If all frontends are
+ * in-use, this API will reclaim and apply the frontend owned by the lowest priority client if
+ * current client has higher priority. Otherwise, this API will not apply any frontend and
+ * return {@link #RESULT_UNAVAILABLE}.
*
* @param desiredFrontendType the Type of the desired fronted. Should be one of
* {@link android.media.tv.tuner.frontend.FrontendSettings.Type}
* @return result status of open operation.
+ * @see #applyFrontend(FrontendInfo)
+ * @see #tune(FrontendSettings)
*/
@Result
@FlaggedApi(Flags.FLAG_TUNER_W_APIS)
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 447e980..5b6b6c0 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -220,14 +220,15 @@
field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
field public static final String CATEGORY_OTHER = "other";
field public static final String CATEGORY_PAYMENT = "payment";
- field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String DH = "DH";
- field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String ESE = "ESE";
field public static final String EXTRA_CATEGORY = "category";
field public static final String EXTRA_SERVICE_COMPONENT = "component";
+ field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DH = 0; // 0x0
+ field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE = 1; // 0x1
+ field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC = 2; // 0x2
+ field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET = -1; // 0xffffffff
field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
- field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String UICC = "UICC";
}
public abstract class HostApduService extends android.app.Service {
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 25a01b9..717e01e 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -94,7 +94,7 @@
public final class CardEmulation {
method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static android.content.ComponentName getPreferredPaymentService(@NonNull android.content.Context);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
- method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void overrideRoutingTable(@NonNull android.app.Activity, @Nullable String, @Nullable String);
+ method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void overrideRoutingTable(@NonNull android.app.Activity, int, int);
method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void recoverRoutingTable(@NonNull android.app.Activity);
}
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 0605dbe..497309c 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -18,12 +18,12 @@
import android.Manifest;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.annotation.UserHandleAware;
import android.annotation.UserIdInt;
@@ -155,17 +155,23 @@
* Route to Device Host (DH).
*/
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
- public static final String DH = "DH";
+ public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DH = 0;
/**
* Route to eSE.
*/
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
- public static final String ESE = "ESE";
+ public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE = 1;
/**
* Route to UICC.
*/
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
- public static final String UICC = "UICC";
+ public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC = 2;
+
+ /**
+ * Route unset.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+ public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET = -1;
static boolean sIsInitialized = false;
static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
@@ -734,7 +740,7 @@
*
* @return the preferred payment service description
*/
- @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @RequiresPermission(Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
@Nullable
public CharSequence getDescriptionForPreferredPaymentService() {
ApduServiceInfo serviceInfo = callServiceReturn(() ->
@@ -884,10 +890,12 @@
}
/** @hide */
- @StringDef({
- DH,
- ESE,
- UICC
+ @IntDef(prefix = "PROTOCOL_AND_TECHNOLOGY_ROUTE_",
+ value = {
+ PROTOCOL_AND_TECHNOLOGY_ROUTE_DH,
+ PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE,
+ PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC,
+ PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtocolAndTechnologyRoute {}
@@ -896,29 +904,32 @@
* Setting NFC controller routing table, which includes Protocol Route and Technology Route,
* while this Activity is in the foreground.
*
- * The parameter set to null can be used to keep current values for that entry. Either
+ * The parameter set to {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET}
+ * can be used to keep current values for that entry. Either
* Protocol Route or Technology Route should be override when calling this API, otherwise
* throw {@link IllegalArgumentException}.
* <p>
* Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
* <pre>
* protected void onResume() {
- * mNfcAdapter.overrideRoutingTable(this , "ESE" , null);
+ * mNfcAdapter.overrideRoutingTable(
+ * this, {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE}, null);
* }</pre>
* </p>
* Also activities must call {@link #recoverRoutingTable(Activity)}
* when it goes to the background. Only the package of the
* currently preferred service (the service set as preferred by the current foreground
* application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the
- * current Default Wallet Role Holder {@link android.app.role.RoleManager#ROLE_WALLET}),
+ * current Default Wallet Role Holder {@link RoleManager#ROLE_WALLET}),
* otherwise a call to this method will fail and throw {@link SecurityException}.
* @param activity The Activity that requests NFC controller routing table to be changed.
- * @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE".
- * @param technology Tech-A, Tech-B and Tech-F route destination, which can be "DH" or "UICC"
- * or "ESE".
+ * @param protocol ISO-DEP route destination, where the possible inputs are defined
+ * in {@link ProtocolAndTechnologyRoute}.
+ * @param technology Tech-A, Tech-B and Tech-F route destination, where the possible inputs
+ * are defined in {@link ProtocolAndTechnologyRoute}
* @throws SecurityException if the caller is not the preferred NFC service
* @throws IllegalArgumentException if the activity is not resumed or the caller is not in the
- * foreground, or both protocol route and technology route are null.
+ * foreground.
* <p>
* This is a high risk API and only included to support mainline effort
* @hide
@@ -926,25 +937,36 @@
@SystemApi
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
public void overrideRoutingTable(
- @NonNull Activity activity, @ProtocolAndTechnologyRoute @Nullable String protocol,
- @ProtocolAndTechnologyRoute @Nullable String technology) {
+ @NonNull Activity activity, @ProtocolAndTechnologyRoute int protocol,
+ @ProtocolAndTechnologyRoute int technology) {
if (!activity.isResumed()) {
throw new IllegalArgumentException("Activity must be resumed.");
}
- if (protocol == null && technology == null) {
- throw new IllegalArgumentException(("Both Protocol and Technology are null."));
- }
+ String protocolRoute = switch (protocol) {
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "ESE";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "UICC";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
+ default -> throw new IllegalStateException("Unexpected value: " + protocol);
+ };
+ String technologyRoute = switch (technology) {
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "ESE";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "UICC";
+ case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
+ default -> throw new IllegalStateException("Unexpected value: " + protocol);
+ };
callService(() ->
sService.overrideRoutingTable(
mContext.getUser().getIdentifier(),
- protocol,
- technology,
+ protocolRoute,
+ technologyRoute,
mContext.getPackageName()));
}
/**
* Restore the NFC controller routing table,
- * which was changed by {@link #overrideRoutingTable(Activity, String, String)}
+ * which was changed by {@link #overrideRoutingTable(Activity, int, int)}
*
* @param activity The Activity that requested NFC controller routing table to be changed.
* @throws IllegalArgumentException if the caller is not in the foreground.
diff --git a/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg.xml b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg.xml
new file mode 100644
index 0000000..35517ea
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorAccentPrimary" />
+ <corners android:radius="12dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml
new file mode 100644
index 0000000..18696c6
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:drawable="@drawable/audio_sharing_rounded_bg"/>
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 055afed..0ffb763 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -1,5 +1,6 @@
package com.android.settingslib.bluetooth;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.UNKNOWN_VALUE_PLACEHOLDER;
import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED;
import android.annotation.SuppressLint;
@@ -704,12 +705,50 @@
return !sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected);
}
+ /**
+ * Check if {@link BluetoothDevice} has a active local broadcast source.
+ *
+ * @param device The bluetooth device to check.
+ * @param localBtManager The BT manager to provide BT functions.
+ * @return Whether the device has a active local broadcast source.
+ */
+ @WorkerThread
+ public static boolean hasActiveLocalBroadcastSourceForBtDevice(
+ @Nullable BluetoothDevice device, @Nullable LocalBluetoothManager localBtManager) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ localBtManager == null
+ ? null
+ : localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ LocalBluetoothLeBroadcast broadcast =
+ localBtManager == null
+ ? null
+ : localBtManager.getProfileManager().getLeAudioBroadcastProfile();
+ if (device == null || assistant == null || broadcast == null) {
+ Log.d(TAG, "Skip check hasActiveLocalBroadcastSourceForBtDevice due to arg is null");
+ return false;
+ }
+ List<BluetoothLeBroadcastReceiveState> sourceList = assistant.getAllSources(device);
+ int broadcastId = broadcast.getLatestBroadcastId();
+ return !sourceList.isEmpty()
+ && broadcastId != UNKNOWN_VALUE_PLACEHOLDER
+ && sourceList.stream()
+ .anyMatch(
+ source -> isSourceMatched(source, broadcastId));
+ }
+
/** Checks the connectivity status based on the provided broadcast receive state. */
@WorkerThread
public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
return state.getBisSyncState().stream().anyMatch(bitmap -> bitmap != 0);
}
+ /** Checks if the broadcast id is matched based on the provided broadcast receive state. */
+ @WorkerThread
+ public static boolean isSourceMatched(
+ @Nullable BluetoothLeBroadcastReceiveState state, int broadcastId) {
+ return state != null && state.getBroadcastId() == broadcastId;
+ }
+
/**
* Checks if the Bluetooth device is an available hearing device, which means: 1) currently
* connected 2) is Hearing Aid 3) connected profile match hearing aid related profiles (e.g.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 26905b1..6f2567b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -101,7 +101,7 @@
private static final int DEFAULT_CODE_MIN = 1000;
// Order of this profile in device profiles list
private static final int ORDINAL = 1;
- private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
+ static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
private static final Uri[] SETTINGS_URIS =
new Uri[] {
Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_NAME),
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index 4f2329b..47a08eb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -136,12 +136,10 @@
return true;
}
- if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
- // App is subject to DevicePolicyManager.setUserControlDisabledPackages() policy.
- final int userId = UserHandle.getUserId(uid);
- if (mAppContext.getPackageManager().isPackageStateProtected(pkg, userId)) {
- return true;
- }
+ // App is subject to DevicePolicyManager.setUserControlDisabledPackages() policy.
+ final int userId = UserHandle.getUserId(uid);
+ if (mAppContext.getPackageManager().isPackageStateProtected(pkg, userId)) {
+ return true;
}
return false;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 3a7b0c7..a0e764a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -16,6 +16,7 @@
package com.android.settingslib.bluetooth;
import static com.android.settingslib.bluetooth.BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.UNKNOWN_VALUE_PLACEHOLDER;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA;
import static com.google.common.truth.Truth.assertThat;
@@ -96,6 +97,7 @@
+ "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager";
private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component";
+ private static final int TEST_BROADCAST_ID = 25;
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -671,6 +673,34 @@
}
@Test
+ public void testHasActiveLocalBroadcastSourceForBtDevice_hasActiveLocalSource() {
+ when(mBroadcast.getLatestBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+ when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasActiveLocalBroadcastSourceForBtDevice(
+ mBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
+ }
+
+ @Test
+ public void testHasActiveLocalBroadcastSourceForBtDevice_noActiveLocalSource() {
+ when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(UNKNOWN_VALUE_PLACEHOLDER);
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasActiveLocalBroadcastSourceForBtDevice(
+ mBluetoothDevice, mLocalBluetoothManager))
+ .isFalse();
+ }
+
+
+ @Test
public void isAvailableHearingDevice_isConnectedHearingAid_returnTure() {
when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 65937ea..f6e1057 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -340,7 +340,8 @@
new String[] {
String.valueOf(Global.Wearable.PAIRED_DEVICE_OS_TYPE_UNKNOWN),
String.valueOf(Global.Wearable.PAIRED_DEVICE_OS_TYPE_ANDROID),
- String.valueOf(Global.Wearable.PAIRED_DEVICE_OS_TYPE_IOS)
+ String.valueOf(Global.Wearable.PAIRED_DEVICE_OS_TYPE_IOS),
+ String.valueOf(Global.Wearable.PAIRED_DEVICE_OS_TYPE_NONE)
}));
VALIDATORS.put(
Global.Wearable.COMPANION_BLE_ROLE,
diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig
index 95e4b59..10d7352 100644
--- a/packages/SystemUI/aconfig/biometrics_framework.aconfig
+++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig
@@ -3,3 +3,12 @@
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+flag {
+ name: "bp_icon_a11y"
+ namespace: "biometrics_framework"
+ description: "Fixes biometric prompt icon not working as button with a11y"
+ bug: "359423579"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 9d3a827..df4b51a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -384,6 +384,13 @@
}
flag {
+ name: "status_bar_ron_chips"
+ namespace: "systemui"
+ description: "Show rich ongoing notifications as chips in the status bar"
+ bug: "361346412"
+}
+
+flag {
name: "compose_bouncer"
namespace: "systemui"
description: "Use the new compose bouncer in SystemUI"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 5a4020d..270d751 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -76,7 +76,7 @@
modifier: Modifier,
) =
BouncerScene(
- viewModel = rememberViewModel { contentViewModelFactory.create() },
+ viewModel = rememberViewModel("BouncerScene") { contentViewModelFactory.create() },
dialogFactory = dialogFactory,
modifier = modifier,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index b0590e0..be6a0f9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -567,16 +567,8 @@
// Do nothing if there is no new live content
val indexOfFirstUpdatedContent =
newLiveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
- if (indexOfFirstUpdatedContent < 0) {
- return@LaunchedEffect
- }
-
- // Scroll if the live content is not visible
- val lastVisibleItemIndex = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
- if (lastVisibleItemIndex != null && indexOfFirstUpdatedContent > lastVisibleItemIndex) {
- // Launching with a scope to prevent the job from being canceled in the case of a
- // recomposition during scrolling
- coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) }
+ if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) {
+ gridState.scrollToItem(indexOfFirstUpdatedContent)
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 672b8a7..dbe7538 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -50,7 +50,7 @@
fun SceneScope.Content(
modifier: Modifier = Modifier,
) {
- val viewModel = rememberViewModel { viewModelFactory.create() }
+ val viewModel = rememberViewModel("LockscreenContent") { viewModelFactory.create() }
val isContentVisible: Boolean by viewModel.isContentVisible.collectAsStateWithLifecycle()
if (!isContentVisible) {
// If the content isn't supposed to be visible, show a large empty box as it's needed
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 5f4dc6e..18e1092 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -83,7 +83,7 @@
fun SceneScope.HeadsUpNotifications() {
SnoozeableHeadsUpNotificationSpace(
stackScrollView = stackScrollView.get(),
- viewModel = rememberViewModel { viewModelFactory.create() },
+ viewModel = rememberViewModel("HeadsUpNotifications") { viewModelFactory.create() },
)
}
@@ -107,7 +107,7 @@
ConstrainedNotificationStack(
stackScrollView = stackScrollView.get(),
- viewModel = rememberViewModel { viewModelFactory.create() },
+ viewModel = rememberViewModel("Notifications") { viewModelFactory.create() },
modifier =
modifier
.fillMaxWidth()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 9e292d0..a2beba8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -321,7 +321,9 @@
val minScrimOffset: () -> Float = { minScrimTop - maxScrimTop() }
// The height of the scrim visible on screen when it is in its resting (collapsed) state.
- val minVisibleScrimHeight: () -> Float = { screenHeight - maxScrimTop() }
+ val minVisibleScrimHeight: () -> Float = {
+ screenHeight - maxScrimTop() - with(density) { navBarHeight.toPx() }
+ }
// we are not scrolled to the top unless the scrim is at its maximum offset.
LaunchedEffect(viewModel, scrimOffset) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index 62213bd..e9c96ea 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -81,9 +81,10 @@
override fun SceneScope.Content(
modifier: Modifier,
) {
- val notificationsPlaceholderViewModel = rememberViewModel {
- notificationsPlaceholderViewModelFactory.create()
- }
+ val notificationsPlaceholderViewModel =
+ rememberViewModel("NotificationsShadeScene") {
+ notificationsPlaceholderViewModelFactory.create()
+ }
OverlayShade(
modifier = modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 1921624..671b012 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -98,7 +98,7 @@
else -> QSSceneAdapter.State.CLOSED
}
}
- is TransitionState.Transition.ChangeCurrentScene ->
+ is TransitionState.Transition.ChangeScene ->
with(transitionState) {
when {
isSplitShade -> UnsquishingQS(squishiness)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index f11f8bb..d372577 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -152,7 +152,9 @@
notificationStackScrollView = notificationStackScrollView.get(),
viewModelFactory = contentViewModelFactory,
notificationsPlaceholderViewModel =
- rememberViewModel { notificationsPlaceholderViewModelFactory.create() },
+ rememberViewModel("QuickSettingsScene-notifPlaceholderViewModel") {
+ notificationsPlaceholderViewModelFactory.create()
+ },
createTintedIconManager = tintedIconManagerFactory::create,
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
statusBarIconController = statusBarIconController,
@@ -179,10 +181,11 @@
) {
val cutoutLocation = LocalDisplayCutout.current.location
- val viewModel = rememberViewModel { viewModelFactory.create() }
- val brightnessMirrorViewModel = rememberViewModel {
- viewModel.brightnessMirrorViewModelFactory.create()
- }
+ val viewModel = rememberViewModel("QuickSettingsScene-viewModel") { viewModelFactory.create() }
+ val brightnessMirrorViewModel =
+ rememberViewModel("QuickSettingsScene-brightnessMirrorViewModel") {
+ viewModel.brightnessMirrorViewModelFactory.create()
+ }
val brightnessMirrorShowing by brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
val contentAlpha by
animateFloatAsState(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index b25773b..90d7da6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -94,7 +94,8 @@
override fun SceneScope.Content(
modifier: Modifier,
) {
- val viewModel = rememberViewModel { contentViewModelFactory.create() }
+ val viewModel =
+ rememberViewModel("QuickSettingsShadeScene") { contentViewModelFactory.create() }
OverlayShade(
viewModelFactory = viewModel.overlayShadeViewModelFactory,
lockscreenContent = lockscreenContent,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index d489d73..cbbace4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -75,7 +75,10 @@
Spacer(modifier.fillMaxSize())
SnoozeableHeadsUpNotificationSpace(
stackScrollView = notificationStackScrolLView.get(),
- viewModel = rememberViewModel { notificationsPlaceholderViewModelFactory.create() },
+ viewModel =
+ rememberViewModel("GoneScene") {
+ notificationsPlaceholderViewModelFactory.create()
+ },
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
index 1fee874..022eb1f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
@@ -5,10 +5,16 @@
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.bouncer.ui.composable.Bouncer
+const val FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION = 0.5f
+
fun TransitionBuilder.lockscreenToBouncerTransition() {
spec = tween(durationMillis = 500)
translate(Bouncer.Elements.Content, y = 300.dp)
- fractionRange(end = 0.5f) { fade(Bouncer.Elements.Background) }
- fractionRange(start = 0.5f) { fade(Bouncer.Elements.Content) }
+ fractionRange(end = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
+ fade(Bouncer.Elements.Background)
+ }
+ fractionRange(start = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
+ fade(Bouncer.Elements.Content)
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 445ffcb..595bbb0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -69,7 +69,7 @@
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
- val viewModel = rememberViewModel { viewModelFactory.create() }
+ val viewModel = rememberViewModel("OverlayShade") { viewModelFactory.create() }
val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle()
Box(modifier) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 8c53740..05a0119 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -129,7 +129,7 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
- val viewModel = rememberViewModel { viewModelFactory.create() }
+ val viewModel = rememberViewModel("CollapsedShadeHeader") { viewModelFactory.create() }
val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
if (isDisabled) {
return
@@ -287,7 +287,7 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
- val viewModel = rememberViewModel { viewModelFactory.create() }
+ val viewModel = rememberViewModel("ExpandedShadeHeader") { viewModelFactory.create() }
val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
if (isDisabled) {
return
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index f8513a8..b7c6edc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -182,9 +182,12 @@
) =
ShadeScene(
notificationStackScrollView.get(),
- viewModel = rememberViewModel { contentViewModelFactory.create() },
+ viewModel =
+ rememberViewModel("ShadeScene-viewModel") { contentViewModelFactory.create() },
notificationsPlaceholderViewModel =
- rememberViewModel { notificationsPlaceholderViewModelFactory.create() },
+ rememberViewModel("ShadeScene-notifPlaceholderViewModel") {
+ notificationsPlaceholderViewModelFactory.create()
+ },
createTintedIconManager = tintedIconManagerFactory::create,
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
statusBarIconController = statusBarIconController,
@@ -493,9 +496,10 @@
}
}
- val brightnessMirrorViewModel = rememberViewModel {
- viewModel.brightnessMirrorViewModelFactory.create()
- }
+ val brightnessMirrorViewModel =
+ rememberViewModel("SplitShade-brightnessMirrorViewModel") {
+ viewModel.brightnessMirrorViewModelFactory.create()
+ }
val brightnessMirrorShowing by brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
val contentAlpha by
animateFloatAsState(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index abe079a..e15bc12 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -28,7 +28,7 @@
layoutState: MutableSceneTransitionLayoutStateImpl,
target: SceneKey,
transitionKey: TransitionKey?,
-): TransitionState.Transition.ChangeCurrentScene? {
+): TransitionState.Transition.ChangeScene? {
val transitionState = layoutState.transitionState
if (transitionState.currentScene == target) {
// This can happen in 3 different situations, for which there isn't anything else to do:
@@ -55,7 +55,7 @@
replacedTransition = null,
)
}
- is TransitionState.Transition.ChangeCurrentScene -> {
+ is TransitionState.Transition.ChangeScene -> {
val isInitiatedByUserInput = transitionState.isInitiatedByUserInput
// A transition is currently running: first check whether `transition.toScene` or
@@ -139,7 +139,7 @@
reversed: Boolean = false,
fromScene: SceneKey = layoutState.transitionState.currentScene,
chain: Boolean = true,
-): TransitionState.Transition.ChangeCurrentScene {
+): TransitionState.Transition.ChangeScene {
val oneOffAnimation = OneOffAnimation()
val targetProgress = if (reversed) 0f else 1f
val transition =
@@ -184,7 +184,7 @@
override val isInitiatedByUserInput: Boolean,
replacedTransition: TransitionState.Transition?,
private val oneOffAnimation: OneOffAnimation,
-) : TransitionState.Transition.ChangeCurrentScene(fromScene, toScene, replacedTransition) {
+) : TransitionState.Transition.ChangeScene(fromScene, toScene, replacedTransition) {
override val progress: Float
get() = oneOffAnimation.progress
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 71ff8a8..37e4daa 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -109,7 +109,7 @@
// Only intercept the current transition if one of the 2 swipes results is also a transition
// between the same pair of contents.
val swipes = computeSwipes(startedPosition, pointersDown = 1)
- val fromContent = swipeAnimation.currentContent
+ val fromContent = layoutImpl.content(swipeAnimation.currentContent)
val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent)
val currentScene = layoutImpl.state.currentScene
val contentTransition = swipeAnimation.contentTransition
@@ -145,7 +145,9 @@
// We need to recompute the swipe results since this is a new gesture, and the
// fromScene.userActions may have changed.
val swipes = oldDragController.swipes
- swipes.updateSwipesResults(fromContent = oldSwipeAnimation.fromContent)
+ swipes.updateSwipesResults(
+ fromContent = layoutImpl.content(oldSwipeAnimation.fromContent)
+ )
// A new gesture should always create a new SwipeAnimation. This way there cannot be
// different gestures controlling the same transition.
@@ -155,8 +157,10 @@
val swipes = computeSwipes(startedPosition, pointersDown)
val fromContent = layoutImpl.contentForUserActions()
+
+ swipes.updateSwipesResults(fromContent)
val result =
- swipes.findUserActionResult(fromContent, overSlop, updateSwipesResults = true)
+ swipes.findUserActionResult(overSlop)
// As we were unable to locate a valid target scene, the initial SwipeAnimation
// cannot be defined. Consequently, a simple NoOp Controller will be returned.
?: return NoOpDragController
@@ -188,7 +192,13 @@
else -> error("Unknown result $result ($upOrLeftResult $downOrRightResult)")
}
- return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
+ return createSwipeAnimation(
+ layoutImpl,
+ layoutImpl.coroutineScope,
+ result,
+ isUpOrLeft,
+ orientation
+ )
}
private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes {
@@ -291,7 +301,7 @@
return onDrag(delta, swipeAnimation)
}
- private fun <T : Content> onDrag(delta: Float, swipeAnimation: SwipeAnimation<T>): Float {
+ private fun <T : ContentKey> onDrag(delta: Float, swipeAnimation: SwipeAnimation<T>): Float {
if (delta == 0f || !isDrivingTransition || swipeAnimation.isFinishing) {
return 0f
}
@@ -304,12 +314,12 @@
fun hasReachedToSceneUpOrLeft() =
distance < 0 &&
desiredOffset <= distance &&
- swipes.upOrLeftResult?.toContent(layoutState.currentScene) == toContent.key
+ swipes.upOrLeftResult?.toContent(layoutState.currentScene) == toContent
fun hasReachedToSceneDownOrRight() =
distance > 0 &&
desiredOffset >= distance &&
- swipes.downOrRightResult?.toContent(layoutState.currentScene) == toContent.key
+ swipes.downOrRightResult?.toContent(layoutState.currentScene) == toContent
// Considering accelerated swipe: Change fromContent in the case where the user quickly
// swiped multiple times in the same direction to accelerate the transition from A => B then
@@ -321,7 +331,7 @@
swipeAnimation.currentContent == toContent &&
(hasReachedToSceneUpOrLeft() || hasReachedToSceneDownOrRight())
- val fromContent: Content
+ val fromContent: ContentKey
val currentTransitionOffset: Float
val newOffset: Float
val consumedDelta: Float
@@ -357,12 +367,10 @@
swipeAnimation.dragOffset = currentTransitionOffset
- val result =
- swipes.findUserActionResult(
- fromContent = fromContent,
- directionOffset = newOffset,
- updateSwipesResults = hasReachedToContent
- )
+ if (hasReachedToContent) {
+ swipes.updateSwipesResults(draggableHandler.layoutImpl.content(fromContent))
+ }
+ val result = swipes.findUserActionResult(directionOffset = newOffset)
if (result == null) {
onStop(velocity = delta, canChangeContent = true)
@@ -371,7 +379,7 @@
val needNewTransition =
hasReachedToContent ||
- result.toContent(layoutState.currentScene) != swipeAnimation.toContent.key ||
+ result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
result.transitionKey != swipeAnimation.contentTransition.key
if (needNewTransition) {
@@ -390,7 +398,7 @@
return onStop(velocity, canChangeContent, swipeAnimation)
}
- private fun <T : Content> onStop(
+ private fun <T : ContentKey> onStop(
velocity: Float,
canChangeContent: Boolean,
@@ -407,7 +415,6 @@
fun animateTo(targetContent: T) {
swipeAnimation.animateOffset(
- coroutineScope = draggableHandler.coroutineScope,
initialVelocity = velocity,
targetContent = targetContent,
)
@@ -518,6 +525,14 @@
return upOrLeftResult to downOrRightResult
}
+ /**
+ * Update the swipes results.
+ *
+ * Usually we don't want to update them while doing a drag, because this could change the target
+ * content (jump cutting) to a different content, when some system state changed the targets the
+ * background. However, an update is needed any time we calculate the targets for a new
+ * fromContent.
+ */
fun updateSwipesResults(fromContent: Content) {
val (upOrLeftResult, downOrRightResult) = computeSwipesResults(fromContent)
@@ -526,31 +541,17 @@
}
/**
- * Returns the [UserActionResult] from [fromContent] in the direction of [directionOffset].
+ * Returns the [UserActionResult] in the direction of [directionOffset].
*
- * @param fromContent the content from which we look for the target
* @param directionOffset signed float that indicates the direction. Positive is down or right
* negative is up or left.
- * @param updateSwipesResults whether the swipe results should be updated to the current values
- * held in the user actions map. Usually we don't want to update them while doing a drag,
- * because this could change the target content (jump cutting) to a different content, when
- * some system state changed the targets the background. However, an update is needed any time
- * we calculate the targets for a new fromContent.
* @return null when there are no targets in either direction. If one direction is null and you
* drag into the null direction this function will return the opposite direction, assuming
* that the users intention is to start the drag into the other direction eventually. If
* [directionOffset] is 0f and both direction are available, it will default to
* [upOrLeftResult].
*/
- fun findUserActionResult(
- fromContent: Content,
- directionOffset: Float,
- updateSwipesResults: Boolean,
- ): UserActionResult? {
- if (updateSwipesResults) {
- updateSwipesResults(fromContent)
- }
-
+ fun findUserActionResult(directionOffset: Float): UserActionResult? {
return when {
upOrLeftResult == null && downOrRightResult == null -> null
(directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt
index 6181cfb..cb18c67 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt
@@ -37,7 +37,7 @@
* @see InterruptionResult
*/
fun onInterruption(
- interrupted: TransitionState.Transition.ChangeCurrentScene,
+ interrupted: TransitionState.Transition.ChangeScene,
newTargetScene: SceneKey,
): InterruptionResult?
}
@@ -76,7 +76,7 @@
*/
object DefaultInterruptionHandler : InterruptionHandler {
override fun onInterruption(
- interrupted: TransitionState.Transition.ChangeCurrentScene,
+ interrupted: TransitionState.Transition.ChangeScene,
newTargetScene: SceneKey,
): InterruptionResult {
return InterruptionResult(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 236e202..3a7c2bf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -43,7 +43,7 @@
fun currentScene(): Flow<SceneKey> {
return when (this) {
is Idle -> flowOf(currentScene)
- is Transition.ChangeCurrentScene -> currentScene
+ is Transition.ChangeScene -> currentScene
is Transition.ShowOrHideOverlay -> flowOf(currentScene)
is Transition.ReplaceOverlay -> flowOf(currentScene)
}
@@ -94,7 +94,7 @@
.trimMargin()
/** A transition animating between [fromScene] and [toScene]. */
- class ChangeCurrentScene(
+ class ChangeScene(
override val fromScene: SceneKey,
override val toScene: SceneKey,
val currentScene: Flow<SceneKey>,
@@ -174,8 +174,8 @@
previewProgress: Flow<Float> = flowOf(0f),
isInPreviewStage: Flow<Boolean> = flowOf(false),
currentOverlays: Flow<Set<OverlayKey>> = flowOf(emptySet()),
- ): ChangeCurrentScene {
- return ChangeCurrentScene(
+ ): ChangeScene {
+ return ChangeScene(
fromScene,
toScene,
currentScene,
@@ -190,7 +190,7 @@
}
}
- fun isIdle(scene: SceneKey?): Boolean {
+ fun isIdle(scene: SceneKey? = null): Boolean {
return this is Idle && (scene == null || this.currentScene == scene)
}
@@ -210,8 +210,8 @@
return snapshotFlow {
when (val state = transitionState) {
is TransitionState.Idle -> ObservableTransitionState.Idle(state.currentScene)
- is TransitionState.Transition.ChangeCurrentScene -> {
- ObservableTransitionState.Transition.ChangeCurrentScene(
+ is TransitionState.Transition.ChangeScene -> {
+ ObservableTransitionState.Transition.ChangeScene(
fromScene = state.fromScene,
toScene = state.toScene,
currentScene = snapshotFlow { state.currentScene },
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index e7e6b2a..be4fea1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -18,120 +18,75 @@
import androidx.activity.BackEventCompat
import androidx.activity.compose.PredictiveBackHandler
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import com.android.compose.animation.scene.content.state.TransitionState
import kotlin.coroutines.cancellation.CancellationException
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
@Composable
internal fun PredictiveBackHandler(
- state: MutableSceneTransitionLayoutStateImpl,
- coroutineScope: CoroutineScope,
- targetSceneForBack: SceneKey? = null,
+ layoutImpl: SceneTransitionLayoutImpl,
+ result: UserActionResult?,
) {
PredictiveBackHandler(
- enabled = targetSceneForBack != null,
+ enabled = result != null,
) { progress: Flow<BackEventCompat> ->
- val fromScene = state.transitionState.currentScene
- if (targetSceneForBack == null || targetSceneForBack == fromScene) {
+ if (result == null) {
// Note: We have to collect progress otherwise PredictiveBackHandler will throw.
progress.first()
return@PredictiveBackHandler
}
- val transition =
- PredictiveBackTransition(state, coroutineScope, fromScene, toScene = targetSceneForBack)
- state.startTransition(transition)
- try {
- progress.collect { backEvent -> transition.dragProgress = backEvent.progress }
+ val animation =
+ createSwipeAnimation(
+ layoutImpl,
+ layoutImpl.coroutineScope,
+ result,
+ isUpOrLeft = false,
+ // Note that the orientation does not matter here given that it's only used to
+ // compute the distance. In our case the distance is always 1f.
+ orientation = Orientation.Horizontal,
+ distance = 1f,
+ )
- // Back gesture successful.
- transition.animateTo(targetSceneForBack)
- } catch (e: CancellationException) {
- // Back gesture cancelled.
- transition.animateTo(fromScene)
- }
+ animate(layoutImpl, animation, progress)
}
}
-private class PredictiveBackTransition(
- val state: MutableSceneTransitionLayoutStateImpl,
- val coroutineScope: CoroutineScope,
- fromScene: SceneKey,
- toScene: SceneKey,
-) : TransitionState.Transition.ChangeCurrentScene(fromScene, toScene) {
- override var currentScene by mutableStateOf(fromScene)
- private set
-
- /** The animated progress once the gesture was committed or cancelled. */
- private var progressAnimatable by mutableStateOf<Animatable<Float, AnimationVector1D>?>(null)
- var dragProgress: Float by mutableFloatStateOf(0f)
-
- override val previewProgress: Float
- get() = dragProgress
-
- override val previewProgressVelocity: Float
- get() = 0f // Currently, velocity is not exposed by predictive back API
-
- override val isInPreviewStage: Boolean
- get() = previewTransformationSpec != null && currentScene == fromScene
-
- override val progress: Float
- get() = progressAnimatable?.value ?: previewTransformationSpec?.let { 0f } ?: dragProgress
-
- override val progressVelocity: Float
- get() = progressAnimatable?.velocity ?: 0f
-
- override val isInitiatedByUserInput: Boolean
- get() = true
-
- override val isUserInputOngoing: Boolean
- get() = progressAnimatable == null
-
- private var animationJob: Job? = null
-
- override fun finish(): Job = animateTo(currentScene)
-
- fun animateTo(scene: SceneKey): Job {
- check(scene == fromScene || scene == toScene)
- animationJob?.let {
- return it
+private suspend fun <T : ContentKey> animate(
+ layoutImpl: SceneTransitionLayoutImpl,
+ animation: SwipeAnimation<T>,
+ progress: Flow<BackEventCompat>,
+) {
+ fun animateOffset(targetContent: T) {
+ if (
+ layoutImpl.state.transitionState != animation.contentTransition || animation.isFinishing
+ ) {
+ return
}
- if (scene != currentScene && state.transitionState == this && state.canChangeScene(scene)) {
- currentScene = scene
- }
+ animation.animateOffset(
+ initialVelocity = 0f,
+ targetContent = targetContent,
- val targetProgress =
- when (currentScene) {
- fromScene -> 0f
- toScene -> 1f
- else -> error("scene $currentScene should be either $fromScene or $toScene")
- }
- val startProgress = if (previewTransformationSpec != null) 0f else dragProgress
- val animatable = Animatable(startProgress).also { progressAnimatable = it }
+ // TODO(b/350705972): Allow to customize or reuse the same customization endpoints as
+ // the normal swipe transitions. We can't just reuse them here because other swipe
+ // transitions animate pixels while this transition animates progress, so the visibility
+ // thresholds will be completely different.
+ spec = spring(),
+ )
+ }
- // Important: We start atomically to make sure that we start the coroutine even if it is
- // cancelled right after it is launched, so that finishTransition() is correctly called.
- return coroutineScope
- .launch(start = CoroutineStart.ATOMIC) {
- try {
- animatable.animateTo(targetProgress)
- } finally {
- state.finishTransition(this@PredictiveBackTransition)
- }
- }
- .also { animationJob = it }
+ layoutImpl.state.startTransition(animation.contentTransition)
+ try {
+ progress.collect { backEvent -> animation.dragOffset = backEvent.progress }
+
+ // Back gesture successful.
+ animateOffset(animation.toContent)
+ } catch (e: CancellationException) {
+ // Back gesture cancelled.
+ animateOffset(animation.fromContent)
}
}
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 258be81..b33b4f6 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
@@ -353,19 +353,8 @@
@Composable
private fun BackHandler() {
- val targetSceneForBack =
- when (val result = contentForUserActions().userActions[Back.Resolved]) {
- null -> null
- is UserActionResult.ChangeScene -> result.toScene
- is UserActionResult.ShowOverlay,
- is UserActionResult.HideOverlay,
- is UserActionResult.ReplaceByOverlay -> {
- // TODO(b/353679003): Support overlay transitions when going back
- null
- }
- }
-
- PredictiveBackHandler(state, coroutineScope, targetSceneForBack)
+ val result = contentForUserActions().userActions[Back.Resolved]
+ PredictiveBackHandler(layoutImpl = this, result = result)
}
@Composable
@@ -389,7 +378,7 @@
// Compose the new scene we are going to first.
transitions.fastForEachReversed { transition ->
when (transition) {
- is TransitionState.Transition.ChangeCurrentScene -> {
+ is TransitionState.Transition.ChangeScene -> {
maybeAdd(transition.toScene)
maybeAdd(transition.fromScene)
}
@@ -439,7 +428,7 @@
transitions.fastForEach { transition ->
when (transition) {
- is TransitionState.Transition.ChangeCurrentScene -> {}
+ is TransitionState.Transition.ChangeScene -> {}
is TransitionState.Transition.ShowOrHideOverlay ->
maybeAdd(transition.overlay)
is TransitionState.Transition.ReplaceOverlay -> {
@@ -495,7 +484,7 @@
val width: Int
val height: Int
val transition =
- layoutImpl.state.currentTransition as? TransitionState.Transition.ChangeCurrentScene
+ layoutImpl.state.currentTransition as? TransitionState.Transition.ChangeScene
if (transition == null) {
width = placeable.width
height = placeable.height
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 47065c7..f3128f1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -299,7 +299,7 @@
targetScene: SceneKey,
coroutineScope: CoroutineScope,
transitionKey: TransitionKey?,
- ): TransitionState.Transition.ChangeCurrentScene? {
+ ): TransitionState.Transition.ChangeScene? {
checkThread()
return coroutineScope.animateToScene(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 8ca90f1..57ff597 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -18,15 +18,13 @@
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.SpringSpec
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.IntSize
-import com.android.compose.animation.scene.content.Content
-import com.android.compose.animation.scene.content.Overlay
-import com.android.compose.animation.scene.content.Scene
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import kotlin.math.absoluteValue
@@ -36,29 +34,96 @@
import kotlinx.coroutines.launch
internal fun createSwipeAnimation(
- layoutImpl: SceneTransitionLayoutImpl,
+ layoutState: MutableSceneTransitionLayoutStateImpl,
+ animationScope: CoroutineScope,
result: UserActionResult,
isUpOrLeft: Boolean,
orientation: Orientation,
+ distance: Float,
): SwipeAnimation<*> {
- fun <T : Content> swipeAnimation(fromContent: T, toContent: T): SwipeAnimation<T> {
+ return createSwipeAnimation(
+ layoutState,
+ animationScope,
+ result,
+ isUpOrLeft,
+ orientation,
+ distance = { distance },
+ contentForUserActions = {
+ error("Computing contentForUserActions requires a SceneTransitionLayoutImpl")
+ },
+ )
+}
+
+internal fun createSwipeAnimation(
+ layoutImpl: SceneTransitionLayoutImpl,
+ animationScope: CoroutineScope,
+ result: UserActionResult,
+ isUpOrLeft: Boolean,
+ orientation: Orientation,
+ distance: Float = DistanceUnspecified
+): SwipeAnimation<*> {
+ var lastDistance = distance
+
+ fun distance(animation: SwipeAnimation<*>): Float {
+ if (lastDistance != DistanceUnspecified) {
+ return lastDistance
+ }
+
+ val absoluteDistance =
+ with(animation.contentTransition.transformationSpec.distance ?: DefaultSwipeDistance) {
+ layoutImpl.userActionDistanceScope.absoluteDistance(
+ layoutImpl.content(animation.fromContent).targetSize,
+ orientation,
+ )
+ }
+
+ if (absoluteDistance <= 0f) {
+ return DistanceUnspecified
+ }
+
+ val distance = if (isUpOrLeft) -absoluteDistance else absoluteDistance
+ lastDistance = distance
+ return distance
+ }
+
+ return createSwipeAnimation(
+ layoutImpl.state,
+ animationScope,
+ result,
+ isUpOrLeft,
+ orientation,
+ distance = ::distance,
+ contentForUserActions = { layoutImpl.contentForUserActions().key },
+ )
+}
+
+private fun createSwipeAnimation(
+ layoutState: MutableSceneTransitionLayoutStateImpl,
+ animationScope: CoroutineScope,
+ result: UserActionResult,
+ isUpOrLeft: Boolean,
+ orientation: Orientation,
+ distance: (SwipeAnimation<*>) -> Float,
+ contentForUserActions: () -> ContentKey,
+): SwipeAnimation<*> {
+ fun <T : ContentKey> swipeAnimation(fromContent: T, toContent: T): SwipeAnimation<T> {
return SwipeAnimation(
- layoutImpl = layoutImpl,
+ layoutState = layoutState,
+ animationScope = animationScope,
fromContent = fromContent,
toContent = toContent,
- userActionDistanceScope = layoutImpl.userActionDistanceScope,
orientation = orientation,
isUpOrLeft = isUpOrLeft,
requiresFullDistanceSwipe = result.requiresFullDistanceSwipe,
+ distance = distance,
)
}
- val layoutState = layoutImpl.state
return when (result) {
is UserActionResult.ChangeScene -> {
- val fromScene = layoutImpl.scene(layoutState.currentScene)
- val toScene = layoutImpl.scene(result.toScene)
- ChangeCurrentSceneSwipeTransition(
+ val fromScene = layoutState.currentScene
+ val toScene = result.toScene
+ ChangeSceneSwipeTransition(
layoutState = layoutState,
swipeAnimation = swipeAnimation(fromContent = fromScene, toContent = toScene),
key = result.transitionKey,
@@ -67,12 +132,12 @@
.swipeAnimation
}
is UserActionResult.ShowOverlay -> {
- val fromScene = layoutImpl.scene(layoutState.currentScene)
- val overlay = layoutImpl.overlay(result.overlay)
+ val fromScene = layoutState.currentScene
+ val overlay = result.overlay
ShowOrHideOverlaySwipeTransition(
layoutState = layoutState,
- _fromOrToScene = fromScene,
- _overlay = overlay,
+ fromOrToScene = fromScene,
+ overlay = overlay,
swipeAnimation = swipeAnimation(fromContent = fromScene, toContent = overlay),
key = result.transitionKey,
replacedTransition = null,
@@ -80,12 +145,12 @@
.swipeAnimation
}
is UserActionResult.HideOverlay -> {
- val toScene = layoutImpl.scene(layoutState.currentScene)
- val overlay = layoutImpl.overlay(result.overlay)
+ val toScene = layoutState.currentScene
+ val overlay = result.overlay
ShowOrHideOverlaySwipeTransition(
layoutState = layoutState,
- _fromOrToScene = toScene,
- _overlay = overlay,
+ fromOrToScene = toScene,
+ overlay = overlay,
swipeAnimation = swipeAnimation(fromContent = overlay, toContent = toScene),
key = result.transitionKey,
replacedTransition = null,
@@ -93,8 +158,14 @@
.swipeAnimation
}
is UserActionResult.ReplaceByOverlay -> {
- val fromOverlay = layoutImpl.contentForUserActions() as Overlay
- val toOverlay = layoutImpl.overlay(result.overlay)
+ val fromOverlay =
+ when (val contentForUserActions = contentForUserActions()) {
+ is SceneKey ->
+ error("ReplaceByOverlay can only be called when an overlay is shown")
+ is OverlayKey -> contentForUserActions
+ }
+
+ val toOverlay = result.overlay
ReplaceOverlaySwipeTransition(
layoutState = layoutState,
swipeAnimation =
@@ -109,9 +180,8 @@
internal fun createSwipeAnimation(old: SwipeAnimation<*>): SwipeAnimation<*> {
return when (val transition = old.contentTransition) {
- is TransitionState.Transition.ChangeCurrentScene -> {
- ChangeCurrentSceneSwipeTransition(transition as ChangeCurrentSceneSwipeTransition)
- .swipeAnimation
+ is TransitionState.Transition.ChangeScene -> {
+ ChangeSceneSwipeTransition(transition as ChangeSceneSwipeTransition).swipeAnimation
}
is TransitionState.Transition.ShowOrHideOverlay -> {
ShowOrHideOverlaySwipeTransition(transition as ShowOrHideOverlaySwipeTransition)
@@ -125,15 +195,15 @@
}
/** A helper class that contains the main logic for swipe transitions. */
-internal class SwipeAnimation<T : Content>(
- val layoutImpl: SceneTransitionLayoutImpl,
+internal class SwipeAnimation<T : ContentKey>(
+ val layoutState: MutableSceneTransitionLayoutStateImpl,
+ val animationScope: CoroutineScope,
val fromContent: T,
val toContent: T,
- private val userActionDistanceScope: UserActionDistanceScope,
override val orientation: Orientation,
override val isUpOrLeft: Boolean,
val requiresFullDistanceSwipe: Boolean,
- private var lastDistance: Float = DistanceUnspecified,
+ private val distance: (SwipeAnimation<T>) -> Float,
currentContent: T = fromContent,
dragOffset: Float = 0f,
) : TransitionState.HasOverscrollProperties {
@@ -147,7 +217,13 @@
// Important: If we are going to return early because distance is equal to 0, we should
// still make sure we read the offset before returning so that the calling code still
// subscribes to the offset value.
- val offset = offsetAnimation?.animatable?.value ?: dragOffset
+ val animatable = offsetAnimation?.animatable
+ val offset =
+ when {
+ animatable != null -> animatable.value
+ contentTransition.previewTransformationSpec != null -> 0f
+ else -> dragOffset
+ }
return computeProgress(offset)
}
@@ -172,6 +248,15 @@
return velocityInDistanceUnit / distance.absoluteValue
}
+ val previewProgress: Float
+ get() = computeProgress(dragOffset)
+
+ val previewProgressVelocity: Float
+ get() = 0f
+
+ val isInPreviewStage: Boolean
+ get() = contentTransition.previewTransformationSpec != null && currentContent == fromContent
+
override var bouncingContent: ContentKey? = null
/** The current offset caused by the drag gesture. */
@@ -183,17 +268,8 @@
val isUserInputOngoing: Boolean
get() = offsetAnimation == null
- override val overscrollScope: OverscrollScope =
- object : OverscrollScope {
- override val density: Float
- get() = layoutImpl.density.density
-
- override val fontScale: Float
- get() = layoutImpl.density.fontScale
-
- override val absoluteDistance: Float
- get() = distance().absoluteValue
- }
+ override val absoluteDistance: Float
+ get() = distance().absoluteValue
/** Whether [finish] was called on this animation. */
var isFinishing = false
@@ -202,14 +278,14 @@
constructor(
other: SwipeAnimation<T>
) : this(
- layoutImpl = other.layoutImpl,
+ layoutState = other.layoutState,
+ animationScope = other.animationScope,
fromContent = other.fromContent,
toContent = other.toContent,
- userActionDistanceScope = other.userActionDistanceScope,
orientation = other.orientation,
isUpOrLeft = other.isUpOrLeft,
requiresFullDistanceSwipe = other.requiresFullDistanceSwipe,
- lastDistance = other.lastDistance,
+ distance = other.distance,
currentContent = other.currentContent,
dragOffset = other.dragOffset,
)
@@ -222,27 +298,7 @@
* transition when the distance depends on the size or position of an element that is composed
* in the content we are going to.
*/
- fun distance(): Float {
- if (lastDistance != DistanceUnspecified) {
- return lastDistance
- }
-
- val absoluteDistance =
- with(contentTransition.transformationSpec.distance ?: DefaultSwipeDistance) {
- userActionDistanceScope.absoluteDistance(
- fromContent.targetSize,
- orientation,
- )
- }
-
- if (absoluteDistance <= 0f) {
- return DistanceUnspecified
- }
-
- val distance = if (isUpOrLeft) -absoluteDistance else absoluteDistance
- lastDistance = distance
- return distance
- }
+ fun distance(): Float = distance(this)
/** Ends any previous [offsetAnimation] and runs the new [animation]. */
private fun startOffsetAnimation(animation: () -> OffsetAnimation): OffsetAnimation {
@@ -262,10 +318,9 @@
}
fun animateOffset(
- // TODO(b/317063114) The CoroutineScope should be removed.
- coroutineScope: CoroutineScope,
initialVelocity: Float,
targetContent: T,
+ spec: SpringSpec<Float>? = null,
): OffsetAnimation {
val initialProgress = progress
// Skip the animation if we have already reached the target content and the overscroll does
@@ -304,12 +359,14 @@
}
return startOffsetAnimation {
- val animatable = Animatable(dragOffset, OffsetVisibilityThreshold)
+ val startProgress =
+ if (contentTransition.previewTransformationSpec != null) 0f else dragOffset
+ val animatable = Animatable(startProgress, OffsetVisibilityThreshold)
val isTargetGreater = targetOffset > animatable.value
val startedWhenOvercrollingTargetContent =
if (targetContent == fromContent) initialProgress < 0f else initialProgress > 1f
val job =
- coroutineScope
+ animationScope
// Important: We start atomically to make sure that we start the coroutine even
// if it is cancelled right after it is launched, so that snapToContent() is
// correctly called. Otherwise, this transition will never be stopped and we
@@ -325,8 +382,9 @@
try {
val swipeSpec =
- contentTransition.transformationSpec.swipeSpec
- ?: layoutImpl.state.transitions.defaultSwipeSpec
+ spec
+ ?: contentTransition.transformationSpec.swipeSpec
+ ?: layoutState.transitions.defaultSwipeSpec
animatable.animateTo(
targetValue = targetOffset,
animationSpec = swipeSpec,
@@ -349,7 +407,7 @@
}
if (isBouncing) {
- bouncingContent = targetContent.key
+ bouncingContent = targetContent
// Immediately stop this transition if we are bouncing on a
// content that does not bounce.
@@ -368,20 +426,19 @@
}
}
- private fun canChangeContent(targetContent: Content): Boolean {
- val layoutState = layoutImpl.state
+ private fun canChangeContent(targetContent: ContentKey): Boolean {
return when (val transition = contentTransition) {
- is TransitionState.Transition.ChangeCurrentScene ->
- layoutState.canChangeScene(targetContent.key as SceneKey)
+ is TransitionState.Transition.ChangeScene ->
+ layoutState.canChangeScene(targetContent as SceneKey)
is TransitionState.Transition.ShowOrHideOverlay -> {
- if (targetContent.key == transition.overlay) {
+ if (targetContent == transition.overlay) {
layoutState.canShowOverlay(transition.overlay)
} else {
layoutState.canHideOverlay(transition.overlay)
}
}
is TransitionState.Transition.ReplaceOverlay -> {
- val to = targetContent.key as OverlayKey
+ val to = targetContent as OverlayKey
val from =
if (to == transition.toOverlay) transition.fromOverlay else transition.toOverlay
layoutState.canReplaceOverlay(from, to)
@@ -392,7 +449,7 @@
private fun snapToContent(content: T) {
cancelOffsetAnimation()
check(currentContent == content)
- layoutImpl.state.finishTransition(contentTransition)
+ layoutState.finishTransition(contentTransition)
}
fun finish(): Job {
@@ -405,12 +462,7 @@
}
// Animate to the current content.
- val animation =
- animateOffset(
- coroutineScope = layoutImpl.coroutineScope,
- initialVelocity = 0f,
- targetContent = currentContent,
- )
+ val animation = animateOffset(initialVelocity = 0f, targetContent = currentContent)
check(offsetAnimation == animation)
return animation.job
}
@@ -436,21 +488,21 @@
}
}
-private class ChangeCurrentSceneSwipeTransition(
+private class ChangeSceneSwipeTransition(
val layoutState: MutableSceneTransitionLayoutStateImpl,
- val swipeAnimation: SwipeAnimation<Scene>,
+ val swipeAnimation: SwipeAnimation<SceneKey>,
override val key: TransitionKey?,
- replacedTransition: ChangeCurrentSceneSwipeTransition?,
+ replacedTransition: ChangeSceneSwipeTransition?,
) :
- TransitionState.Transition.ChangeCurrentScene(
- swipeAnimation.fromContent.key,
- swipeAnimation.toContent.key,
+ TransitionState.Transition.ChangeScene(
+ swipeAnimation.fromContent,
+ swipeAnimation.toContent,
replacedTransition,
),
TransitionState.HasOverscrollProperties by swipeAnimation {
constructor(
- other: ChangeCurrentSceneSwipeTransition
+ other: ChangeSceneSwipeTransition
) : this(
layoutState = other.layoutState,
swipeAnimation = SwipeAnimation(other.swipeAnimation),
@@ -463,7 +515,7 @@
}
override val currentScene: SceneKey
- get() = swipeAnimation.currentContent.key
+ get() = swipeAnimation.currentContent
override val progress: Float
get() = swipeAnimation.progress
@@ -471,6 +523,15 @@
override val progressVelocity: Float
get() = swipeAnimation.progressVelocity
+ override val previewProgress: Float
+ get() = swipeAnimation.previewProgress
+
+ override val previewProgressVelocity: Float
+ get() = swipeAnimation.previewProgressVelocity
+
+ override val isInPreviewStage: Boolean
+ get() = swipeAnimation.isInPreviewStage
+
override val isInitiatedByUserInput: Boolean = true
override val isUserInputOngoing: Boolean
@@ -481,17 +542,17 @@
private class ShowOrHideOverlaySwipeTransition(
val layoutState: MutableSceneTransitionLayoutStateImpl,
- val swipeAnimation: SwipeAnimation<Content>,
- val _overlay: Overlay,
- val _fromOrToScene: Scene,
+ val swipeAnimation: SwipeAnimation<ContentKey>,
+ overlay: OverlayKey,
+ fromOrToScene: SceneKey,
override val key: TransitionKey?,
replacedTransition: ShowOrHideOverlaySwipeTransition?,
) :
TransitionState.Transition.ShowOrHideOverlay(
- _overlay.key,
- _fromOrToScene.key,
- swipeAnimation.fromContent.key,
- swipeAnimation.toContent.key,
+ overlay,
+ fromOrToScene,
+ swipeAnimation.fromContent,
+ swipeAnimation.toContent,
replacedTransition,
),
TransitionState.HasOverscrollProperties by swipeAnimation {
@@ -500,8 +561,8 @@
) : this(
layoutState = other.layoutState,
swipeAnimation = SwipeAnimation(other.swipeAnimation),
- _overlay = other._overlay,
- _fromOrToScene = other._fromOrToScene,
+ overlay = other.overlay,
+ fromOrToScene = other.fromOrToScene,
key = other.key,
replacedTransition = other,
)
@@ -511,7 +572,7 @@
}
override val isEffectivelyShown: Boolean
- get() = swipeAnimation.currentContent == _overlay
+ get() = swipeAnimation.currentContent == overlay
override val progress: Float
get() = swipeAnimation.progress
@@ -519,6 +580,15 @@
override val progressVelocity: Float
get() = swipeAnimation.progressVelocity
+ override val previewProgress: Float
+ get() = swipeAnimation.previewProgress
+
+ override val previewProgressVelocity: Float
+ get() = swipeAnimation.previewProgressVelocity
+
+ override val isInPreviewStage: Boolean
+ get() = swipeAnimation.isInPreviewStage
+
override val isInitiatedByUserInput: Boolean = true
override val isUserInputOngoing: Boolean
@@ -529,13 +599,13 @@
private class ReplaceOverlaySwipeTransition(
val layoutState: MutableSceneTransitionLayoutStateImpl,
- val swipeAnimation: SwipeAnimation<Overlay>,
+ val swipeAnimation: SwipeAnimation<OverlayKey>,
override val key: TransitionKey?,
replacedTransition: ReplaceOverlaySwipeTransition?,
) :
TransitionState.Transition.ReplaceOverlay(
- swipeAnimation.fromContent.key,
- swipeAnimation.toContent.key,
+ swipeAnimation.fromContent,
+ swipeAnimation.toContent,
replacedTransition,
),
TransitionState.HasOverscrollProperties by swipeAnimation {
@@ -553,7 +623,7 @@
}
override val effectivelyShownOverlay: OverlayKey
- get() = swipeAnimation.currentContent.key
+ get() = swipeAnimation.currentContent
override val progress: Float
get() = swipeAnimation.progress
@@ -561,6 +631,15 @@
override val progressVelocity: Float
get() = swipeAnimation.progressVelocity
+ override val previewProgress: Float
+ get() = swipeAnimation.previewProgress
+
+ override val previewProgressVelocity: Float
+ get() = swipeAnimation.previewProgressVelocity
+
+ override val isInPreviewStage: Boolean
+ get() = swipeAnimation.isInPreviewStage
+
override val isInitiatedByUserInput: Boolean = true
override val isUserInputOngoing: Boolean
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index fdb019f..0cd8c1a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -26,7 +26,6 @@
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.OverlayKey
-import com.android.compose.animation.scene.OverscrollScope
import com.android.compose.animation.scene.OverscrollSpecImpl
import com.android.compose.animation.scene.ProgressVisibilityThreshold
import com.android.compose.animation.scene.SceneKey
@@ -75,7 +74,7 @@
val replacedTransition: Transition? = null,
) : TransitionState {
/** A transition animating between [fromScene] and [toScene]. */
- abstract class ChangeCurrentScene(
+ abstract class ChangeScene(
/** The scene this transition is starting from. Can't be the same as toScene */
val fromScene: SceneKey,
@@ -386,10 +385,10 @@
val orientation: Orientation
/**
- * Scope which can be used in the Overscroll DSL to define a transformation based on the
- * distance between [Transition.fromContent] and [Transition.toContent].
+ * Return the absolute distance between fromScene and toScene, if available, otherwise
+ * [DistanceUnspecified].
*/
- val overscrollScope: OverscrollScope
+ val absoluteDistance: Float
/**
* The content (scene or overlay) around which the transition is currently bouncing. When
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index 59bca50..8f84586 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene.transformation
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ContentKey
@@ -53,6 +54,8 @@
val x: OverscrollScope.() -> Float = { 0f },
val y: OverscrollScope.() -> Float = { 0f },
) : PropertyTransformation<Offset> {
+ private val cachedOverscrollScope = CachedOverscrollScope()
+
override fun transform(
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
@@ -65,10 +68,47 @@
// OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume
// that this method was invoked after performing this check.
val overscrollProperties = transition as TransitionState.HasOverscrollProperties
+ val overscrollScope =
+ cachedOverscrollScope.getFromCacheOrCompute(layoutImpl.density, overscrollProperties)
return Offset(
- x = value.x + overscrollProperties.overscrollScope.x(),
- y = value.y + overscrollProperties.overscrollScope.y(),
+ x = value.x + overscrollScope.x(),
+ y = value.y + overscrollScope.y(),
)
}
}
+
+/**
+ * A helper class to cache a [OverscrollScope] given a [Density] and
+ * [TransitionState.HasOverscrollProperties]. This helps avoid recreating a scope every frame
+ * whenever an overscroll transition is computed.
+ */
+private class CachedOverscrollScope() {
+ private var previousScope: OverscrollScope? = null
+ private var previousDensity: Density? = null
+ private var previousOverscrollProperties: TransitionState.HasOverscrollProperties? = null
+
+ fun getFromCacheOrCompute(
+ density: Density,
+ overscrollProperties: TransitionState.HasOverscrollProperties,
+ ): OverscrollScope {
+ if (
+ previousScope == null ||
+ density != previousDensity ||
+ previousOverscrollProperties != overscrollProperties
+ ) {
+ val scope =
+ object : OverscrollScope, Density by density {
+ override val absoluteDistance: Float
+ get() = overscrollProperties.absoluteDistance
+ }
+
+ previousScope = scope
+ previousDensity = density
+ previousOverscrollProperties = overscrollProperties
+ return scope
+ }
+
+ return checkNotNull(previousScope)
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
index 59ddb13..564d4b3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -27,7 +27,7 @@
fromScene: SceneKey,
toScene: SceneKey,
override val key: TransitionKey? = null,
-) : TransitionState.Transition.ChangeCurrentScene(fromScene, toScene) {
+) : TransitionState.Transition.ChangeScene(fromScene, toScene) {
override val currentScene: SceneKey
get() {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
index f4e60a2..3f6bd2c 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
@@ -69,7 +69,7 @@
interruptionHandler =
object : InterruptionHandler {
override fun onInterruption(
- interrupted: TransitionState.Transition.ChangeCurrentScene,
+ interrupted: TransitionState.Transition.ChangeScene,
newTargetScene: SceneKey
): InterruptionResult {
return InterruptionResult(
@@ -104,7 +104,7 @@
interruptionHandler =
object : InterruptionHandler {
override fun onInterruption(
- interrupted: TransitionState.Transition.ChangeCurrentScene,
+ interrupted: TransitionState.Transition.ChangeScene,
newTargetScene: SceneKey
): InterruptionResult {
return InterruptionResult(
@@ -198,7 +198,7 @@
companion object {
val FromToCurrentTriple =
Correspondence.transforming(
- { transition: TransitionState.Transition.ChangeCurrentScene? ->
+ { transition: TransitionState.Transition.ChangeScene? ->
Triple(transition?.fromScene, transition?.toScene, transition?.currentScene)
},
"(from, to, current) triple"
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index a549d03..e4879d9 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -163,7 +163,7 @@
fromContentZIndex: Float,
toContentZIndex: Float
): ContentKey {
- transition as TransitionState.Transition.ChangeCurrentScene
+ transition as TransitionState.Transition.ChangeScene
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneB)
assertThat(fromContentZIndex).isEqualTo(0)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index 00c7588..c5b6cdf 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -20,10 +20,16 @@
import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestOverlays.OverlayA
+import com.android.compose.animation.scene.TestOverlays.OverlayB
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
@@ -198,6 +204,42 @@
assertThat(canChangeSceneCalled).isFalse()
}
+ @Test
+ fun backDismissesOverlayWithHighestZIndexByDefault() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ SceneA,
+ initialOverlays = setOf(OverlayA, OverlayB)
+ )
+ }
+
+ rule.setContent {
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ scene(SceneA) { Box(Modifier.fillMaxSize()) }
+ overlay(OverlayA) { Box(Modifier.fillMaxSize()) }
+ overlay(OverlayB) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ // Initial state.
+ rule.onNode(hasTestTag(SceneA.testTag)).assertIsDisplayed()
+ rule.onNode(hasTestTag(OverlayA.testTag)).assertIsDisplayed()
+ rule.onNode(hasTestTag(OverlayB.testTag)).assertIsDisplayed()
+
+ // Press back. This should hide overlay B because it has a higher zIndex than overlay A.
+ rule.runOnUiThread { rule.activity.onBackPressedDispatcher.onBackPressed() }
+ rule.onNode(hasTestTag(SceneA.testTag)).assertIsDisplayed()
+ rule.onNode(hasTestTag(OverlayA.testTag)).assertIsDisplayed()
+ rule.onNode(hasTestTag(OverlayB.testTag)).assertDoesNotExist()
+
+ // Press back again. This should hide overlay A.
+ rule.runOnUiThread { rule.activity.onBackPressedDispatcher.onBackPressed() }
+ rule.onNode(hasTestTag(SceneA.testTag)).assertIsDisplayed()
+ rule.onNode(hasTestTag(OverlayA.testTag)).assertDoesNotExist()
+ rule.onNode(hasTestTag(OverlayB.testTag)).assertDoesNotExist()
+ }
+
private fun backEvent(progress: Float = 0f): BackEventCompat {
return BackEventCompat(
touchX = 0f,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
index 1f7fe37..467031a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
@@ -42,9 +42,9 @@
orientation: Orientation = Orientation.Horizontal,
onFinish: ((TransitionState.Transition) -> Job)? = null,
replacedTransition: TransitionState.Transition? = null,
-): TransitionState.Transition.ChangeCurrentScene {
+): TransitionState.Transition.ChangeScene {
return object :
- TransitionState.Transition.ChangeCurrentScene(from, to, replacedTransition),
+ TransitionState.Transition.ChangeScene(from, to, replacedTransition),
TransitionState.HasOverscrollProperties {
override val currentScene: SceneKey
get() = current()
@@ -69,12 +69,7 @@
override val isUpOrLeft: Boolean = isUpOrLeft
override val bouncingContent: ContentKey? = bouncingContent
override val orientation: Orientation = orientation
- override val overscrollScope: OverscrollScope =
- object : OverscrollScope {
- override val density: Float = 1f
- override val fontScale: Float = 1f
- override val absoluteDistance = 0f
- }
+ override val absoluteDistance = 0f
override fun finish(): Job {
val onFinish =
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
index 3fb5708..44e0ba5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
@@ -32,8 +32,8 @@
return Truth.assertAbout(TransitionStateSubject.transitionStates()).that(state)
}
-/** Assert on a [TransitionState.Transition.ChangeCurrentScene]. */
-fun assertThat(transition: TransitionState.Transition.ChangeCurrentScene): SceneTransitionSubject {
+/** Assert on a [TransitionState.Transition.ChangeScene]. */
+fun assertThat(transition: TransitionState.Transition.ChangeScene): SceneTransitionSubject {
return Truth.assertAbout(SceneTransitionSubject.sceneTransitions()).that(transition)
}
@@ -74,14 +74,14 @@
return actual as TransitionState.Idle
}
- fun isSceneTransition(): TransitionState.Transition.ChangeCurrentScene {
- if (actual !is TransitionState.Transition.ChangeCurrentScene) {
+ fun isSceneTransition(): TransitionState.Transition.ChangeScene {
+ if (actual !is TransitionState.Transition.ChangeScene) {
failWithActual(
simpleFact("expected to be TransitionState.Transition.ChangeCurrentScene")
)
}
- return actual as TransitionState.Transition.ChangeCurrentScene
+ return actual as TransitionState.Transition.ChangeScene
}
fun isShowOrHideOverlayTransition(): TransitionState.Transition.ShowOrHideOverlay {
@@ -183,8 +183,8 @@
class SceneTransitionSubject
private constructor(
metadata: FailureMetadata,
- actual: TransitionState.Transition.ChangeCurrentScene,
-) : BaseTransitionSubject<TransitionState.Transition.ChangeCurrentScene>(metadata, actual) {
+ actual: TransitionState.Transition.ChangeScene,
+) : BaseTransitionSubject<TransitionState.Transition.ChangeScene>(metadata, actual) {
fun hasFromScene(sceneKey: SceneKey) {
check("fromScene").that(actual.fromScene).isEqualTo(sceneKey)
}
@@ -195,7 +195,7 @@
companion object {
fun sceneTransitions() =
- Factory { metadata, actual: TransitionState.Transition.ChangeCurrentScene ->
+ Factory { metadata, actual: TransitionState.Transition.ChangeScene ->
SceneTransitionSubject(metadata, actual)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
index baef620..a36b0bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
@@ -218,4 +218,60 @@
assertThat(underTest.getMessageToShow(startWindow)?.msgId)
.isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
}
+
+ @Test
+ fun messageMustMeetThreshold() {
+ underTest =
+ FaceHelpMessageDebouncer(
+ window = window,
+ startWindow = 0,
+ shownFaceMessageFrequencyBoost = 0,
+ threshold = .8f,
+ )
+
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ 0
+ )
+ )
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ 0
+ )
+ )
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+ "tooBright",
+ 0
+ )
+ )
+
+ // although tooClose message is the majority, it doesn't meet the 80% threshold
+ assertThat(underTest.getMessageToShow(startWindow)).isNull()
+
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ 0
+ )
+ )
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ 0
+ )
+ )
+
+ // message shows once it meets the threshold
+ assertThat(underTest.getMessageToShow(startWindow)?.msgId)
+ .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
+ assertThat(underTest.getMessageToShow(startWindow)?.msg).isEqualTo("tooClose")
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
index b31f6f5..add7a7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
@@ -16,11 +16,15 @@
package com.android.systemui.biometrics
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.keyguard.logging.BiometricMessageDeferralLogger
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -31,14 +35,29 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@android.platform.test.annotations.EnabledOnRavenwood
-class FaceHelpMessageDeferralTest : SysuiTestCase() {
+class FaceHelpMessageDeferralTest(flags: FlagsParameterization) : SysuiTestCase() {
val threshold = .75f
@Mock lateinit var logger: BiometricMessageDeferralLogger
@Mock lateinit var dumpManager: DumpManager
+ val systemClock = FakeSystemClock()
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE)
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Before
fun setUp() {
@@ -111,10 +130,11 @@
}
@Test
+ @DisableFlags(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE)
fun testReturnsMostFrequentDeferredMessage() {
val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
- // WHEN there's 80%of the messages are msgId=1 and 20% is msgId=2
+ // WHEN there's 80% of the messages are msgId=1 and 20% is msgId=2
biometricMessageDeferral.processFrame(1)
biometricMessageDeferral.processFrame(1)
biometricMessageDeferral.processFrame(1)
@@ -124,7 +144,41 @@
biometricMessageDeferral.processFrame(2)
biometricMessageDeferral.updateMessage(2, "msgId-2")
- // THEN the most frequent deferred message is that meets the threshold is returned
+ // THEN the most frequent deferred message that meets the threshold is returned
+ assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE)
+ fun testReturnsMostFrequentDeferredMessage_onlyAnalyzesLastNWindow() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+ // WHEN there's 80% of the messages are msgId=1 and 20% is msgId=2, but the last
+ // N window only contains messages with msgId=2
+ repeat(80) { biometricMessageDeferral.processFrame(1) }
+ biometricMessageDeferral.updateMessage(1, "msgId-1")
+ systemClock.setElapsedRealtime(systemClock.elapsedRealtime() + 501L)
+ repeat(20) { biometricMessageDeferral.processFrame(2) }
+ biometricMessageDeferral.updateMessage(2, "msgId-2")
+
+ // THEN the most frequent deferred message in the last N window (500L) is returned
+ assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE)
+ fun testReturnsMostFrequentDeferredMessage_analyzesAllFrames() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+ // WHEN there's 80% of the messages are msgId=1 and 20% is msgId=2, but the last
+ // N window only contains messages with msgId=2
+ repeat(80) { biometricMessageDeferral.processFrame(1) }
+ biometricMessageDeferral.updateMessage(1, "msgId-1")
+ systemClock.setElapsedRealtime(systemClock.elapsedRealtime() + 501L)
+ repeat(20) { biometricMessageDeferral.processFrame(2) }
+ biometricMessageDeferral.updateMessage(2, "msgId-2")
+
+ // THEN the most frequent deferred message is returned
assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
}
@@ -213,14 +267,17 @@
private fun createMsgDeferral(
messagesToDefer: Set<Int>,
acquiredInfoToIgnore: Set<Int> = emptySet(),
+ windowToAnalyzeLastNFrames: Long = 500L,
): BiometricMessageDeferral {
return BiometricMessageDeferral(
- messagesToDefer,
- acquiredInfoToIgnore,
- threshold,
- logger,
- dumpManager,
- "0",
+ messagesToDefer = messagesToDefer,
+ acquiredInfoToIgnore = acquiredInfoToIgnore,
+ threshold = threshold,
+ windowToAnalyzeLastNFrames = windowToAnalyzeLastNFrames,
+ logBuffer = logger,
+ dumpManager = dumpManager,
+ id = "0",
+ systemClock = { systemClock },
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
index 76920e4..3b0057d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -81,6 +81,7 @@
@Test
fun startDreamWhenTransitioningToHub() =
testScope.runTest {
+ keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt
index d251585..1a426d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
@@ -67,6 +68,7 @@
smartspaceController,
fakeExecutor,
systemClock,
+ logcatLogBuffer("CommunalSmartspaceRepositoryImplTest"),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 1d03ced..99fcbb8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -352,7 +352,7 @@
smartspaceRepository.setTimers(targets)
- val smartspaceContent by collectLastValue(underTest.getOngoingContent(true))
+ val smartspaceContent by collectLastValue(underTest.ongoingContent)
assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
for (index in 0 until totalTargets) {
assertThat(smartspaceContent?.get(index)?.size).isEqualTo(expectedSizes[index])
@@ -368,7 +368,7 @@
// Media is playing.
mediaRepository.mediaActive()
- val umoContent by collectLastValue(underTest.getOngoingContent(true))
+ val umoContent by collectLastValue(underTest.ongoingContent)
assertThat(umoContent?.size).isEqualTo(1)
assertThat(umoContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java)
@@ -376,20 +376,6 @@
}
@Test
- fun umo_mediaPlaying_doNotShowUmo() =
- testScope.run {
- // Tutorial completed.
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-
- // Media is playing.
- mediaRepository.mediaActive()
-
- val umoContent by collectLastValue(underTest.getOngoingContent(false))
-
- assertThat(umoContent?.size).isEqualTo(0)
- }
-
- @Test
fun ongoing_shouldOrderAndSizeByTimestamp() =
testScope.runTest {
// Keyguard showing, and tutorial completed.
@@ -412,7 +398,7 @@
val timer3 = smartspaceTimer("timer3", timestamp = 4L)
smartspaceRepository.setTimers(listOf(timer1, timer2, timer3))
- val ongoingContent by collectLastValue(underTest.getOngoingContent(true))
+ val ongoingContent by collectLastValue(underTest.ongoingContent)
assertThat(ongoingContent?.size).isEqualTo(4)
assertThat(ongoingContent?.get(0)?.key)
.isEqualTo(CommunalContentModel.KEY.smartspace("timer3"))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 8218178..179ba22 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -20,9 +20,7 @@
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Intent
-import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
import android.content.pm.UserInfo
import android.provider.Settings
import android.view.accessibility.AccessibilityEvent
@@ -72,7 +70,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.never
@@ -141,6 +138,7 @@
context,
accessibilityManager,
packageManager,
+ WIDGET_PICKER_PACKAGE_NAME,
)
}
@@ -259,18 +257,8 @@
@Test
fun onOpenWidgetPicker_launchesWidgetPickerActivity() {
testScope.runTest {
- whenever(packageManager.resolveActivity(any(), anyInt())).then {
- ResolveInfo().apply {
- activityInfo = ActivityInfo().apply { packageName = WIDGET_PICKER_PACKAGE_NAME }
- }
- }
-
val success =
- underTest.onOpenWidgetPicker(
- testableResources.resources,
- packageManager,
- activityResultLauncher
- )
+ underTest.onOpenWidgetPicker(testableResources.resources, activityResultLauncher)
verify(activityResultLauncher).launch(any())
assertTrue(success)
@@ -278,38 +266,14 @@
}
@Test
- fun onOpenWidgetPicker_launcherActivityNotResolved_doesNotLaunchWidgetPickerActivity() {
- testScope.runTest {
- whenever(packageManager.resolveActivity(any(), anyInt())).thenReturn(null)
-
- val success =
- underTest.onOpenWidgetPicker(
- testableResources.resources,
- packageManager,
- activityResultLauncher
- )
-
- verify(activityResultLauncher, never()).launch(any())
- assertFalse(success)
- }
- }
-
- @Test
fun onOpenWidgetPicker_activityLaunchThrowsException_failure() {
testScope.runTest {
- whenever(packageManager.resolveActivity(any(), anyInt())).then {
- ResolveInfo().apply {
- activityInfo = ActivityInfo().apply { packageName = WIDGET_PICKER_PACKAGE_NAME }
- }
- }
-
whenever(activityResultLauncher.launch(any()))
.thenThrow(ActivityNotFoundException::class.java)
val success =
underTest.onOpenWidgetPicker(
testableResources.resources,
- packageManager,
activityResultLauncher,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index f6f5bc0..780d357 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -97,6 +97,7 @@
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
@@ -757,7 +758,7 @@
// updateViewVisibility is called when the flow is collected.
assertThat(communalContent).isNotNull()
- verify(mediaHost).updateViewVisibility()
+ verify(mediaHost, atLeastOnce()).updateViewVisibility()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index eda9039..d21a827 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.dreams
+import android.app.WindowConfiguration
import android.content.ComponentName
import android.content.Intent
import android.os.RemoteException
@@ -65,6 +66,8 @@
import com.android.systemui.keyguard.gesture.domain.gestureInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
+import com.android.systemui.navigationbar.gestural.domain.TaskInfo
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
import com.android.systemui.testKosmos
import com.android.systemui.touch.TouchInsetManager
import com.android.systemui.util.concurrency.FakeExecutor
@@ -83,13 +86,17 @@
import org.mockito.Mockito
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.isNull
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
+import org.mockito.kotlin.firstValue
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@@ -315,6 +322,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -332,6 +340,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -351,6 +360,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -373,6 +383,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -391,6 +402,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -406,6 +418,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
true /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -413,6 +426,47 @@
}
@Test
+ fun testDeferredResetRespondsToAnimationEnd() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*isPreview*/,
+ true /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+
+ whenever(mStateController.areExitAnimationsRunning()).thenReturn(true)
+ clearInvocations(mStateController, mTouchMonitor)
+
+ // Starting a dream will cause it to end first.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*isPreview*/,
+ true /*shouldShowComplication*/
+ )
+
+ mMainExecutor.runAllReady()
+
+ verifyZeroInteractions(mTouchMonitor)
+
+ val captor = ArgumentCaptor.forClass(DreamOverlayStateController.Callback::class.java)
+ verify(mStateController).addCallback(captor.capture())
+
+ whenever(mStateController.areExitAnimationsRunning()).thenReturn(false)
+
+ captor.firstValue.onStateChanged()
+
+ // Should only be called once since it should be null during the second reset.
+ verify(mTouchMonitor).destroy()
+ }
+
+ @Test
fun testLowLightSetByStartDream() {
val client = client
@@ -421,6 +475,7 @@
mWindowParams,
mDreamOverlayCallback,
LOW_LIGHT_COMPONENT.flattenToString(),
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -437,6 +492,7 @@
mWindowParams,
mDreamOverlayCallback,
HOME_CONTROL_PANEL_DREAM_COMPONENT.flattenToString(),
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -453,6 +509,7 @@
mWindowParams,
mDreamOverlayCallback,
LOW_LIGHT_COMPONENT.flattenToString(),
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -487,6 +544,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
// Immediately end the dream.
@@ -518,6 +576,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -537,6 +596,7 @@
mWindowParams,
mDreamOverlayCallback,
LOW_LIGHT_COMPONENT.flattenToString(),
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -588,6 +648,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -611,6 +672,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -631,6 +693,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
true /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -660,6 +723,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
true /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -683,6 +747,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -708,6 +773,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -733,6 +799,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
// Set communal available, verify that overlay callback is informed.
@@ -761,6 +828,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
true /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -781,6 +849,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
true /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -800,6 +869,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
true /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -823,6 +893,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -853,6 +924,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
testScope.runCurrent()
@@ -869,6 +941,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -892,6 +965,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -923,6 +997,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -954,6 +1029,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -989,6 +1065,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
@@ -1015,7 +1092,7 @@
}
@Test
- fun testDreamActivityGesturesBlockedOnStart() {
+ fun testDreamActivityGesturesBlockedWhenDreaming() {
val client = client
// Inform the overlay service of dream starting.
@@ -1023,36 +1100,42 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
- val captor = argumentCaptor<ComponentName>()
+
+ val matcherCaptor = argumentCaptor<TaskMatcher>()
verify(gestureInteractor)
- .addGestureBlockedActivity(captor.capture(), eq(GestureInteractor.Scope.Global))
- assertThat(captor.firstValue.packageName)
- .isEqualTo(ComponentName.unflattenFromString(DREAM_COMPONENT)?.packageName)
- }
+ .addGestureBlockedMatcher(matcherCaptor.capture(), eq(GestureInteractor.Scope.Global))
+ val matcher = matcherCaptor.firstValue
- @Test
- fun testDreamActivityGesturesUnblockedOnEnd() {
- val client = client
-
- // Inform the overlay service of dream starting.
- client.startDream(
- mWindowParams,
- mDreamOverlayCallback,
- DREAM_COMPONENT,
- false /*shouldShowComplication*/
- )
- mMainExecutor.runAllReady()
+ val dreamTaskInfo = TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
+ assertThat(matcher.matches(dreamTaskInfo)).isTrue()
client.endDream()
mMainExecutor.runAllReady()
- val captor = argumentCaptor<ComponentName>()
+
verify(gestureInteractor)
- .removeGestureBlockedActivity(captor.capture(), eq(GestureInteractor.Scope.Global))
- assertThat(captor.firstValue.packageName)
- .isEqualTo(ComponentName.unflattenFromString(DREAM_COMPONENT)?.packageName)
+ .removeGestureBlockedMatcher(eq(matcher), eq(GestureInteractor.Scope.Global))
+ }
+
+ @Test
+ fun testDreamActivityGesturesNotBlockedWhenPreview() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ true /*isPreview*/,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+
+ verify(gestureInteractor, never())
+ .addGestureBlockedMatcher(any(), eq(GestureInteractor.Scope.Global))
}
@Test
@@ -1077,6 +1160,7 @@
mWindowParams,
mDreamOverlayCallback,
DREAM_COMPONENT,
+ false /*isPreview*/,
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt
index 91d37cf..a764256 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt
@@ -16,12 +16,14 @@
package com.android.systemui.gesture.data
+import android.app.WindowConfiguration
import android.content.ComponentName
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.navigationbar.gestural.data.respository.GestureRepositoryImpl
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -40,14 +42,36 @@
@Test
fun addRemoveComponentToBlock_updatesBlockedComponentSet() =
testScope.runTest {
- val component = mock<ComponentName>()
+ val matcher = TaskMatcher.TopActivityComponent(mock<ComponentName>())
- underTest.addGestureBlockedActivity(component)
- val addedBlockedComponents by collectLastValue(underTest.gestureBlockedActivities)
- assertThat(addedBlockedComponents).contains(component)
+ kotlin.run {
+ underTest.addGestureBlockedMatcher(matcher)
+ val blockedMatchers by collectLastValue(underTest.gestureBlockedMatchers)
+ assertThat(blockedMatchers).contains(matcher)
+ }
- underTest.removeGestureBlockedActivity(component)
- val removedBlockedComponents by collectLastValue(underTest.gestureBlockedActivities)
- assertThat(removedBlockedComponents).isEmpty()
+ kotlin.run {
+ underTest.removeGestureBlockedMatcher(matcher)
+ val blockedMatchers by collectLastValue(underTest.gestureBlockedMatchers)
+ assertThat(blockedMatchers).doesNotContain(matcher)
+ }
+ }
+
+ @Test
+ fun addRemoveActivityTypeToBlock_updatesBlockedActivityTypesSet() =
+ testScope.runTest {
+ val matcher = TaskMatcher.TopActivityType(WindowConfiguration.ACTIVITY_TYPE_STANDARD)
+
+ kotlin.run {
+ underTest.addGestureBlockedMatcher(matcher)
+ val blockedMatchers by collectLastValue(underTest.gestureBlockedMatchers)
+ assertThat(blockedMatchers).contains(matcher)
+ }
+
+ kotlin.run {
+ underTest.removeGestureBlockedMatcher(matcher)
+ val blockedMatchers by collectLastValue(underTest.gestureBlockedMatchers)
+ assertThat(blockedMatchers).doesNotContain(matcher)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt
index 6395448..0ce0d93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.navigationbar.gestural.data.gestureRepository
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
import com.android.systemui.shared.system.activityManagerWrapper
import com.android.systemui.shared.system.taskStackChangeListeners
import com.android.systemui.testKosmos
@@ -76,13 +77,16 @@
fun addBlockedActivity_testCombination() =
testScope.runTest {
val globalComponent = mock<ComponentName>()
- repository.addGestureBlockedActivity(globalComponent)
+ repository.addGestureBlockedMatcher(TaskMatcher.TopActivityComponent(globalComponent))
val localComponent = mock<ComponentName>()
val blocked by collectLastValue(underTest.topActivityBlocked)
- underTest.addGestureBlockedActivity(localComponent, GestureInteractor.Scope.Local)
+ underTest.addGestureBlockedMatcher(
+ TaskMatcher.TopActivityComponent(localComponent),
+ GestureInteractor.Scope.Local
+ )
assertThat(blocked).isFalse()
@@ -95,7 +99,7 @@
fun initialization_testEmit() =
testScope.runTest {
val globalComponent = mock<ComponentName>()
- repository.addGestureBlockedActivity(globalComponent)
+ repository.addGestureBlockedMatcher(TaskMatcher.TopActivityComponent(globalComponent))
setTopActivity(globalComponent)
val interactor = createInteractor()
@@ -114,10 +118,36 @@
val localComponent = mock<ComponentName>()
- interactor1.addGestureBlockedActivity(localComponent, GestureInteractor.Scope.Local)
+ interactor1.addGestureBlockedMatcher(
+ TaskMatcher.TopActivityComponent(localComponent),
+ GestureInteractor.Scope.Local
+ )
setTopActivity(localComponent)
assertThat(interactor1Blocked).isTrue()
assertThat(interactor2Blocked).isFalse()
}
+
+ @Test
+ fun matchingBlockers_separatelyManaged() =
+ testScope.runTest {
+ val interactor = createInteractor()
+ val interactorBlocked by collectLastValue(interactor.topActivityBlocked)
+
+ val localComponent = mock<ComponentName>()
+
+ val matcher1 = TaskMatcher.TopActivityComponent(localComponent)
+ val matcher2 = TaskMatcher.TopActivityComponent(localComponent)
+
+ interactor.addGestureBlockedMatcher(matcher1, GestureInteractor.Scope.Local)
+ interactor.addGestureBlockedMatcher(matcher2, GestureInteractor.Scope.Local)
+ setTopActivity(localComponent)
+ assertThat(interactorBlocked).isTrue()
+
+ interactor.removeGestureBlockedMatcher(matcher1, GestureInteractor.Scope.Local)
+ assertThat(interactorBlocked).isTrue()
+
+ interactor.removeGestureBlockedMatcher(matcher2, GestureInteractor.Scope.Local)
+ assertThat(interactorBlocked).isFalse()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/TaskMatcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/TaskMatcherTest.kt
new file mode 100644
index 0000000..a246270
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/TaskMatcherTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.gesture.domain
+
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.navigationbar.gestural.domain.TaskInfo
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class TaskMatcherTest : SysuiTestCase() {
+ @Test
+ fun activityMatcher_matchesComponentName() {
+ val componentName = ComponentName.unflattenFromString("com.foo/.bar")!!
+ val matcher = TaskMatcher.TopActivityComponent(componentName)
+
+ val taskInfo = TaskInfo(componentName, WindowConfiguration.ACTIVITY_TYPE_STANDARD)
+ assertThat(matcher.matches(taskInfo)).isTrue()
+ }
+
+ @Test
+ fun activityMatcher_doesNotMatchComponentName() {
+ val componentName = ComponentName.unflattenFromString("com.foo/.bar")!!
+ val matcher = TaskMatcher.TopActivityComponent(componentName)
+
+ val taskInfo =
+ TaskInfo(
+ ComponentName.unflattenFromString("com.bar/.baz"),
+ WindowConfiguration.ACTIVITY_TYPE_STANDARD
+ )
+ assertThat(matcher.matches(taskInfo)).isFalse()
+ }
+
+ @Test
+ fun activityMatcher_matchesActivityType() {
+ val activityType = WindowConfiguration.ACTIVITY_TYPE_HOME
+ val matcher = TaskMatcher.TopActivityType(activityType)
+
+ val taskInfo = TaskInfo(mock<ComponentName>(), activityType)
+ assertThat(matcher.matches(taskInfo)).isTrue()
+ }
+
+ @Test
+ fun activityMatcher_doesNotMatchEmptyActivityType() {
+ val activityType = WindowConfiguration.ACTIVITY_TYPE_HOME
+ val matcher = TaskMatcher.TopActivityType(activityType)
+
+ val taskInfo = TaskInfo(null, activityType)
+ assertThat(matcher.matches(taskInfo)).isFalse()
+ }
+
+ @Test
+ fun activityMatcher_doesNotMatchActivityType() {
+ val activityType = WindowConfiguration.ACTIVITY_TYPE_HOME
+ val matcher = TaskMatcher.TopActivityType(activityType)
+
+ val taskInfo = TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_STANDARD)
+ assertThat(matcher.matches(taskInfo)).isFalse()
+ }
+
+ @Test
+ fun activityMatcher_equivalentMatchersAreNotEqual() {
+ val activityType = WindowConfiguration.ACTIVITY_TYPE_HOME
+ val matcher1 = TaskMatcher.TopActivityType(activityType)
+ val matcher2 = TaskMatcher.TopActivityType(activityType)
+
+ assertThat(matcher1).isNotEqualTo(matcher2)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index 6eb9862..9273dce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -39,12 +39,15 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
@@ -53,6 +56,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.testKosmos
import junit.framework.Assert.assertEquals
@@ -166,6 +170,10 @@
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
testScope.runTest {
+ val isGone by
+ collectLastValue(
+ kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Gone, GONE)
+ )
powerInteractor.setAwakeForTest()
transitionRepository.sendTransitionSteps(
from = KeyguardState.AOD,
@@ -175,7 +183,7 @@
runCurrent()
// Make sure we're GONE.
- assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+ assertEquals(true, isGone)
// Get part way to AOD.
powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
@@ -204,6 +212,10 @@
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
testScope.runTest {
+ val isGone by
+ collectLastValue(
+ kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Gone, GONE)
+ )
powerInteractor.setAwakeForTest()
transitionRepository.sendTransitionSteps(
from = KeyguardState.AOD,
@@ -213,7 +225,7 @@
runCurrent()
// Make sure we're GONE.
- assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+ assertEquals(true, isGone)
// Get all the way to AOD
powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
@@ -239,6 +251,10 @@
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
testScope.runTest {
+ val isLockscreen by
+ collectLastValue(
+ kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Lockscreen, LOCKSCREEN)
+ )
powerInteractor.setAwakeForTest()
transitionRepository.sendTransitionSteps(
from = KeyguardState.AOD,
@@ -248,10 +264,7 @@
runCurrent()
// Make sure we're in LOCKSCREEN.
- assertEquals(
- KeyguardState.LOCKSCREEN,
- kosmos.keyguardTransitionInteractor.getFinishedState()
- )
+ assertEquals(true, isLockscreen)
// Get part way to AOD.
powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
@@ -327,6 +340,10 @@
@DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetectedInAod_fromGone() =
testScope.runTest {
+ val isGone by
+ collectLastValue(
+ kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Gone, GONE)
+ )
powerInteractor.setAwakeForTest()
transitionRepository.sendTransitionSteps(
from = KeyguardState.AOD,
@@ -336,7 +353,7 @@
runCurrent()
// Make sure we're GONE.
- assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+ assertEquals(true, isGone)
// Start going to AOD on first button push
powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index ee4a0d2d..c18deb1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -48,12 +48,15 @@
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -62,6 +65,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -316,6 +320,10 @@
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
testScope.runTest {
+ val isGone by
+ collectLastValue(
+ kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Gone, GONE)
+ )
powerInteractor.setAwakeForTest()
transitionRepository.sendTransitionSteps(
from = KeyguardState.DOZING,
@@ -325,7 +333,7 @@
runCurrent()
// Make sure we're GONE.
- assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+ assertEquals(true, isGone)
// Get part way to AOD.
powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
@@ -355,6 +363,10 @@
@Suppress("ktlint:standard:max-line-length")
fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
testScope.runTest {
+ val isGone by
+ collectLastValue(
+ kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Gone, GONE)
+ )
powerInteractor.setAwakeForTest()
transitionRepository.sendTransitionSteps(
from = KeyguardState.DOZING,
@@ -364,7 +376,7 @@
runCurrent()
// Make sure we're GONE.
- assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+ assertEquals(true, isGone)
// Get all the way to AOD
powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
@@ -390,6 +402,10 @@
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
testScope.runTest {
+ val isLockscreen by
+ collectLastValue(
+ kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Lockscreen, LOCKSCREEN)
+ )
powerInteractor.setAwakeForTest()
transitionRepository.sendTransitionSteps(
from = KeyguardState.DOZING,
@@ -399,10 +415,7 @@
runCurrent()
// Make sure we're in LOCKSCREEN.
- assertEquals(
- KeyguardState.LOCKSCREEN,
- kosmos.keyguardTransitionInteractor.getFinishedState()
- )
+ assertEquals(true, isLockscreen)
// Get part way to AOD.
powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 6e76cbc..6708ffa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -90,52 +90,6 @@
}
@Test
- fun finishedKeyguardStateTests() =
- testScope.runTest {
- val finishedSteps by collectValues(underTest.finishedKeyguardState)
- runCurrent()
- val steps = mutableListOf<TransitionStep>()
-
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
- steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
- steps.forEach {
- repository.sendTransitionStep(it)
- runCurrent()
- }
-
- assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD))
- }
-
- @Test
- fun startedKeyguardStateTests() =
- testScope.runTest {
- val startedStates by collectValues(underTest.startedKeyguardState)
- runCurrent()
- val steps = mutableListOf<TransitionStep>()
-
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
- steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
- steps.forEach {
- repository.sendTransitionStep(it)
- runCurrent()
- }
-
- assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE))
- }
-
- @Test
fun startedKeyguardTransitionStepTests() =
testScope.runTest {
val startedSteps by collectValues(underTest.startedKeyguardTransitionStep)
@@ -1206,95 +1160,6 @@
}
@Test
- fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() =
- testScope.runTest {
- val finishedStates by collectValues(underTest.finishedKeyguardState)
-
- // We default FINISHED in LOCKSCREEN.
- assertEquals(listOf(LOCKSCREEN), finishedStates)
-
- sendSteps(
- TransitionStep(LOCKSCREEN, AOD, 0f, STARTED),
- TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING),
- TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED),
- )
-
- // We're FINISHED in AOD.
- assertEquals(
- listOf(
- LOCKSCREEN,
- AOD,
- ),
- finishedStates
- )
-
- // Transition back to LOCKSCREEN.
- sendSteps(
- TransitionStep(AOD, LOCKSCREEN, 0f, STARTED),
- TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING),
- TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED),
- )
-
- // We're FINISHED in LOCKSCREEN.
- assertEquals(
- listOf(
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- ),
- finishedStates
- )
-
- sendSteps(
- TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
- TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
- )
-
- // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in
- // LOCKSCREEN.
- assertEquals(
- listOf(
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- ),
- finishedStates
- )
-
- sendSteps(
- TransitionStep(LOCKSCREEN, GONE, 0.6f, CANCELED),
- )
-
- // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN.
- assertEquals(
- listOf(
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- ),
- finishedStates
- )
-
- sendSteps(
- TransitionStep(GONE, LOCKSCREEN, 0.6f, STARTED),
- TransitionStep(GONE, LOCKSCREEN, 0.9f, RUNNING),
- TransitionStep(GONE, LOCKSCREEN, 1f, FINISHED),
- )
-
- // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to
- // LOCKSCREEN after the cancellation.
- assertEquals(
- listOf(
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- LOCKSCREEN,
- ),
- finishedStates
- )
- }
-
- @Test
fun testCurrentState() =
testScope.runTest {
val currentStates by collectValues(underTest.currentKeyguardState)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 3e0a1f3..073ed61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -19,18 +19,20 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.fakeLightRevealScrimRepository
+import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_EFFECT
import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.testKosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Test
@@ -64,35 +66,45 @@
@Test
fun lightRevealEffect_doesNotChangeDuringKeyguardTransition() =
- runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<LightRevealEffect>()
- val job = underTest.lightRevealEffect.onEach(values::add).launchIn(this)
+ kosmos.testScope.runTest {
+ val values by collectValues(underTest.lightRevealEffect)
+ runCurrent()
+ assertEquals(listOf(DEFAULT_REVEAL_EFFECT), values)
fakeLightRevealScrimRepository.setRevealEffect(reveal1)
-
+ runCurrent()
// The reveal effect shouldn't emit anything until a keyguard transition starts.
- assertEquals(values.size, 0)
+ assertEquals(listOf(DEFAULT_REVEAL_EFFECT), values)
// Once it starts, it should emit reveal1.
fakeKeyguardTransitionRepository.sendTransitionStep(
- TransitionStep(transitionState = TransitionState.STARTED)
+ TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.STARTED)
)
- assertEquals(values, listOf(reveal1))
+ runCurrent()
+ assertEquals(listOf(DEFAULT_REVEAL_EFFECT, reveal1), values)
// Until the next transition starts, reveal2 should not be emitted.
fakeLightRevealScrimRepository.setRevealEffect(reveal2)
+ runCurrent()
fakeKeyguardTransitionRepository.sendTransitionStep(
- TransitionStep(transitionState = TransitionState.RUNNING)
+ TransitionStep(
+ to = KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.RUNNING
+ )
)
+ runCurrent()
fakeKeyguardTransitionRepository.sendTransitionStep(
- TransitionStep(transitionState = TransitionState.FINISHED)
+ TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.FINISHED)
)
- assertEquals(values, listOf(reveal1))
+ runCurrent()
+ assertEquals(listOf(DEFAULT_REVEAL_EFFECT, reveal1), values)
fakeKeyguardTransitionRepository.sendTransitionStep(
- TransitionStep(transitionState = TransitionState.STARTED)
+ TransitionStep(
+ to = KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.STARTED
+ )
)
- assertEquals(values, listOf(reveal1, reveal2))
-
- job.cancel()
+ runCurrent()
+ assertEquals(listOf(DEFAULT_REVEAL_EFFECT, reveal1, reveal2), values)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
index 8c9c527..ec6045c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
@@ -48,12 +48,13 @@
composeRule.setContent {
val keepAlive by keepAliveMutable
if (keepAlive) {
- val viewModel = rememberViewModel {
- FakeSysUiViewModel(
- upstreamFlow = upstreamFlow,
- upstreamStateFlow = upstreamStateFlow,
- )
- }
+ val viewModel =
+ rememberViewModel("test") {
+ FakeSysUiViewModel(
+ upstreamFlow = upstreamFlow,
+ upstreamStateFlow = upstreamStateFlow,
+ )
+ }
Column {
Text(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 768fbca..7203b61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.composefragment.viewmodel
+import android.app.StatusBarManager
import android.content.testableContext
import android.testing.TestableLooper.RunWithLooper
import androidx.lifecycle.Lifecycle
@@ -32,6 +33,8 @@
import com.android.systemui.res.R
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -178,6 +181,23 @@
}
}
+ @Test
+ fun qsEnabled_followsRepository() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val qsEnabled by collectLastValue(underTest.qsEnabled)
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = QS_DISABLE_FLAG)
+
+ assertThat(qsEnabled).isFalse()
+
+ fakeDisableFlagsRepository.disableFlags.value = DisableFlagsModel()
+
+ assertThat(qsEnabled).isTrue()
+ }
+ }
+
private inline fun TestScope.testWithinLifecycle(
crossinline block: suspend TestScope.() -> TestResult
): TestResult {
@@ -186,4 +206,8 @@
block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
}
}
+
+ companion object {
+ private const val QS_DISABLE_FLAG = StatusBarManager.DISABLE2_QUICK_SETTINGS
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 4d3909c..f365afb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -501,7 +501,7 @@
private fun getCurrentSceneInUi(): SceneKey {
return when (val state = transitionState.value) {
is ObservableTransitionState.Idle -> state.currentScene
- is ObservableTransitionState.Transition.ChangeCurrentScene -> state.fromScene
+ is ObservableTransitionState.Transition.ChangeScene -> state.fromScene
is ObservableTransitionState.Transition.ShowOrHideOverlay -> state.currentScene
is ObservableTransitionState.Transition.ReplaceOverlay -> state.currentScene
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 8f8d2e2..d3b51d1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -51,7 +51,7 @@
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.dismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.scenetransition.lockscreenSceneTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
@@ -551,8 +551,7 @@
fun switchToAOD_whenAvailable_whenDeviceSleepsLocked() =
testScope.runTest {
kosmos.lockscreenSceneTransitionInteractor.start()
- val asleepState by
- collectLastValue(kosmos.keyguardTransitionInteractor.asleepKeyguardState)
+ val asleepState by collectLastValue(kosmos.keyguardInteractor.asleepKeyguardState)
val currentTransitionInfo by
collectLastValue(kosmos.keyguardTransitionRepository.currentTransitionInfoInternal)
val transitionState =
@@ -584,8 +583,7 @@
fun switchToDozing_whenAodUnavailable_whenDeviceSleepsLocked() =
testScope.runTest {
kosmos.lockscreenSceneTransitionInteractor.start()
- val asleepState by
- collectLastValue(kosmos.keyguardTransitionInteractor.asleepKeyguardState)
+ val asleepState by collectLastValue(kosmos.keyguardInteractor.asleepKeyguardState)
val currentTransitionInfo by
collectLastValue(kosmos.keyguardTransitionRepository.currentTransitionInfoInternal)
val transitionState =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 733cac9..3f97f0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -42,10 +42,14 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -56,6 +60,10 @@
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.Transition
+import com.android.systemui.scene.data.repository.setTransition
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.shade.shadeTestUtil
@@ -66,6 +74,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -295,34 +304,47 @@
// Start transitioning to glanceable hub
val progress = 0.6f
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GLANCEABLE_HUB,
- value = 0f,
- )
+ kosmos.setTransition(
+ sceneTransition = Transition(from = Scenes.Lockscreen, to = Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ value = 0f,
+ )
)
+
runCurrent()
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.RUNNING,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GLANCEABLE_HUB,
- value = progress,
- )
+ kosmos.setTransition(
+ sceneTransition =
+ Transition(
+ from = Scenes.Lockscreen,
+ to = Scenes.Communal,
+ progress = flowOf(progress)
+ ),
+ stateTransition =
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ value = progress,
+ )
)
+
runCurrent()
assertThat(alpha).isIn(Range.closed(0f, 1f))
// Finish transition to glanceable hub
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.FINISHED,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GLANCEABLE_HUB,
- value = 1f,
- )
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ value = 1f,
+ )
)
assertThat(alpha).isEqualTo(0f)
@@ -348,35 +370,46 @@
// Start transitioning to glanceable hub
val progress = 0.6f
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = KeyguardState.DREAMING,
- to = KeyguardState.GLANCEABLE_HUB,
- value = 0f,
- )
+ kosmos.setTransition(
+ sceneTransition = Transition(from = Scenes.Lockscreen, to = Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = DREAMING,
+ to = GLANCEABLE_HUB,
+ value = 0f,
+ )
)
runCurrent()
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.RUNNING,
- from = KeyguardState.DREAMING,
- to = KeyguardState.GLANCEABLE_HUB,
- value = progress,
- )
+ kosmos.setTransition(
+ sceneTransition =
+ Transition(
+ from = Scenes.Lockscreen,
+ to = Scenes.Communal,
+ progress = flowOf(progress)
+ ),
+ stateTransition =
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = DREAMING,
+ to = GLANCEABLE_HUB,
+ value = progress,
+ )
)
runCurrent()
// Keep notifications hidden during the transition from dream to hub
assertThat(alpha).isEqualTo(0)
// Finish transition to glanceable hub
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.FINISHED,
- from = KeyguardState.DREAMING,
- to = KeyguardState.GLANCEABLE_HUB,
- value = 1f,
- )
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = DREAMING,
+ to = GLANCEABLE_HUB,
+ value = 1f,
+ )
)
assertThat(alpha).isEqualTo(0f)
}
@@ -400,35 +433,47 @@
testScope.runTest {
val isOnLockscreen by collectLastValue(underTest.isOnLockscreen)
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- testScope,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition = TransitionStep(from = LOCKSCREEN, to = GONE)
)
assertThat(isOnLockscreen).isFalse()
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition = TransitionStep(from = GONE, to = LOCKSCREEN)
+ )
+ assertThat(isOnLockscreen).isTrue()
// While progressing from lockscreen, should still be true
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- value = 0.8f,
- transitionState = TransitionState.RUNNING
- )
+ kosmos.setTransition(
+ sceneTransition = Transition(from = Scenes.Lockscreen, to = Scenes.Gone),
+ stateTransition =
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GONE,
+ value = 0.8f,
+ transitionState = TransitionState.RUNNING
+ )
)
assertThat(isOnLockscreen).isTrue()
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN,
- testScope,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition =
+ TransitionStep(
+ from = GONE,
+ to = LOCKSCREEN,
+ )
)
assertThat(isOnLockscreen).isTrue()
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.PRIMARY_BOUNCER,
- testScope,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Bouncer),
+ stateTransition =
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = PRIMARY_BOUNCER,
+ )
)
assertThat(isOnLockscreen).isTrue()
}
@@ -442,8 +487,8 @@
shadeTestUtil.setLockscreenShadeExpansion(0f)
shadeTestUtil.setQsExpansion(0f)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
+ from = LOCKSCREEN,
+ to = OCCLUDED,
testScope,
)
assertThat(isOnLockscreenWithoutShade).isFalse()
@@ -480,11 +525,15 @@
assertThat(isOnGlanceableHubWithoutShade).isFalse()
// Move to glanceable hub
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GLANCEABLE_HUB,
- testScope = this
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ )
)
+
assertThat(isOnGlanceableHubWithoutShade).isTrue()
// While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion
@@ -502,6 +551,14 @@
shadeTestUtil.setQsExpansion(0f)
shadeTestUtil.setLockscreenShadeExpansion(0f)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = GLANCEABLE_HUB,
+ )
+ )
assertThat(isOnGlanceableHubWithoutShade).isTrue()
}
@@ -808,8 +865,8 @@
// GONE transition gets to 90% complete
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
+ from = LOCKSCREEN,
+ to = GONE,
transitionState = TransitionState.STARTED,
value = 0f,
)
@@ -817,8 +874,8 @@
runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
+ from = LOCKSCREEN,
+ to = GONE,
transitionState = TransitionState.RUNNING,
value = 0.9f,
)
@@ -843,8 +900,8 @@
// OCCLUDED transition gets to 90% complete
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
+ from = LOCKSCREEN,
+ to = OCCLUDED,
transitionState = TransitionState.STARTED,
value = 0f,
)
@@ -852,8 +909,8 @@
runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
+ from = LOCKSCREEN,
+ to = OCCLUDED,
transitionState = TransitionState.RUNNING,
value = 0.9f,
)
@@ -877,8 +934,8 @@
showLockscreen()
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
+ from = LOCKSCREEN,
+ to = GONE,
testScope
)
keyguardRepository.setStatusBarState(StatusBarState.SHADE)
@@ -922,8 +979,8 @@
// ... then user hits power to go to AOD
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
+ from = LOCKSCREEN,
+ to = AOD,
testScope,
)
// ... followed by a shade collapse
@@ -945,7 +1002,7 @@
// PRIMARY_BOUNCER->GONE transition is started
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.PRIMARY_BOUNCER,
+ from = PRIMARY_BOUNCER,
to = GONE,
transitionState = TransitionState.STARTED,
value = 0f,
@@ -956,7 +1013,7 @@
// PRIMARY_BOUNCER->GONE transition running
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.PRIMARY_BOUNCER,
+ from = PRIMARY_BOUNCER,
to = GONE,
transitionState = TransitionState.RUNNING,
value = 0.1f,
@@ -967,7 +1024,7 @@
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.PRIMARY_BOUNCER,
+ from = PRIMARY_BOUNCER,
to = GONE,
transitionState = TransitionState.RUNNING,
value = 0.9f,
@@ -979,7 +1036,7 @@
hideCommunalScene()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.PRIMARY_BOUNCER,
+ from = PRIMARY_BOUNCER,
to = GONE,
transitionState = TransitionState.FINISHED,
value = 1f
@@ -1003,7 +1060,7 @@
// PRIMARY_BOUNCER->GONE transition is started
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.PRIMARY_BOUNCER,
+ from = PRIMARY_BOUNCER,
to = GONE,
transitionState = TransitionState.STARTED,
)
@@ -1013,7 +1070,7 @@
// PRIMARY_BOUNCER->GONE transition running
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.PRIMARY_BOUNCER,
+ from = PRIMARY_BOUNCER,
to = GONE,
transitionState = TransitionState.RUNNING,
value = 0.1f,
@@ -1024,7 +1081,7 @@
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.PRIMARY_BOUNCER,
+ from = PRIMARY_BOUNCER,
to = GONE,
transitionState = TransitionState.RUNNING,
value = 0.9f,
@@ -1035,7 +1092,7 @@
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.PRIMARY_BOUNCER,
+ from = PRIMARY_BOUNCER,
to = GONE,
transitionState = TransitionState.FINISHED,
value = 1f
@@ -1058,7 +1115,7 @@
// ALTERNATE_BOUNCER->GONE transition is started
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
+ from = ALTERNATE_BOUNCER,
to = GONE,
transitionState = TransitionState.STARTED,
value = 0f,
@@ -1069,7 +1126,7 @@
// ALTERNATE_BOUNCER->GONE transition running
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
+ from = ALTERNATE_BOUNCER,
to = GONE,
transitionState = TransitionState.RUNNING,
value = 0.1f,
@@ -1080,7 +1137,7 @@
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
+ from = ALTERNATE_BOUNCER,
to = GONE,
transitionState = TransitionState.RUNNING,
value = 0.9f,
@@ -1092,7 +1149,7 @@
hideCommunalScene()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
+ from = ALTERNATE_BOUNCER,
to = GONE,
transitionState = TransitionState.FINISHED,
value = 1f
@@ -1116,7 +1173,7 @@
// ALTERNATE_BOUNCER->GONE transition is started
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
+ from = ALTERNATE_BOUNCER,
to = GONE,
transitionState = TransitionState.STARTED,
)
@@ -1126,7 +1183,7 @@
// ALTERNATE_BOUNCER->GONE transition running
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
+ from = ALTERNATE_BOUNCER,
to = GONE,
transitionState = TransitionState.RUNNING,
value = 0.1f,
@@ -1137,7 +1194,7 @@
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
+ from = ALTERNATE_BOUNCER,
to = GONE,
transitionState = TransitionState.RUNNING,
value = 0.9f,
@@ -1148,7 +1205,7 @@
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
+ from = ALTERNATE_BOUNCER,
to = GONE,
transitionState = TransitionState.FINISHED,
value = 1f
@@ -1165,8 +1222,8 @@
keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ from = AOD,
+ to = LOCKSCREEN,
testScope,
)
}
@@ -1178,8 +1235,8 @@
keyguardRepository.setDreaming(true)
runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.DREAMING,
+ from = LOCKSCREEN,
+ to = DREAMING,
testScope,
)
}
@@ -1191,8 +1248,8 @@
keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ from = AOD,
+ to = LOCKSCREEN,
testScope,
)
}
@@ -1204,8 +1261,8 @@
keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ from = AOD,
+ to = LOCKSCREEN,
testScope,
)
}
@@ -1219,8 +1276,8 @@
kosmos.keyguardBouncerRepository.setPrimaryShow(true)
runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.PRIMARY_BOUNCER,
+ from = GLANCEABLE_HUB,
+ to = PRIMARY_BOUNCER,
testScope,
)
}
@@ -1234,8 +1291,8 @@
kosmos.keyguardBouncerRepository.setPrimaryShow(false)
runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.ALTERNATE_BOUNCER,
+ from = GLANCEABLE_HUB,
+ to = ALTERNATE_BOUNCER,
testScope,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index 51a70bd..fe6c741 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.testKosmos
@@ -157,6 +158,10 @@
@Test
fun testDumpingState() =
test({
+ testableResources.addOverride(R.bool.volume_panel_is_large_screen, false)
+ testableResources.overrideConfiguration(
+ Configuration().apply { orientation = Configuration.ORIENTATION_PORTRAIT }
+ )
componentByKey =
mapOf(
COMPONENT_1 to mockVolumePanelUiComponentProvider,
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 823ff9f..e8fd2ef 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -727,6 +727,10 @@
.75
</item>
+ <!-- The last x ms of face acquired info messages to analyze to determine
+ whether to show a deferred face auth help message. -->
+ <integer name="config_face_help_msgs_defer_analyze_timeframe">500</integer>
+
<!-- Which face help messages to surface when fingerprint is also enrolled.
Message ids correspond with the acquired ids in BiometricFaceConstants -->
<integer-array name="config_face_help_msgs_when_fingerprint_enrolled">
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fd943d0..e6cc6cf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -740,6 +740,8 @@
<string name="quick_settings_bluetooth_device_connected">Connected</string>
<!-- QuickSettings: Bluetooth dialog device in audio sharing default summary [CHAR LIMIT=50]-->
<string name="quick_settings_bluetooth_device_audio_sharing">Audio Sharing</string>
+ <!-- QuickSettings: Bluetooth dialog device summary for devices that are capable of audio sharing and switching to active[CHAR LIMIT=NONE]-->
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active">Tap to switch or share audio</string>
<!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]-->
<string name="quick_settings_bluetooth_device_saved">Saved</string>
<!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 64fe78d..7ec977a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -218,6 +218,7 @@
@ViewDebug.ExportedProperty(category="recents")
public String title;
@ViewDebug.ExportedProperty(category="recents")
+ @Nullable
public String titleDescription;
@ViewDebug.ExportedProperty(category="recents")
public int colorPrimary;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt
index 1685f49..4731ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt
@@ -25,11 +25,13 @@
* - startWindow: Window of time on start required before showing the first help message
* - shownFaceMessageFrequencyBoost: Frequency boost given to messages that are currently shown to
* the user
+ * - threshold: minimum percentage of frames a message must appear in order to show it
*/
class FaceHelpMessageDebouncer(
private val window: Long = DEFAULT_WINDOW_MS,
private val startWindow: Long = window,
private val shownFaceMessageFrequencyBoost: Int = 4,
+ private val threshold: Float = 0f,
) {
private val TAG = "FaceHelpMessageDebouncer"
private var startTime = 0L
@@ -56,7 +58,7 @@
}
}
- private fun getMostFrequentHelpMessage(): HelpFaceAuthenticationStatus? {
+ private fun getMostFrequentHelpMessageSurpassingThreshold(): HelpFaceAuthenticationStatus? {
// freqMap: msgId => frequency
val freqMap = helpFaceAuthStatuses.groupingBy { it.msgId }.eachCount().toMutableMap()
@@ -83,7 +85,25 @@
}
}
?.key
- return helpFaceAuthStatuses.findLast { it.msgId == msgIdWithHighestFrequency }
+
+ if (msgIdWithHighestFrequency == null) {
+ return null
+ }
+
+ val freq =
+ if (msgIdWithHighestFrequency == lastMessageIdShown) {
+ freqMap[msgIdWithHighestFrequency]!! - shownFaceMessageFrequencyBoost
+ } else {
+ freqMap[msgIdWithHighestFrequency]!!
+ }
+ .toFloat()
+
+ return if ((freq / helpFaceAuthStatuses.size.toFloat()) >= threshold) {
+ helpFaceAuthStatuses.findLast { it.msgId == msgIdWithHighestFrequency }
+ } else {
+ Log.v(TAG, "most frequent helpFaceAuthStatus didn't make the threshold: $threshold")
+ null
+ }
}
fun addMessage(helpFaceAuthStatus: HelpFaceAuthenticationStatus) {
@@ -98,14 +118,15 @@
return null
}
removeOldMessages(atTimestamp)
- val messageToShow = getMostFrequentHelpMessage()
+ val messageToShow = getMostFrequentHelpMessageSurpassingThreshold()
if (lastMessageIdShown != messageToShow?.msgId) {
Log.v(
TAG,
"showMessage previousLastMessageId=$lastMessageIdShown" +
"\n\tmessageToShow=$messageToShow " +
"\n\thelpFaceAuthStatusesSize=${helpFaceAuthStatuses.size}" +
- "\n\thelpFaceAuthStatuses=$helpFaceAuthStatuses"
+ "\n\thelpFaceAuthStatuses=$helpFaceAuthStatuses" +
+ "\n\tthreshold=$threshold"
)
lastMessageIdShown = messageToShow?.msgId
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
index 90d06fb..d382ada 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
@@ -17,14 +17,19 @@
package com.android.systemui.biometrics
import android.content.res.Resources
+import android.os.SystemClock.elapsedRealtime
import com.android.keyguard.logging.BiometricMessageDeferralLogger
import com.android.systemui.Dumpable
+import com.android.systemui.Flags.faceMessageDeferUpdate
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.BiometricLog
import com.android.systemui.res.R
+import com.android.systemui.util.time.SystemClock
+import dagger.Lazy
import java.io.PrintWriter
import java.util.Objects
import java.util.UUID
@@ -36,7 +41,8 @@
constructor(
@Main private val resources: Resources,
@BiometricLog private val logBuffer: LogBuffer,
- private val dumpManager: DumpManager
+ private val dumpManager: DumpManager,
+ private val systemClock: Lazy<SystemClock>,
) {
fun create(): FaceHelpMessageDeferral {
val id = UUID.randomUUID().toString()
@@ -45,6 +51,7 @@
logBuffer = BiometricMessageDeferralLogger(logBuffer, "FaceHelpMessageDeferral[$id]"),
dumpManager = dumpManager,
id = id,
+ systemClock,
)
}
}
@@ -58,14 +65,17 @@
logBuffer: BiometricMessageDeferralLogger,
dumpManager: DumpManager,
val id: String,
+ val systemClock: Lazy<SystemClock>,
) :
BiometricMessageDeferral(
resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(),
resources.getIntArray(R.array.config_face_help_msgs_ignore).toHashSet(),
resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold),
+ resources.getInteger(R.integer.config_face_help_msgs_defer_analyze_timeframe).toLong(),
logBuffer,
dumpManager,
id,
+ systemClock,
)
/**
@@ -77,10 +87,24 @@
private val messagesToDefer: Set<Int>,
private val acquiredInfoToIgnore: Set<Int>,
private val threshold: Float,
+ private val windowToAnalyzeLastNFrames: Long,
private val logBuffer: BiometricMessageDeferralLogger,
dumpManager: DumpManager,
id: String,
+ private val systemClock: Lazy<SystemClock>,
) : Dumpable {
+
+ private val faceHelpMessageDebouncer: FaceHelpMessageDebouncer? =
+ if (faceMessageDeferUpdate()) {
+ FaceHelpMessageDebouncer(
+ window = windowToAnalyzeLastNFrames,
+ startWindow = 0L,
+ shownFaceMessageFrequencyBoost = 0,
+ threshold = threshold,
+ )
+ } else {
+ null
+ }
private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap()
private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap()
private var mostFrequentAcquiredInfoToDefer: Int? = null
@@ -97,13 +121,20 @@
pw.println("messagesToDefer=$messagesToDefer")
pw.println("totalFrames=$totalFrames")
pw.println("threshold=$threshold")
+ pw.println("faceMessageDeferUpdateFlagEnabled=${faceMessageDeferUpdate()}")
+ if (faceMessageDeferUpdate()) {
+ pw.println("windowToAnalyzeLastNFrames(ms)=$windowToAnalyzeLastNFrames")
+ }
}
/** Reset all saved counts. */
fun reset() {
totalFrames = 0
- mostFrequentAcquiredInfoToDefer = null
- acquiredInfoToFrequency.clear()
+ if (!faceMessageDeferUpdate()) {
+ mostFrequentAcquiredInfoToDefer = null
+ acquiredInfoToFrequency.clear()
+ }
+
acquiredInfoToHelpString.clear()
logBuffer.reset()
}
@@ -137,24 +168,48 @@
logBuffer.logFrameIgnored(acquiredInfo)
return
}
-
totalFrames++
- val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1
- acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount
- if (
- messagesToDefer.contains(acquiredInfo) &&
- (mostFrequentAcquiredInfoToDefer == null ||
- newAcquiredInfoCount >
- acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0))
- ) {
- mostFrequentAcquiredInfoToDefer = acquiredInfo
+ if (faceMessageDeferUpdate()) {
+ faceHelpMessageDebouncer?.let {
+ val helpFaceAuthStatus =
+ HelpFaceAuthenticationStatus(
+ msgId = acquiredInfo,
+ msg = null,
+ systemClock.get().elapsedRealtime()
+ )
+ if (totalFrames == 1) { // first frame
+ it.startNewFaceAuthSession(helpFaceAuthStatus.createdAt)
+ }
+ it.addMessage(helpFaceAuthStatus)
+ }
+ } else {
+ val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1
+ acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount
+ if (
+ messagesToDefer.contains(acquiredInfo) &&
+ (mostFrequentAcquiredInfoToDefer == null ||
+ newAcquiredInfoCount >
+ acquiredInfoToFrequency.getOrDefault(
+ mostFrequentAcquiredInfoToDefer!!,
+ 0
+ ))
+ ) {
+ mostFrequentAcquiredInfoToDefer = acquiredInfo
+ }
}
logBuffer.logFrameProcessed(
acquiredInfo,
totalFrames,
- mostFrequentAcquiredInfoToDefer?.toString()
+ if (faceMessageDeferUpdate()) {
+ faceHelpMessageDebouncer
+ ?.getMessageToShow(systemClock.get().elapsedRealtime())
+ ?.msgId
+ .toString()
+ } else {
+ mostFrequentAcquiredInfoToDefer?.toString()
+ }
)
}
@@ -166,9 +221,16 @@
* [threshold] percentage.
*/
fun getDeferredMessage(): CharSequence? {
- mostFrequentAcquiredInfoToDefer?.let {
- if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) {
- return acquiredInfoToHelpString[it]
+ if (faceMessageDeferUpdate()) {
+ faceHelpMessageDebouncer?.let {
+ val helpFaceAuthStatus = it.getMessageToShow(systemClock.get().elapsedRealtime())
+ return acquiredInfoToHelpString[helpFaceAuthStatus?.msgId]
+ }
+ } else {
+ mostFrequentAcquiredInfoToDefer?.let {
+ if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) {
+ return acquiredInfoToHelpString[it]
+ }
}
}
return null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index cd9b9bc..0b440ad 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -45,6 +45,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieCompositionFactory
+import com.android.systemui.Flags.bpIconA11y
import com.android.systemui.biometrics.Utils.ellipsize
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -54,6 +55,7 @@
import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
import com.android.systemui.biometrics.ui.viewmodel.PromptSize
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.common.ui.view.onTouchListener
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
@@ -330,17 +332,31 @@
// reuse the icon as a confirm button
launch {
- viewModel.isIconConfirmButton
- .map { isPending ->
- when {
- isPending && modalities.hasFaceAndFingerprint ->
- View.OnTouchListener { _: View, event: MotionEvent ->
- viewModel.onOverlayTouch(event)
- }
- else -> null
+ if (bpIconA11y()) {
+ viewModel.isIconConfirmButton.collect { isButton ->
+ if (isButton) {
+ iconView.onTouchListener { _: View, event: MotionEvent ->
+ viewModel.onOverlayTouch(event)
+ }
+ iconView.setOnClickListener { viewModel.confirmAuthenticated() }
+ } else {
+ iconView.setOnTouchListener(null)
+ iconView.setOnClickListener(null)
}
}
- .collect { onTouch -> iconView.setOnTouchListener(onTouch) }
+ } else {
+ viewModel.isIconConfirmButton
+ .map { isPending ->
+ when {
+ isPending && modalities.hasFaceAndFingerprint ->
+ View.OnTouchListener { _: View, event: MotionEvent ->
+ viewModel.onOverlayTouch(event)
+ }
+ else -> null
+ }
+ }
+ .collect { onTouch -> iconView.setOnTouchListener(onTouch) }
+ }
}
// dismiss prompt when authenticated and confirmed
@@ -358,7 +374,8 @@
// Allow icon to be used as confirmation button with udfps and a11y
// enabled
if (
- accessibilityManager.isTouchExplorationEnabled &&
+ !bpIconA11y() &&
+ accessibilityManager.isTouchExplorationEnabled &&
modalities.hasUdfps
) {
iconView.setOnClickListener { viewModel.confirmAuthenticated() }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index 4a358c0..bdd4c16 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -31,6 +31,8 @@
@UiEvent(doc = "Saved clicked to connect") SAVED_DEVICE_CONNECT(1500),
@UiEvent(doc = "Active device clicked to disconnect") ACTIVE_DEVICE_DISCONNECT(1507),
@UiEvent(doc = "Audio sharing device clicked, do nothing") AUDIO_SHARING_DEVICE_CLICKED(1699),
+ @UiEvent(doc = "Available audio sharing device clicked, do nothing")
+ AVAILABLE_AUDIO_SHARING_DEVICE_CLICKED(1880),
@UiEvent(doc = "Connected other device clicked to disconnect")
CONNECTED_OTHER_DEVICE_DISCONNECT(1508),
@UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617),
@@ -44,8 +46,14 @@
doc = "Not broadcasting, having one connected, another saved LE audio device is clicked"
)
LAUNCH_SETTINGS_NOT_SHARING_SAVED_LE_DEVICE_CLICKED(1719),
+ @Deprecated(
+ "Use case no longer needed",
+ ReplaceWith("LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED")
+ )
@UiEvent(doc = "Not broadcasting, one of the two connected LE audio devices is clicked")
- LAUNCH_SETTINGS_NOT_SHARING_CONNECTED_LE_DEVICE_CLICKED(1720);
+ LAUNCH_SETTINGS_NOT_SHARING_CONNECTED_LE_DEVICE_CLICKED(1720),
+ @UiEvent(doc = "Not broadcasting, having two connected, the active LE audio devices is clicked")
+ LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED(1881);
override fun getId() = metricId
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index a78130f..2ba4c73 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -38,6 +38,7 @@
enum class DeviceItemType {
ACTIVE_MEDIA_BLUETOOTH_DEVICE,
AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
CONNECTED_BLUETOOTH_DEVICE,
SAVED_BLUETOOTH_DEVICE,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
index 9d82e76..f1894d3 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
@@ -67,7 +67,7 @@
backgroundDispatcher,
logger
),
- NotSharingClickedConnected(
+ NotSharingClickedActive(
leAudioProfile,
assistantProfile,
backgroundDispatcher,
@@ -106,6 +106,12 @@
DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED)
}
+ DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ // TODO(b/360759048): pop up dialog
+ uiEventLogger.log(
+ BluetoothTileDialogUiEvent.AVAILABLE_AUDIO_SHARING_DEVICE_CLICKED
+ )
+ }
DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
setActive()
uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
@@ -238,14 +244,14 @@
BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_NOT_SHARING_SAVED_LE_DEVICE_CLICKED
}
- private class NotSharingClickedConnected(
+ private class NotSharingClickedActive(
private val leAudioProfile: LeAudioProfile?,
private val assistantProfile: LocalBluetoothLeBroadcastAssistant?,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val logger: BluetoothTileDialogLogger,
) : LaunchSettingsCriteria {
- // If not broadcasting, having two device connected, clicked on any connected LE audio
- // devices
+ // If not broadcasting, having two device connected, clicked on the active LE audio
+ // device
override suspend fun matched(inAudioSharing: Boolean, deviceItem: DeviceItem): Boolean {
return withContext(backgroundDispatcher) {
val matched =
@@ -259,7 +265,7 @@
logger
)
.size == 2 &&
- deviceItem.isActiveOrConnectedLeAudioSupported
+ deviceItem.isActiveLeAudioSupported
}
} ?: false
@@ -275,7 +281,7 @@
}
override suspend fun getClickUiEvent(deviceItem: DeviceItem) =
- BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_NOT_SHARING_CONNECTED_LE_DEVICE_CLICKED
+ BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED
}
private companion object {
@@ -290,10 +296,8 @@
val DeviceItem.isNotConnectedLeAudioSupported: Boolean
get() = type == DeviceItemType.SAVED_BLUETOOTH_DEVICE && isLeAudioSupported
- val DeviceItem.isActiveOrConnectedLeAudioSupported: Boolean
- get() =
- (type == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE ||
- type == DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE) && isLeAudioSupported
+ val DeviceItem.isActiveLeAudioSupported: Boolean
+ get() = type == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE && isLeAudioSupported
val DeviceItem.isMediaDevice: Boolean
get() =
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index e846bf7..7280489 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -125,6 +125,37 @@
}
}
+internal class AvailableAudioSharingMediaDeviceItemFactory(
+ private val localBluetoothManager: LocalBluetoothManager?
+) : AvailableMediaDeviceItemFactory() {
+ override fun isFilterMatched(
+ context: Context,
+ cachedDevice: CachedBluetoothDevice,
+ audioManager: AudioManager
+ ): Boolean {
+ return BluetoothUtils.isAudioSharingEnabled() &&
+ super.isFilterMatched(context, cachedDevice, audioManager) &&
+ BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(
+ cachedDevice,
+ localBluetoothManager
+ )
+ }
+
+ override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ context.getString(
+ R.string.quick_settings_bluetooth_device_audio_sharing_or_switch_active
+ ),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+ "",
+ isActive = false
+ )
+ }
+}
+
internal class ActiveHearingDeviceItemFactory : ActiveMediaDeviceItemFactory() {
override fun isFilterMatched(
context: Context,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index 9524496..9114eca 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -118,6 +118,7 @@
listOf(
ActiveMediaDeviceItemFactory(),
AudioSharingMediaDeviceItemFactory(localBluetoothManager),
+ AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager),
AvailableMediaDeviceItemFactory(),
ConnectedDeviceItemFactory(),
SavedDeviceItemFactory()
@@ -127,6 +128,7 @@
listOf(
DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
DeviceItemType.SAVED_BLUETOOTH_DEVICE,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
index c1f7d59..102ae7a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -52,7 +52,9 @@
setContent {
PlatformTheme {
BouncerContent(
- rememberViewModel { viewModelFactory.create() },
+ rememberViewModel("ComposeBouncerViewBinder") {
+ viewModelFactory.create()
+ },
dialogFactory,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index abca518..c67b354 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -22,7 +22,6 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.SysUiViewModel
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -40,7 +39,10 @@
* being able to attempt to unlock the device.
*/
val isInputEnabled: StateFlow<Boolean>,
-) : SysUiViewModel, ExclusiveActivatable() {
+
+ /** Name to use for performance tracing purposes. */
+ val traceName: String,
+) : ExclusiveActivatable() {
private val _animateFailure = MutableStateFlow(false)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index d21eccd..05b4656 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -39,7 +39,6 @@
import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage
import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.util.kotlin.Utils.Companion.sample
@@ -80,7 +79,7 @@
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
private val flags: ComposeBouncerFlags,
-) : SysUiViewModel, ExclusiveActivatable() {
+) : ExclusiveActivatable() {
/**
* A message shown when the user has attempted the wrong credential too many times and now must
* wait a while before attempting to authenticate again.
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index 79e5f8d..b985fc4 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -23,6 +23,7 @@
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.type
import androidx.core.graphics.drawable.toBitmap
+import com.android.app.tracing.coroutines.traceCoroutine
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
@@ -34,7 +35,6 @@
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -63,7 +63,7 @@
private val pinViewModelFactory: PinBouncerViewModel.Factory,
private val patternViewModelFactory: PatternBouncerViewModel.Factory,
private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
-) : SysUiViewModel, ExclusiveActivatable() {
+) : ExclusiveActivatable() {
private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
@@ -147,7 +147,7 @@
.map(::getChildViewModel)
.collectLatest { childViewModelOrNull ->
_authMethodViewModel.value = childViewModelOrNull
- childViewModelOrNull?.activate()
+ childViewModelOrNull?.let { traceCoroutine(it.traceName) { it.activate() } }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index c91fd6a..fc860e5 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -53,6 +53,7 @@
AuthMethodBouncerViewModel(
interactor = interactor,
isInputEnabled = isInputEnabled,
+ traceName = "PasswordBouncerViewModel",
) {
private val _password = MutableStateFlow("")
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 4c02929..60ec3019 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -51,6 +51,7 @@
AuthMethodBouncerViewModel(
interactor = interactor,
isInputEnabled = isInputEnabled,
+ traceName = "PatternBouncerViewModel",
) {
/** The number of columns in the dot grid. */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index c611954..db78a98 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -63,6 +63,7 @@
AuthMethodBouncerViewModel(
interactor = interactor,
isInputEnabled = isInputEnabled,
+ traceName = "PinBouncerViewModel",
) {
/**
* Whether the sim-related UI in the pin view is showing.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 0582cc2..c69cea4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -32,11 +32,14 @@
import com.android.systemui.keyguard.shared.model.filterState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
/**
@@ -54,6 +57,18 @@
private val dreamManager: DreamManager,
@Background private val bgScope: CoroutineScope,
) : CoreStartable {
+ /** Flow that emits when the dream should be started underneath the glanceable hub. */
+ val startDream =
+ allOf(
+ keyguardTransitionInteractor
+ .transitionValue(Scenes.Communal, KeyguardState.GLANCEABLE_HUB)
+ .map { it == 1f },
+ not(keyguardInteractor.isDreaming),
+ // TODO(b/362830856): Remove this workaround.
+ keyguardInteractor.isKeyguardShowing,
+ )
+ .filter { it }
+
@SuppressLint("MissingPermission")
override fun start() {
if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
@@ -72,17 +87,10 @@
// Restart the dream underneath the hub in order to support the ability to swipe
// away the hub to enter the dream.
- keyguardTransitionInteractor
- .transition(
- edge = Edge.create(to = Scenes.Communal),
- edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GLANCEABLE_HUB)
- )
- .filterState(TransitionState.FINISHED)
+ startDream
.sampleFilter(powerInteractor.isAwake) { isAwake ->
- dreamManager.canStartDreaming(isAwake)
+ !glanceableHubAllowKeyguardWhenDreaming() && dreamManager.canStartDreaming(isAwake)
}
- .sampleFilter(keyguardInteractor.isDreaming) { isDreaming -> !isDreaming }
- .filter { !glanceableHubAllowKeyguardWhenDreaming() }
.onEach { dreamManager.startDream() }
.launchIn(bgScope)
}
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 ba2b7bf..a33e0ac 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.dagger
import android.content.Context
+import android.content.res.Resources
import com.android.systemui.CoreStartable
import com.android.systemui.communal.data.backup.CommunalBackupUtils
import com.android.systemui.communal.data.db.CommunalDatabaseModule
@@ -38,6 +39,8 @@
import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSource
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -90,6 +93,7 @@
companion object {
const val LOGGABLE_PREFIXES = "loggable_prefixes"
+ const val LAUNCHER_PACKAGE = "launcher_package"
@Provides
@Communal
@@ -126,5 +130,12 @@
.getStringArray(com.android.internal.R.array.config_loggable_dream_prefixes)
.toList()
}
+
+ /** The package name of the launcher */
+ @Provides
+ @Named(LAUNCHER_PACKAGE)
+ fun provideLauncherPackage(@Main resources: Resources): String {
+ return resources.getString(R.string.launcher_overlayable_package)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
index 86241a5..f77dd58 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
@@ -24,6 +24,9 @@
import com.android.systemui.communal.smartspace.CommunalSmartspaceController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.util.time.SystemClock
import java.util.concurrent.Executor
@@ -49,8 +52,11 @@
private val communalSmartspaceController: CommunalSmartspaceController,
@Main private val uiExecutor: Executor,
private val systemClock: SystemClock,
+ @CommunalLog logBuffer: LogBuffer,
) : CommunalSmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener {
+ private val logger = Logger(logBuffer, "CommunalSmartspaceRepository")
+
private val _timers: MutableStateFlow<List<CommunalSmartspaceTimer>> =
MutableStateFlow(emptyList())
override val timers: Flow<List<CommunalSmartspaceTimer>> = _timers
@@ -87,6 +93,8 @@
remoteViews = target.remoteViews!!,
)
}
+
+ logger.d({ "Smartspace timers updated: $str1" }) { str1 = _timers.value.toString() }
}
override fun startListening() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 98abbeb..9b96341 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -510,7 +510,7 @@
* A flow of ongoing content, including smartspace timers and umo, ordered by creation time and
* sized dynamically.
*/
- fun getOngoingContent(mediaHostVisible: Boolean): Flow<List<CommunalContentModel.Ongoing>> =
+ val ongoingContent: Flow<List<CommunalContentModel.Ongoing>> =
combine(smartspaceRepository.timers, mediaRepository.mediaModel) { timers, media ->
val ongoingContent = mutableListOf<CommunalContentModel.Ongoing>()
@@ -526,7 +526,7 @@
)
// Add UMO
- if (mediaHostVisible && media.hasAnyMediaOrRecommendation) {
+ if (media.hasAnyMediaOrRecommendation) {
ongoingContent.add(
CommunalContentModel.Umo(
createdTimestampMillis = media.createdTimestampMillis,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index 5bbb46d..e04d309 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -182,6 +182,7 @@
}
private suspend fun finishCurrentTransition() {
+ if (currentTransitionId == null) return
internalTransitionInteractor.updateTransition(
currentTransitionId!!,
1f,
@@ -224,7 +225,7 @@
collectProgress(transition)
} else if (transition.toScene == CommunalScenes.Communal) {
if (currentToState == KeyguardState.GLANCEABLE_HUB) {
- transitionKtfTo(transitionInteractor.getStartedFromState())
+ transitionKtfTo(transitionInteractor.startedKeyguardTransitionStep.value.from)
}
startTransitionToGlanceableHub()
collectProgress(transition)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 16788d1..65f0679 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -29,6 +29,7 @@
import android.view.accessibility.AccessibilityManager
import androidx.activity.result.ActivityResultLauncher
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.communal.dagger.CommunalModule.Companion.LAUNCHER_PACKAGE
import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -81,6 +82,7 @@
@Application private val context: Context,
private val accessibilityManager: AccessibilityManager,
private val packageManager: PackageManager,
+ @Named(LAUNCHER_PACKAGE) private val launcherPackage: String,
) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
@@ -185,7 +187,6 @@
/** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */
suspend fun onOpenWidgetPicker(
resources: Resources,
- packageManager: PackageManager,
activityLauncher: ActivityResultLauncher<Intent>
): Boolean =
withContext(backgroundDispatcher) {
@@ -196,7 +197,7 @@
) {
it.providerInfo
}
- getWidgetPickerActivityIntent(resources, packageManager, excludeList)?.let {
+ getWidgetPickerActivityIntent(resources, excludeList)?.let {
try {
activityLauncher.launch(it)
return@withContext true
@@ -209,18 +210,10 @@
private fun getWidgetPickerActivityIntent(
resources: Resources,
- packageManager: PackageManager,
excludeList: ArrayList<AppWidgetProviderInfo>
): Intent? {
- val packageName =
- getLauncherPackageName(packageManager)
- ?: run {
- Log.e(TAG, "Couldn't resolve launcher package name")
- return@getWidgetPickerActivityIntent null
- }
-
return Intent(Intent.ACTION_PICK).apply {
- setPackage(packageName)
+ setPackage(launcherPackage)
putExtra(
EXTRA_DESIRED_WIDGET_WIDTH,
resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width)
@@ -247,16 +240,6 @@
}
}
- private fun getLauncherPackageName(packageManager: PackageManager): String? {
- return packageManager
- .resolveActivity(
- Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) },
- PackageManager.MATCH_DEFAULT_ONLY
- )
- ?.activityInfo
- ?.packageName
- }
-
/** Sets whether edit mode is currently open */
fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 5a39a62..d69ba1b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -99,7 +99,7 @@
private val logger = Logger(logBuffer, "CommunalViewModel")
- private val _isMediaHostVisible =
+ private val isMediaHostVisible =
conflatedCallbackFlow {
val callback = { visible: Boolean ->
trySend(visible)
@@ -117,12 +117,26 @@
mediaHost.updateViewVisibility()
emit(mediaHost.visible)
}
+ .distinctUntilChanged()
.onEach { logger.d({ "_isMediaHostVisible: $bool1" }) { bool1 = it } }
.flowOn(mainDispatcher)
/** Communal content saved from the previous emission when the flow is active (not "frozen"). */
private var frozenCommunalContent: List<CommunalContentModel>? = null
+ private val ongoingContent =
+ combine(
+ isMediaHostVisible,
+ communalInteractor.ongoingContent.onEach { mediaHost.updateViewVisibility() }
+ ) { mediaVisible, ongoingContent ->
+ if (mediaVisible) {
+ ongoingContent
+ } else {
+ // Media is not visible, don't show UMO
+ ongoingContent.filterNot { it is CommunalContentModel.Umo }
+ }
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
private val latestCommunalContent: Flow<List<CommunalContentModel>> =
tutorialInteractor.isTutorialAvailable
@@ -130,8 +144,6 @@
if (isTutorialMode) {
return@flatMapLatest flowOf(communalInteractor.tutorialContent)
}
- val ongoingContent =
- _isMediaHostVisible.flatMapLatest { communalInteractor.getOngoingContent(it) }
combine(
ongoingContent,
communalInteractor.widgetContent,
@@ -254,6 +266,7 @@
expandedMatchesParentHeight = true
showsOnlyActiveMedia = false
falsingProtectionNeeded = false
+ disablePagination = true
init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 55a24d0..d84dc20 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -270,11 +270,7 @@
private fun onOpenWidgetPicker() {
lifecycleScope.launch {
- communalViewModel.onOpenWidgetPicker(
- resources,
- packageManager,
- addWidgetActivityLauncher
- )
+ communalViewModel.onOpenWidgetPicker(resources, addWidgetActivityLauncher)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index caf5b01..e3f740e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -24,12 +24,11 @@
import static com.android.systemui.dreams.dagger.DreamModule.HOME_CONTROL_PANEL_DREAM_COMPONENT;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.graphics.drawable.ColorDrawable;
-import android.service.dreams.DreamActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -65,6 +64,7 @@
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -89,6 +89,8 @@
LifecycleOwner {
private static final String TAG = "DreamOverlayService";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final TaskMatcher DREAM_TYPE_MATCHER =
+ new TaskMatcher.TopActivityType(WindowConfiguration.ACTIVITY_TYPE_DREAM);
// The Context is used to construct the hosting constraint layout and child overlay views.
private final Context mContext;
@@ -141,10 +143,6 @@
private final TouchInsetManager mTouchInsetManager;
private final LifecycleOwner mLifecycleOwner;
-
-
- private ComponentName mCurrentBlockedGestureDreamActivityComponent;
-
private final ArrayList<Job> mFlows = new ArrayList<>();
/**
@@ -221,16 +219,122 @@
}
};
- private final DreamOverlayStateController.Callback mExitAnimationFinishedCallback =
- new DreamOverlayStateController.Callback() {
- @Override
- public void onStateChanged() {
- if (!mStateController.areExitAnimationsRunning()) {
- mStateController.removeCallback(mExitAnimationFinishedCallback);
- resetCurrentDreamOverlayLocked();
+ /**
+ * {@link ResetHandler} protects resetting {@link DreamOverlayService} by making sure reset
+ * requests are processed before subsequent actions proceed. Requests themselves are also
+ * ordered between each other as well to ensure actions are correctly sequenced.
+ */
+ private final class ResetHandler {
+ @FunctionalInterface
+ interface Callback {
+ void onComplete();
+ }
+
+ private record Info(Callback callback, String source) {}
+
+ private final ArrayList<Info> mPendingCallbacks = new ArrayList<>();
+
+ DreamOverlayStateController.Callback mStateCallback =
+ new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ process(true);
}
+ };
+
+ /**
+ * Called from places where there is no need to wait for the reset to complete. This still
+ * will defer the reset until it is okay to reset and also sequences the request with
+ * others.
+ */
+ public void reset(String source) {
+ reset(()-> {}, source);
+ }
+
+ /**
+ * Invoked to request a reset with a callback that will fire after reset if it is deferred.
+ *
+ * @return {@code true} if the reset happened immediately, {@code false} if it was deferred
+ * and will fire later, invoking the callback.
+ */
+ public boolean reset(Callback callback, String source) {
+ // Always add listener pre-emptively
+ if (mPendingCallbacks.isEmpty()) {
+ mStateController.addCallback(mStateCallback);
+ }
+
+ final Info info = new Info(callback, source);
+ mPendingCallbacks.add(info);
+ process(false);
+
+ boolean processed = !mPendingCallbacks.contains(info);
+
+ if (!processed) {
+ Log.d(TAG, "delayed resetting from: " + source);
+ }
+
+ return processed;
+ }
+
+ private void resetInternal() {
+ // This ensures the container view of the current dream is removed before
+ // the controller is potentially reset.
+ removeContainerViewFromParentLocked();
+
+ if (mStarted && mWindow != null) {
+ try {
+ mWindow.clearContentView();
+ mWindowManager.removeView(mWindow.getDecorView());
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Error removing decor view when resetting overlay", e);
}
- };
+ }
+
+ mStateController.setOverlayActive(false);
+ mStateController.setLowLightActive(false);
+ mStateController.setEntryAnimationsFinished(false);
+
+ if (mDreamOverlayContainerViewController != null) {
+ mDreamOverlayContainerViewController.destroy();
+ mDreamOverlayContainerViewController = null;
+ }
+
+ if (mTouchMonitor != null) {
+ mTouchMonitor.destroy();
+ mTouchMonitor = null;
+ }
+
+ mWindow = null;
+
+ // Always unregister the any set DreamActivity from being blocked from gestures.
+ mGestureInteractor.removeGestureBlockedMatcher(DREAM_TYPE_MATCHER,
+ GestureInteractor.Scope.Global);
+
+ mStarted = false;
+ }
+
+ private boolean canReset() {
+ return !mStateController.areExitAnimationsRunning();
+ }
+
+ private void process(boolean fromDelayedCallback) {
+ while (canReset() && !mPendingCallbacks.isEmpty()) {
+ final Info callbackInfo = mPendingCallbacks.removeFirst();
+ resetInternal();
+ callbackInfo.callback.onComplete();
+
+ if (fromDelayedCallback) {
+ Log.d(TAG, "reset overlay (delayed) for " + callbackInfo.source);
+ }
+ }
+
+ if (mPendingCallbacks.isEmpty()) {
+ mStateController.removeCallback(mStateCallback);
+ }
+ }
+ }
+
+ private final ResetHandler mResetHandler = new ResetHandler();
private final DreamOverlayStateController mStateController;
@@ -344,10 +448,8 @@
mExecutor.execute(() -> {
setLifecycleStateLocked(Lifecycle.State.DESTROYED);
-
- resetCurrentDreamOverlayLocked();
-
mDestroyed = true;
+ mResetHandler.reset("destroying");
});
mDispatcher.onServicePreSuperOnDestroy();
@@ -387,7 +489,10 @@
// Reset the current dream overlay before starting a new one. This can happen
// when two dreams overlap (briefly, for a smoother dream transition) and both
// dreams are bound to the dream overlay service.
- resetCurrentDreamOverlayLocked();
+ if (!mResetHandler.reset(() -> onStartDream(layoutParams),
+ "starting with dream already started")) {
+ return;
+ }
}
mDreamOverlayContainerViewController =
@@ -399,7 +504,7 @@
// If we are not able to add the overlay window, reset the overlay.
if (!addOverlayWindowLocked(layoutParams)) {
- resetCurrentDreamOverlayLocked();
+ mResetHandler.reset("couldn't add window while starting");
return;
}
@@ -420,7 +525,11 @@
mStarted = true;
updateRedirectWakeup();
- updateBlockedGestureDreamActivityComponent();
+
+ if (!isDreamInPreviewMode()) {
+ mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER,
+ GestureInteractor.Scope.Global);
+ }
}
private void updateRedirectWakeup() {
@@ -431,21 +540,9 @@
redirectWake(mCommunalAvailable && !glanceableHubAllowKeyguardWhenDreaming());
}
- private void updateBlockedGestureDreamActivityComponent() {
- // TODO(b/343815446): We should not be crafting this ActivityInfo ourselves. It should be
- // in a common place, Such as DreamActivity itself.
- final ActivityInfo info = new ActivityInfo();
- info.name = DreamActivity.class.getName();
- info.packageName = getDreamComponent().getPackageName();
- mCurrentBlockedGestureDreamActivityComponent = info.getComponentName();
-
- mGestureInteractor.addGestureBlockedActivity(mCurrentBlockedGestureDreamActivityComponent,
- GestureInteractor.Scope.Global);
- }
-
@Override
public void onEndDream() {
- resetCurrentDreamOverlayLocked();
+ mResetHandler.reset("ending dream");
}
@Override
@@ -576,49 +673,4 @@
Log.w(TAG, "Removing dream overlay container view parent!");
parentView.removeView(containerView);
}
-
- private void resetCurrentDreamOverlayLocked() {
- if (mStateController.areExitAnimationsRunning()) {
- mStateController.addCallback(mExitAnimationFinishedCallback);
- return;
- }
-
- // This ensures the container view of the current dream is removed before
- // the controller is potentially reset.
- removeContainerViewFromParentLocked();
-
- if (mStarted && mWindow != null) {
- try {
- mWindow.clearContentView();
- mWindowManager.removeView(mWindow.getDecorView());
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Error removing decor view when resetting overlay", e);
- }
- }
-
- mStateController.setOverlayActive(false);
- mStateController.setLowLightActive(false);
- mStateController.setEntryAnimationsFinished(false);
-
- if (mDreamOverlayContainerViewController != null) {
- mDreamOverlayContainerViewController.destroy();
- mDreamOverlayContainerViewController = null;
- }
-
- if (mTouchMonitor != null) {
- mTouchMonitor.destroy();
- mTouchMonitor = null;
- }
-
- mWindow = null;
-
- // Always unregister the any set DreamActivity from being blocked from gestures.
- if (mCurrentBlockedGestureDreamActivityComponent != null) {
- mGestureInteractor.removeGestureBlockedActivity(
- mCurrentBlockedGestureDreamActivityComponent, GestureInteractor.Scope.Global);
- mCurrentBlockedGestureDreamActivityComponent = null;
- }
-
- mStarted = false;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
index b654307..a20dfa5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
@@ -25,7 +25,6 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.inputdevice.data.repository.InputDeviceRepository
import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceAdded
-import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceChange
import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceRemoved
import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.FreshStart
import com.android.systemui.keyboard.data.model.Keyboard
@@ -78,24 +77,16 @@
inputDeviceRepository: InputDeviceRepository
) : KeyboardRepository {
- private val keyboardsChange: Flow<Pair<Collection<Int>, DeviceChange>> =
- inputDeviceRepository.deviceChange
- .map { (ids, change) -> ids.filter { id -> isPhysicalFullKeyboard(id) } to change }
- .filter { (_, change) ->
- when (change) {
- FreshStart -> true
- is DeviceAdded -> isPhysicalFullKeyboard(change.deviceId)
- is DeviceRemoved -> isPhysicalFullKeyboard(change.deviceId)
- }
- }
-
@FlowPreview
override val newlyConnectedKeyboard: Flow<Keyboard> =
- keyboardsChange
+ inputDeviceRepository.deviceChange
.flatMapConcat { (devices, operation) ->
when (operation) {
- FreshStart -> devices.asFlow()
- is DeviceAdded -> flowOf(operation.deviceId)
+ FreshStart -> devices.filter { id -> isPhysicalFullKeyboard(id) }.asFlow()
+ is DeviceAdded -> {
+ if (isPhysicalFullKeyboard(operation.deviceId)) flowOf(operation.deviceId)
+ else emptyFlow()
+ }
is DeviceRemoved -> emptyFlow()
}
}
@@ -103,8 +94,8 @@
.flowOn(backgroundDispatcher)
override val isAnyKeyboardConnected: Flow<Boolean> =
- keyboardsChange
- .map { (devices, _) -> devices.isNotEmpty() }
+ inputDeviceRepository.deviceChange
+ .map { (ids, _) -> ids.any { id -> isPhysicalFullKeyboard(id) } }
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 6e04133..4cf9ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -89,7 +89,7 @@
.filterRelevantKeyguardStateAnd { wakefulness -> wakefulness.isAwake() }
.debounce(50L)
.sample(
- startedKeyguardTransitionStep,
+ transitionInteractor.startedKeyguardTransitionStep,
wakeToGoneInteractor.canWakeDirectlyToGone,
)
.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 4666430..2434b29 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -117,7 +117,7 @@
if (SceneContainerFlag.isEnabled) return
scope.launch {
keyguardInteractor.primaryBouncerShowing
- .sample(startedKeyguardTransitionStep, ::Pair)
+ .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isBouncerShowing, lastStartedTransitionStep) = pair
if (
@@ -132,7 +132,7 @@
fun startToLockscreenOrGlanceableHubTransition(openHub: Boolean) {
scope.launch {
if (
- transitionInteractor.startedKeyguardState.replayCache.last() ==
+ transitionInteractor.startedKeyguardTransitionStep.value.to ==
KeyguardState.DREAMING
) {
if (powerInteractor.detailedWakefulness.value.isAwake()) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index cd3df07..228e01e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -134,16 +134,12 @@
.filterRelevantKeyguardState()
.sampleCombine(
internalTransitionInteractor.currentTransitionInfoInternal,
- finishedKeyguardState,
+ transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN),
keyguardInteractor.isActiveDreamLockscreenHosted,
)
.collect {
- (
- isAbleToDream,
- transitionInfo,
- finishedKeyguardState,
- isActiveDreamLockscreenHosted) ->
- val isOnLockscreen = finishedKeyguardState == KeyguardState.LOCKSCREEN
+ (isAbleToDream, transitionInfo, isOnLockscreen, isActiveDreamLockscreenHosted)
+ ->
val isTransitionInterruptible =
transitionInfo.to == KeyguardState.LOCKSCREEN &&
!invalidFromStates.contains(transitionInfo.from)
@@ -189,7 +185,7 @@
scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
shadeRepository.legacyShadeExpansion
.sampleCombine(
- startedKeyguardTransitionStep,
+ transitionInteractor.startedKeyguardTransitionStep,
internalTransitionInteractor.currentTransitionInfoInternal,
keyguardInteractor.statusBarState,
keyguardInteractor.isKeyguardDismissible,
@@ -334,7 +330,7 @@
listenForSleepTransition(
modeOnCanceledFromStartedStep = { startedStep ->
if (
- transitionInteractor.asleepKeyguardState.value == KeyguardState.AOD &&
+ keyguardInteractor.asleepKeyguardState.value == KeyguardState.AOD &&
startedStep.from == KeyguardState.AOD
) {
TransitionModeOnCanceled.REVERSE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index f9ab1bb..bde0f56 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -62,16 +62,16 @@
communalInteractor
.transitionProgressToScene(toScene)
.sample(
- transitionInteractor.startedKeyguardState,
+ transitionInteractor.startedKeyguardTransitionStep,
::Pair,
)
- .collect { (transitionProgress, lastStartedState) ->
+ .collect { (transitionProgress, lastStartedStep) ->
val id = transitionId
if (id == null) {
// No transition started.
if (
transitionProgress is CommunalTransitionProgressModel.Transition &&
- lastStartedState == fromState
+ lastStartedStep.to == fromState
) {
transitionId =
transitionRepository.startTransition(
@@ -84,7 +84,7 @@
)
}
} else {
- if (lastStartedState != toState) {
+ if (lastStartedStep.to != toState) {
return@collect
}
// An existing `id` means a transition is started, and calls to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index a96d7a8..f6f0cc5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -36,7 +36,9 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
@@ -409,6 +411,12 @@
}
}
+ /** Which keyguard state to use when the device goes to sleep. */
+ val asleepKeyguardState: StateFlow<KeyguardState> =
+ repository.isAodAvailable
+ .map { aodAvailable -> if (aodAvailable) AOD else DOZING }
+ .stateIn(applicationScope, SharingStarted.Eagerly, DOZING)
+
/**
* Whether the primary authentication is required for the given user due to lockdown or
* encryption after reboot.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
index 4a8ada7..505c749d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
@@ -48,7 +48,6 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -74,9 +73,7 @@
val isLongPressHandlingEnabled: StateFlow<Boolean> =
if (isFeatureEnabled()) {
combine(
- transitionInteractor.finishedKeyguardState.map {
- it == KeyguardState.LOCKSCREEN
- },
+ transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN),
repository.isQuickSettingsVisible,
) { isFullyTransitionedToLockScreen, isQuickSettingsVisible ->
isFullyTransitionedToLockScreen && !isQuickSettingsVisible
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 6ff369e..92e2a91 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -22,14 +22,16 @@
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -47,7 +49,6 @@
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
@@ -56,7 +57,6 @@
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.launch
/** Encapsulates business-logic related to the keyguard transitions. */
@@ -66,7 +66,6 @@
@Inject
constructor(
@Application val scope: CoroutineScope,
- private val keyguardRepository: KeyguardRepository,
private val repository: KeyguardTransitionRepository,
private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
private val fromPrimaryBouncerTransitionInteractor:
@@ -126,8 +125,10 @@
repository.transitions
.filter { it.transitionState != TransitionState.CANCELED }
.collect { step ->
- getTransitionValueFlow(step.from).emit(1f - step.value)
- getTransitionValueFlow(step.to).emit(step.value)
+ val value =
+ if (step.transitionState == TransitionState.FINISHED) 1f else step.value
+ getTransitionValueFlow(step.from).emit(1f - value)
+ getTransitionValueFlow(step.to).emit(value)
}
}
@@ -183,8 +184,14 @@
}
}
- fun transition(edge: Edge, edgeWithoutSceneContainer: Edge): Flow<TransitionStep> {
- return transition(if (SceneContainerFlag.isEnabled) edge else edgeWithoutSceneContainer)
+ fun transition(edge: Edge, edgeWithoutSceneContainer: Edge? = null): Flow<TransitionStep> {
+ return transition(
+ if (SceneContainerFlag.isEnabled || edgeWithoutSceneContainer == null) {
+ edge
+ } else {
+ edgeWithoutSceneContainer
+ }
+ )
}
/** Given an [edge], return a Flow to collect only relevant [TransitionStep]s. */
@@ -250,10 +257,10 @@
}
fun transitionValue(
- scene: SceneKey,
+ scene: SceneKey? = null,
stateWithoutSceneContainer: KeyguardState,
): Flow<Float> {
- return if (SceneContainerFlag.isEnabled) {
+ return if (SceneContainerFlag.isEnabled && scene != null) {
sceneInteractor.transitionProgress(scene)
} else {
transitionValue(stateWithoutSceneContainer)
@@ -277,73 +284,10 @@
}
/** The last [TransitionStep] with a [TransitionState] of STARTED */
- val startedKeyguardTransitionStep: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
-
- /** The destination state of the last [TransitionState.STARTED] transition. */
- @SuppressLint("SharedFlowCreation")
- val startedKeyguardState: SharedFlow<KeyguardState> =
- startedKeyguardTransitionStep
- .map { step -> step.to }
- .buffer(2, BufferOverflow.DROP_OLDEST)
- .shareIn(scope, SharingStarted.Eagerly, replay = 1)
-
- /** The from state of the last [TransitionState.STARTED] transition. */
- // TODO: is it performant to have several SharedFlows side by side instead of one?
- @SuppressLint("SharedFlowCreation")
- val startedKeyguardFromState: SharedFlow<KeyguardState> =
- startedKeyguardTransitionStep
- .map { step -> step.from }
- .buffer(2, BufferOverflow.DROP_OLDEST)
- .shareIn(scope, SharingStarted.Eagerly, replay = 1)
-
- /** Which keyguard state to use when the device goes to sleep. */
- val asleepKeyguardState: StateFlow<KeyguardState> =
- keyguardRepository.isAodAvailable
- .map { aodAvailable -> if (aodAvailable) AOD else DOZING }
- .stateIn(scope, SharingStarted.Eagerly, DOZING)
-
- /**
- * The last [KeyguardState] to which we [TransitionState.FINISHED] a transition.
- *
- * WARNING: This will NOT emit a value if a transition is CANCELED, and will also not emit a
- * value when a subsequent transition is STARTED. It will *only* emit once we have finally
- * FINISHED in a state. This can have unintuitive implications.
- *
- * For example, if we're transitioning from GONE -> DOZING, and that transition is CANCELED in
- * favor of a DOZING -> LOCKSCREEN transition, the FINISHED state is still GONE, and will remain
- * GONE throughout the DOZING -> LOCKSCREEN transition until the DOZING -> LOCKSCREEN transition
- * finishes (at which point we'll be FINISHED in LOCKSCREEN).
- *
- * Since there's no real limit to how many consecutive transitions can be canceled, it's even
- * possible for the FINISHED state to be the same as the STARTED state while still
- * transitioning.
- *
- * For example:
- * 1. We're finished in GONE.
- * 2. The user presses the power button, starting a GONE -> DOZING transition. We're still
- * FINISHED in GONE.
- * 3. The user changes their mind, pressing the power button to wake up; this starts a DOZING ->
- * LOCKSCREEN transition. We're still FINISHED in GONE.
- * 4. The user quickly swipes away the lockscreen prior to DOZING -> LOCKSCREEN finishing; this
- * starts a LOCKSCREEN -> GONE transition. We're still FINISHED in GONE, but we've also
- * STARTED a transition *to* GONE.
- * 5. We'll emit KeyguardState.GONE again once the transition finishes.
- *
- * If you just need to know when we eventually settle into a state, this flow is likely
- * sufficient. However, if you're having issues with state *during* transitions started after
- * one or more canceled transitions, you probably need to use [currentKeyguardState].
- */
- @SuppressLint("SharedFlowCreation")
- val finishedKeyguardState: SharedFlow<KeyguardState> =
+ val startedKeyguardTransitionStep: StateFlow<TransitionStep> =
repository.transitions
- .transform { step ->
- if (step.transitionState == TransitionState.FINISHED) {
- emit(step.to)
- }
- }
- .buffer(2, BufferOverflow.DROP_OLDEST)
- .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+ .filter { step -> step.transitionState == TransitionState.STARTED }
+ .stateIn(scope, SharingStarted.Eagerly, TransitionStep())
/**
* The [KeyguardState] we're currently in.
@@ -409,8 +353,7 @@
it.from
}
}
- .distinctUntilChanged()
- .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
+ .stateIn(scope, SharingStarted.Eagerly, OFF)
val isInTransition =
combine(
@@ -438,8 +381,8 @@
fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
AOD -> fromAodTransitionInteractor.get().dismissAod()
DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing()
- KeyguardState.OCCLUDED -> fromOccludedTransitionInteractor.get().dismissFromOccluded()
- KeyguardState.GONE ->
+ OCCLUDED -> fromOccludedTransitionInteractor.get().dismissFromOccluded()
+ GONE ->
Log.i(
TAG,
"Already transitioning to GONE; ignoring startDismissKeyguardTransition."
@@ -506,12 +449,13 @@
fun isFinishedIn(scene: SceneKey, stateWithoutSceneContainer: KeyguardState): Flow<Boolean> {
return if (SceneContainerFlag.isEnabled) {
- sceneInteractor.transitionState
- .map { it.isIdle(scene) || it.isTransitioning(from = scene) }
- .distinctUntilChanged()
- } else {
- isFinishedIn(stateWithoutSceneContainer)
- }
+ sceneInteractor.transitionState.map {
+ it.isIdle(scene) || it.isTransitioning(from = scene)
+ }
+ } else {
+ isFinishedIn(stateWithoutSceneContainer)
+ }
+ .distinctUntilChanged()
}
/** Whether we've FINISHED a transition to a state */
@@ -524,13 +468,11 @@
return currentKeyguardState.replayCache.last()
}
- fun getStartedFromState(): KeyguardState {
- return startedKeyguardFromState.replayCache.last()
- }
-
- fun getFinishedState(): KeyguardState {
- return finishedKeyguardState.replayCache.last()
- }
+ private val finishedKeyguardState: StateFlow<KeyguardState> =
+ repository.transitions
+ .filter { it.transitionState == TransitionState.FINISHED }
+ .map { it.to }
+ .stateIn(scope, SharingStarted.Eagerly, OFF)
companion object {
private val TAG = KeyguardTransitionInteractor::class.simpleName
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt
index 47818cb..e00e33d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt
@@ -45,6 +45,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -76,7 +77,7 @@
private val disableFlagsForUserId =
combine(
selectedUserInteractor.selectedUser,
- keyguardTransitionInteractor.startedKeyguardState,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep.map { it.to },
deviceConfigInteractor.property(
namespace = DeviceConfig.NAMESPACE_SYSTEMUI,
name = SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
index 906d586..e404f27 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
@@ -22,12 +22,12 @@
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.Utils.Companion.sample
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import javax.inject.Inject
/**
* Handles logic around the swipe to dismiss gesture, where the user swipes up on the dismissable
@@ -53,15 +53,15 @@
val dismissFling =
shadeRepository.currentFling
.sample(
- transitionInteractor.startedKeyguardState,
+ transitionInteractor.startedKeyguardTransitionStep,
keyguardInteractor.isKeyguardDismissible,
keyguardInteractor.statusBarState,
)
- .filter { (flingInfo, startedState, keyguardDismissable, statusBarState) ->
+ .filter { (flingInfo, startedStep, keyguardDismissable, statusBarState) ->
flingInfo != null &&
- !flingInfo.expand &&
- statusBarState != StatusBarState.SHADE_LOCKED &&
- startedState == KeyguardState.LOCKSCREEN &&
+ !flingInfo.expand &&
+ statusBarState != StatusBarState.SHADE_LOCKED &&
+ startedStep.to == KeyguardState.LOCKSCREEN &&
keyguardDismissable
}
.map { (flingInfo, _) -> flingInfo }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index d06ee64..ba12e93 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -32,7 +32,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -62,17 +61,6 @@
abstract fun start()
- /* Use background dispatcher for all [KeyguardTransitionInteractor] flows. Necessary because
- * the [sample] utility internally runs a collect on the Unconfined dispatcher, resulting
- * in continuations on the main thread. We don't want that for classes that inherit from this.
- */
- val startedKeyguardTransitionStep =
- transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher)
- // The following are MutableSharedFlows, and do not require flowOn
- val startedKeyguardState = transitionInteractor.startedKeyguardState
- val finishedKeyguardState = transitionInteractor.finishedKeyguardState
- val currentKeyguardState = transitionInteractor.currentKeyguardState
-
suspend fun startTransitionTo(
toState: KeyguardState,
animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
@@ -92,17 +80,6 @@
" $fromState. This should never happen - check currentTransitionInfoInternal" +
" or use filterRelevantKeyguardState before starting transitions."
)
-
- if (fromState == transitionInteractor.finishedKeyguardState.replayCache.last()) {
- Log.e(
- name,
- "This transition would not have been ignored prior to ag/26681239, since we " +
- "are FINISHED in $fromState (but have since started another transition). " +
- "If ignoring this transition has caused a regression, fix it by ensuring " +
- "that transitions are exclusively started from the most recently started " +
- "state."
- )
- }
return null
}
@@ -207,11 +184,11 @@
powerInteractor.isAsleep
.filter { isAsleep -> isAsleep }
.filterRelevantKeyguardState()
- .sample(startedKeyguardTransitionStep)
+ .sample(transitionInteractor.startedKeyguardTransitionStep)
.map(modeOnCanceledFromStartedStep)
.collect { modeOnCanceled ->
startTransitionTo(
- toState = transitionInteractor.asleepKeyguardState.value,
+ toState = keyguardInteractor.asleepKeyguardState.value,
modeOnCanceled = modeOnCanceled,
ownerReason = "Sleep transition triggered"
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 25b2b7c..ac87400 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -63,10 +63,13 @@
) {
private val defaultSurfaceBehindVisibility =
combine(
- transitionInteractor.finishedKeyguardState,
+ transitionInteractor.isFinishedIn(
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = KeyguardState.GONE
+ ),
wakeToGoneInteractor.canWakeDirectlyToGone,
- ) { finishedState, canWakeDirectlyToGone ->
- isSurfaceVisible(finishedState) || canWakeDirectlyToGone
+ ) { isOnGone, canWakeDirectlyToGone ->
+ isOnGone || canWakeDirectlyToGone
}
/**
@@ -196,18 +199,20 @@
edge = Edge.create(to = Scenes.Gone),
edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE)
),
- transitionInteractor.finishedKeyguardState,
+ transitionInteractor.isFinishedIn(
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = KeyguardState.GONE
+ ),
surfaceBehindInteractor.isAnimatingSurface,
notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
- ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning ->
+ ) { isInTransitionToGone, isOnGone, isAnimatingSurface, notifLaunchRunning ->
// Using the animation if we're animating it directly, or if the
// ActivityLaunchAnimator is in the process of animating it.
val animationsRunning = isAnimatingSurface || notifLaunchRunning
// We may still be animating the surface after the keyguard is fully GONE, since
// some animations (like the translation spring) are not tied directly to the
// transition step amount.
- isInTransitionToGone ||
- (finishedState == KeyguardState.GONE && animationsRunning)
+ isInTransitionToGone || (isOnGone && animationsRunning)
}
.distinctUntilChanged()
}
@@ -248,7 +253,7 @@
// transition. Same for waking directly to gone, due to the lockscreen being
// disabled or because the device was woken back up before the lock timeout
// duration elapsed.
- KeyguardState.lockscreenVisibleInState(KeyguardState.GONE)
+ false
} else if (canWakeDirectlyToGone) {
// Never show the lockscreen if we can wake directly to GONE. This means
// that the lock timeout has not yet elapsed, or the keyguard is disabled.
@@ -274,8 +279,7 @@
// *not* play the going away animation or related animations.
false
} else {
- // Otherwise, use the visibility of the current state.
- KeyguardState.lockscreenVisibleInState(currentState)
+ currentState != KeyguardState.GONE
}
}
.distinctUntilChanged()
@@ -302,10 +306,4 @@
!BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode)
}
.distinctUntilChanged()
-
- companion object {
- fun isSurfaceVisible(state: KeyguardState): Boolean {
- return !KeyguardState.lockscreenVisibleInState(state)
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
index b850095..ffd7812 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -116,7 +116,7 @@
} else {
val targetState =
if (idle.currentScene == Scenes.Lockscreen) {
- transitionInteractor.getStartedFromState()
+ transitionInteractor.startedKeyguardTransitionStep.value.from
} else {
UNDEFINED
}
@@ -155,7 +155,7 @@
val currentToState =
internalTransitionInteractor.currentTransitionInfoInternal.value.to
if (currentToState == UNDEFINED) {
- transitionKtfTo(transitionInteractor.getStartedFromState())
+ transitionKtfTo(transitionInteractor.startedKeyguardTransitionStep.value.from)
}
}
startTransitionFromLockscreen()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 24db3c2..080ddfd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -156,12 +156,6 @@
companion object {
- /** Whether the lockscreen is visible when we're FINISHED in the given state. */
- fun lockscreenVisibleInState(state: KeyguardState): Boolean {
- // TODO(b/349784682): Transform deprecated states for Flexiglass
- return state != GONE
- }
-
/**
* Whether the device is awake ([PowerInteractor.isAwake]) when we're FINISHED in the given
* keyguard state.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 06b76b3..87c32a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -53,10 +53,10 @@
) {
private val isShowingAodOrDozing: Flow<Boolean> =
combine(
- transitionInteractor.startedKeyguardState,
+ transitionInteractor.startedKeyguardTransitionStep,
transitionInteractor.transitionValue(KeyguardState.DOZING),
- ) { startedKeyguardState, dozingTransitionValue ->
- startedKeyguardState == KeyguardState.AOD || dozingTransitionValue == 1f
+ ) { startedKeyguardStep, dozingTransitionValue ->
+ startedKeyguardStep.to == KeyguardState.AOD || dozingTransitionValue == 1f
}
private fun getColor(usingBackgroundProtection: Boolean): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 5ce1b5e..d3bb4f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -83,8 +83,8 @@
private val intEvaluator = IntEvaluator()
private val floatEvaluator = FloatEvaluator()
private val showingAlternateBouncer: Flow<Boolean> =
- transitionInteractor.startedKeyguardState.map { keyguardState ->
- keyguardState == KeyguardState.ALTERNATE_BOUNCER
+ transitionInteractor.startedKeyguardTransitionStep.map { keyguardStep ->
+ keyguardStep.to == KeyguardState.ALTERNATE_BOUNCER
}
private val qsProgress: Flow<Float> = shadeInteractor.qsExpansion.onStart { emit(0f) }
private val shadeExpansion: Flow<Float> = shadeInteractor.shadeExpansion.onStart { emit(0f) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index c885c9a..fe4ebfe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -85,9 +85,7 @@
private val previewMode = MutableStateFlow(PreviewMode())
private val showingLockscreen: Flow<Boolean> =
- transitionInteractor.finishedKeyguardState.map { keyguardState ->
- keyguardState == KeyguardState.LOCKSCREEN
- }
+ transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN)
/** The only time the expansion is important is while lockscreen is actively displayed */
private val shadeExpansionAlpha =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index a96869d..ebdcaa0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -132,8 +132,8 @@
val burnInModel = _burnInModel.asStateFlow()
val burnInLayerVisibility: Flow<Int> =
- keyguardTransitionInteractor.startedKeyguardState
- .filter { it == AOD || it == LOCKSCREEN }
+ keyguardTransitionInteractor.startedKeyguardTransitionStep
+ .filter { it.to == AOD || it.to == LOCKSCREEN }
.map { VISIBLE }
val goneToAodTransition =
@@ -333,16 +333,17 @@
.transitionValue(LOCKSCREEN)
.map { it > 0f }
.onStart { emit(false) },
- keyguardTransitionInteractor.finishedKeyguardState.map {
- KeyguardState.lockscreenVisibleInState(it)
- },
+ keyguardTransitionInteractor.isFinishedIn(
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = GONE
+ ),
deviceEntryInteractor.isBypassEnabled,
areNotifsFullyHiddenAnimated(),
isPulseExpandingAnimated(),
) { flows ->
val goneToAodTransitionRunning = flows[0] as Boolean
val isOnLockscreen = flows[1] as Boolean
- val onKeyguard = flows[2] as Boolean
+ val isOnGone = flows[2] as Boolean
val isBypassEnabled = flows[3] as Boolean
val notifsFullyHidden = flows[4] as AnimatedValue<Boolean>
val pulseExpanding = flows[5] as AnimatedValue<Boolean>
@@ -352,8 +353,7 @@
// animation is playing, in which case we want them to be visible if we're
// animating in the AOD UI and will be switching to KEYGUARD shortly.
goneToAodTransitionRunning ||
- (!onKeyguard &&
- !screenOffAnimationController.shouldShowAodIconsWhenShade()) ->
+ (isOnGone && !screenOffAnimationController.shouldShowAodIconsWhenShade()) ->
AnimatedValue.NotAnimating(false)
else ->
zip(notifsFullyHidden, pulseExpanding) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 2b6c3c0..fcafd5e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -25,7 +25,6 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -60,7 +59,7 @@
private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
private val occlusionInteractor: SceneContainerOcclusionInteractor,
private val deviceEntryInteractor: DeviceEntryInteractor,
-) : SysUiViewModel, ExclusiveActivatable() {
+) : ExclusiveActivatable() {
@VisibleForTesting val clockSize = clockInteractor.clockSize
val isUdfpsVisible: Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index e64c614..c0b9efaa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -23,7 +23,9 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.transitions.FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -51,10 +53,18 @@
edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER),
)
+ private val alphaForAnimationStep: (Float) -> Float =
+ when {
+ SceneContainerFlag.isEnabled -> { step ->
+ 1f - Math.min((step / FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION), 1f)
+ }
+ else -> { step -> 1f - step }
+ }
+
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
- onStep = { 1f - it }
+ onStep = alphaForAnimationStep
)
val lockscreenAlpha: Flow<Float> = shortcutsAlpha
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt
index e45d537..708b408 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt
@@ -34,7 +34,9 @@
) {
/** When the last keyguard state transition started, was the shade fully expanded? */
private val lastStartedTransitionHadShadeFullyExpanded: Flow<Boolean> =
- transitionInteractor.startedKeyguardState.sample(shadeInteractor.isAnyFullyExpanded)
+ transitionInteractor.startedKeyguardTransitionStep.sample(
+ shadeInteractor.isAnyFullyExpanded
+ )
/**
* Decide which flow to use depending on the shade expansion state at the start of the last
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt
index bd3d40b..c1768a4 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt
@@ -19,6 +19,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
+import com.android.app.tracing.coroutines.traceCoroutine
/** Defines interface for classes that can be activated to do coroutine work. */
interface Activatable {
@@ -66,13 +67,19 @@
*
* If the [key] changes, the old [Activatable] is deactivated and a new one will be instantiated,
* activated, and returned.
+ *
+ * The [traceName] is used for coroutine performance tracing purposes. Please try to use a label
+ * that's unique enough and easy enough to find in code search; this should help correlate
+ * performance findings with actual code. One recommendation: prefer whole string literals instead
+ * of some complex concatenation or templating scheme.
*/
@Composable
fun <T : Activatable> rememberActivated(
+ traceName: String,
key: Any = Unit,
factory: () -> T,
): T {
val instance = remember(key) { factory() }
- LaunchedEffect(instance) { instance.activate() }
+ LaunchedEffect(instance) { traceCoroutine(traceName) { instance.activate() } }
return instance
}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
index 59ec2af..df1394b 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
@@ -19,6 +19,8 @@
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.StateFactoryMarker
+import com.android.app.tracing.coroutines.launch
+import com.android.app.tracing.coroutines.traceCoroutine
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
@@ -37,35 +39,55 @@
* }
* ```
*/
-class Hydrator : ExclusiveActivatable() {
+class Hydrator(
+ /**
+ * A name for performance tracing purposes.
+ *
+ * Please use a short string literal that's easy to find in code search. Try to avoid
+ * concatenation or templating.
+ */
+ private val traceName: String,
+) : ExclusiveActivatable() {
- private val children = mutableListOf<Activatable>()
+ private val children = mutableListOf<NamedActivatable>()
/**
- * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
+ * Returns a snapshot [State] that's kept up-to-date as long as its owner is active.
*
+ * @param traceName Used for coroutine performance tracing purposes. Please try to use a label
+ * that's unique enough and easy enough to find in code search; this should help correlate
+ * performance findings with actual code. One recommendation: prefer whole string literals
+ * instead of some complex concatenation or templating scheme.
* @param source The upstream [StateFlow] to collect from; values emitted to it will be
* automatically set on the returned [State].
*/
@StateFactoryMarker
fun <T> hydratedStateOf(
+ traceName: String,
source: StateFlow<T>,
): State<T> {
return hydratedStateOf(
+ traceName = traceName,
initialValue = source.value,
source = source,
)
}
/**
- * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
+ * Returns a snapshot [State] that's kept up-to-date as long as its owner is active.
*
+ * @param traceName Used for coroutine performance tracing purposes. Please try to use a label
+ * that's unique enough and easy enough to find in code search; this should help correlate
+ * performance findings with actual code. One recommendation: prefer whole string literals
+ * instead of some complex concatenation or templating scheme. Use `null` to disable
+ * performance tracing for this state.
* @param initialValue The first value to place on the [State]
* @param source The upstream [Flow] to collect from; values emitted to it will be automatically
* set on the returned [State].
*/
@StateFactoryMarker
fun <T> hydratedStateOf(
+ traceName: String?,
initialValue: T,
source: Flow<T>,
): State<T> {
@@ -73,18 +95,35 @@
val mutableState = mutableStateOf(initialValue)
children.add(
- object : ExclusiveActivatable() {
- override suspend fun onActivated(): Nothing {
- source.collect { mutableState.value = it }
- awaitCancellation()
- }
- }
+ NamedActivatable(
+ traceName = traceName,
+ activatable =
+ object : ExclusiveActivatable() {
+ override suspend fun onActivated(): Nothing {
+ source.collect { mutableState.value = it }
+ awaitCancellation()
+ }
+ },
+ )
)
return mutableState
}
override suspend fun onActivated() = coroutineScope {
- children.forEach { child -> launch { child.activate() } }
- awaitCancellation()
+ traceCoroutine(traceName) {
+ children.forEach { child ->
+ if (child.traceName != null) {
+ launch(spanName = child.traceName) { child.activatable.activate() }
+ } else {
+ launch { child.activatable.activate() }
+ }
+ }
+ awaitCancellation()
+ }
}
+
+ private data class NamedActivatable(
+ val traceName: String?,
+ val activatable: Activatable,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
index 29ffcbd..508b04e 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
@@ -20,36 +20,46 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
+import com.android.app.tracing.coroutines.traceCoroutine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
-/** Defines interface for all System UI view-models. */
-interface SysUiViewModel
-
/**
- * Returns a remembered [SysUiViewModel] of the type [T]. If the returned instance is also an
+ * Returns a remembered view-model of the type [T]. If the returned instance is also an
* [Activatable], it's automatically kept active until this composable leaves the composition; if
- * the [key] changes, the old [SysUiViewModel] is deactivated and a new one will be instantiated,
+ * the [key] changes, the old view-model is deactivated and a new one will be instantiated,
* activated, and returned.
+ *
+ * The [traceName] is used for coroutine performance tracing purposes. Please try to use a label
+ * that's unique enough and easy enough to find in code search; this should help correlate
+ * performance findings with actual code. One recommendation: prefer whole string literals instead
+ * of some complex concatenation or templating scheme.
*/
@Composable
-fun <T : SysUiViewModel> rememberViewModel(
+fun <T> rememberViewModel(
+ traceName: String,
key: Any = Unit,
factory: () -> T,
): T {
val instance = remember(key) { factory() }
if (instance is Activatable) {
- LaunchedEffect(instance) { instance.activate() }
+ LaunchedEffect(instance) { traceCoroutine(traceName) { instance.activate() } }
}
return instance
}
/**
- * Invokes [block] in a new coroutine with a new [SysUiViewModel] that is automatically activated
- * whenever `this` [View]'s Window's [WindowLifecycleState] is at least at
- * [minWindowLifecycleState], and is automatically canceled once that is no longer the case.
+ * Invokes [block] in a new coroutine with a new view-model that is automatically activated whenever
+ * `this` [View]'s Window's [WindowLifecycleState] is at least at [minWindowLifecycleState], and is
+ * automatically canceled once that is no longer the case.
+ *
+ * The [traceName] is used for coroutine performance tracing purposes. Please try to use a label
+ * that's unique enough and easy enough to find in code search; this should help correlate
+ * performance findings with actual code. One recommendation: prefer whole string literals instead
+ * of some complex concatenation or templating scheme.
*/
-suspend fun <T : SysUiViewModel> View.viewModel(
+suspend fun <T> View.viewModel(
+ traceName: String,
minWindowLifecycleState: WindowLifecycleState,
factory: () -> T,
block: suspend CoroutineScope.(T) -> Unit,
@@ -57,7 +67,7 @@
repeatOnWindowLifecycle(minWindowLifecycleState) {
val instance = factory()
if (instance is Activatable) {
- launch { instance.activate() }
+ launch { traceCoroutine(traceName) { instance.activate() } }
}
block(instance)
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index ba3c1d2..ed76646 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -382,6 +382,16 @@
return factory.create("MediaLog", 20);
}
+ /**
+ * Provides a buffer for media device changes
+ */
+ @Provides
+ @SysUISingleton
+ @MediaDeviceLog
+ public static LogBuffer providesMediaDeviceLogBuffer(LogBufferFactory factory) {
+ return factory.create("MediaDeviceLog", 50);
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaDeviceLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaDeviceLog.kt
new file mode 100644
index 0000000..06bd269
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaDeviceLog.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.log.dagger
+
+import com.android.systemui.log.LogBuffer
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for [com.android.systemui.media.controls.domain.pipeline.MediaDeviceLogger] */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class MediaDeviceLog
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
index 70189b7..378a147 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -30,6 +30,7 @@
import androidx.media.utils.MediaConstants
import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_COMPACT_ACTIONS
import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_NOTIFICATION_ACTIONS
+import com.android.systemui.media.controls.shared.MediaControlDrawables
import com.android.systemui.media.controls.shared.model.MediaAction
import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.plugins.ActivityStarter
@@ -58,14 +59,13 @@
val playOrPause =
if (isConnectingState(state.state)) {
// Spinner needs to be animating to render anything. Start it here.
- val drawable =
- context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+ val drawable = MediaControlDrawables.getProgress(context)
(drawable as Animatable).start()
MediaAction(
drawable,
null, // no action to perform when clicked
context.getString(R.string.controls_media_button_connecting),
- context.getDrawable(R.drawable.ic_media_connecting_container),
+ MediaControlDrawables.getConnecting(context),
// Specify a rebind id to prevent the spinner from restarting on later binds.
com.android.internal.R.drawable.progress_small_material
)
@@ -153,23 +153,23 @@
return when (action) {
PlaybackState.ACTION_PLAY -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_play),
+ MediaControlDrawables.getPlayIcon(context),
{ controller.transportControls.play() },
context.getString(R.string.controls_media_button_play),
- context.getDrawable(R.drawable.ic_media_play_container)
+ MediaControlDrawables.getPlayBackground(context)
)
}
PlaybackState.ACTION_PAUSE -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_pause),
+ MediaControlDrawables.getPauseIcon(context),
{ controller.transportControls.pause() },
context.getString(R.string.controls_media_button_pause),
- context.getDrawable(R.drawable.ic_media_pause_container)
+ MediaControlDrawables.getPauseBackground(context)
)
}
PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_prev),
+ MediaControlDrawables.getPrevIcon(context),
{ controller.transportControls.skipToPrevious() },
context.getString(R.string.controls_media_button_prev),
null
@@ -177,7 +177,7 @@
}
PlaybackState.ACTION_SKIP_TO_NEXT -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_next),
+ MediaControlDrawables.getNextIcon(context),
{ controller.transportControls.skipToNext() },
context.getString(R.string.controls_media_button_next),
null
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 916f8b2..415449f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -16,9 +16,8 @@
package com.android.systemui.media.controls.domain.pipeline
+import android.annotation.MainThread
import android.annotation.SuppressLint
-import android.app.ActivityOptions
-import android.app.BroadcastOptions
import android.app.Notification
import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
import android.app.PendingIntent
@@ -39,7 +38,6 @@
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
-import android.graphics.drawable.Animatable
import android.graphics.drawable.Icon
import android.media.MediaDescription
import android.media.MediaMetadata
@@ -47,7 +45,6 @@
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.net.Uri
-import android.os.Handler
import android.os.Parcelable
import android.os.Process
import android.os.UserHandle
@@ -63,6 +60,7 @@
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -92,7 +90,6 @@
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.util.Assert
@@ -137,7 +134,7 @@
@Background private val backgroundExecutor: Executor,
@Main private val uiExecutor: Executor,
@Main private val foregroundExecutor: DelayableExecutor,
- @Main private val handler: Handler,
+ @Main private val mainDispatcher: CoroutineDispatcher,
private val mediaControllerFactory: MediaControllerFactory,
private val broadcastDispatcher: BroadcastDispatcher,
private val dumpManager: DumpManager,
@@ -152,6 +149,7 @@
private val smartspaceManager: SmartspaceManager?,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val mediaDataRepository: MediaDataRepository,
+ private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
) : CoreStartable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -217,7 +215,7 @@
threadFactory: ThreadFactory,
@Main uiExecutor: Executor,
@Main foregroundExecutor: DelayableExecutor,
- @Main handler: Handler,
+ @Main mainDispatcher: CoroutineDispatcher,
mediaControllerFactory: MediaControllerFactory,
dumpManager: DumpManager,
broadcastDispatcher: BroadcastDispatcher,
@@ -230,6 +228,7 @@
smartspaceManager: SmartspaceManager?,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
mediaDataRepository: MediaDataRepository,
+ mediaDataLoader: dagger.Lazy<MediaDataLoader>,
) : this(
context,
applicationScope,
@@ -239,7 +238,7 @@
threadFactory.buildExecutorOnNewThread(TAG),
uiExecutor,
foregroundExecutor,
- handler,
+ mainDispatcher,
mediaControllerFactory,
broadcastDispatcher,
dumpManager,
@@ -254,6 +253,7 @@
smartspaceManager,
keyguardUpdateMonitor,
mediaDataRepository,
+ mediaDataLoader,
)
private val appChangeReceiver =
@@ -436,16 +436,30 @@
logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
logger.logResumeMediaAdded(appUid, packageName, instanceId)
}
- backgroundExecutor.execute {
- loadMediaDataInBgForResumption(
- userId,
- desc,
- action,
- token,
- appName,
- appIntent,
- packageName
- )
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ applicationScope.launch {
+ loadMediaDataForResumption(
+ userId,
+ desc,
+ action,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ }
+ } else {
+ backgroundExecutor.execute {
+ loadMediaDataInBgForResumption(
+ userId,
+ desc,
+ action,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ }
}
}
@@ -471,7 +485,13 @@
oldKey: String?,
isNewlyActiveEntry: Boolean = false,
) {
- backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ applicationScope.launch {
+ loadMediaDataWithLoader(key, sbn, oldKey, isNewlyActiveEntry)
+ }
+ } else {
+ backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+ }
}
/** Add a listener for internal events. */
@@ -646,6 +666,75 @@
}
}
+ private suspend fun loadMediaDataForResumption(
+ userId: Int,
+ desc: MediaDescription,
+ resumeAction: Runnable,
+ token: MediaSession.Token,
+ appName: String,
+ appIntent: PendingIntent,
+ packageName: String
+ ) =
+ withContext(backgroundDispatcher) {
+ val lastActive = systemClock.elapsedRealtime()
+ val currentEntry = mediaDataRepository.mediaEntries.value[packageName]
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+ val result =
+ mediaDataLoader
+ .get()
+ .loadMediaDataForResumption(
+ userId,
+ desc,
+ resumeAction,
+ currentEntry,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ if (result == null || desc.title.isNullOrBlank()) {
+ Log.d(TAG, "No MediaData result for resumption")
+ mediaDataRepository.removeMediaEntry(packageName)
+ return@withContext
+ }
+
+ val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+ withContext(mainDispatcher) {
+ onMediaDataLoaded(
+ packageName,
+ null,
+ MediaData(
+ userId = userId,
+ initialized = true,
+ app = result.appName,
+ appIcon = null,
+ artist = result.artist,
+ song = result.song,
+ artwork = result.artworkIcon,
+ actions = result.actionIcons,
+ actionsToShowInCompact = result.actionsToShowInCompact,
+ semanticActions = result.semanticActions,
+ packageName = packageName,
+ token = result.token,
+ clickIntent = result.clickIntent,
+ device = result.device,
+ active = false,
+ resumeAction = resumeAction,
+ resumption = true,
+ notificationKey = packageName,
+ hasCheckedForResume = true,
+ lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
+ instanceId = instanceId,
+ appUid = result.appUid,
+ isExplicit = result.isExplicit,
+ resumeProgress = result.resumeProgress,
+ )
+ )
+ }
+ }
+
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun loadMediaDataInBgForResumption(
userId: Int,
desc: MediaDescription,
@@ -730,6 +819,82 @@
}
}
+ private suspend fun loadMediaDataWithLoader(
+ key: String,
+ sbn: StatusBarNotification,
+ oldKey: String?,
+ isNewlyActiveEntry: Boolean = false,
+ ) =
+ withContext(backgroundDispatcher) {
+ val lastActive = systemClock.elapsedRealtime()
+ val result = mediaDataLoader.get().loadMediaData(key, sbn)
+ if (result == null) {
+ Log.d(TAG, "No result from loadMediaData")
+ return@withContext
+ }
+
+ val currentEntry = mediaDataRepository.mediaEntries.value[key]
+ val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+ val resumeAction: Runnable? = currentEntry?.resumeAction
+ val hasCheckedForResume = currentEntry?.hasCheckedForResume == true
+ val active = currentEntry?.active ?: true
+
+ // We need to log the correct media added.
+ if (isNewlyActiveEntry) {
+ logSingleVsMultipleMediaAdded(result.appUid, sbn.packageName, instanceId)
+ logger.logActiveMediaAdded(
+ result.appUid,
+ sbn.packageName,
+ instanceId,
+ result.playbackLocation
+ )
+ } else if (result.playbackLocation != currentEntry?.playbackLocation) {
+ logger.logPlaybackLocationChange(
+ result.appUid,
+ sbn.packageName,
+ instanceId,
+ result.playbackLocation
+ )
+ }
+
+ withContext(mainDispatcher) {
+ onMediaDataLoaded(
+ key,
+ oldKey,
+ MediaData(
+ userId = sbn.normalizedUserId,
+ initialized = true,
+ app = result.appName,
+ appIcon = result.appIcon,
+ artist = result.artist,
+ song = result.song,
+ artwork = result.artworkIcon,
+ actions = result.actionIcons,
+ actionsToShowInCompact = result.actionsToShowInCompact,
+ semanticActions = result.semanticActions,
+ packageName = sbn.packageName,
+ token = result.token,
+ clickIntent = result.clickIntent,
+ device = result.device,
+ active = active,
+ resumeAction = resumeAction,
+ playbackLocation = result.playbackLocation,
+ notificationKey = key,
+ hasCheckedForResume = hasCheckedForResume,
+ isPlaying = result.isPlaying,
+ isClearable = !sbn.isOngoing,
+ lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
+ instanceId = instanceId,
+ appUid = result.appUid,
+ isExplicit = result.isExplicit,
+ )
+ )
+ }
+ }
+
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
fun loadMediaDataInBg(
key: String,
sbn: StatusBarNotification,
@@ -843,7 +1008,7 @@
var actionsToShowCollapsed: List<Int> = emptyList()
val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user)
if (semanticActions == null) {
- val actions = createActionsFromNotification(sbn)
+ val actions = createActionsFromNotification(context, activityStarter, sbn)
actionIcons = actions.first
actionsToShowCollapsed = actions.second
}
@@ -926,6 +1091,7 @@
}
}
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
try {
return context.packageManager.getApplicationInfo(packageName, 0)
@@ -935,6 +1101,7 @@
return null
}
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
if (name != null) {
@@ -948,78 +1115,6 @@
}
}
- /** Generate action buttons based on notification actions */
- private fun createActionsFromNotification(
- sbn: StatusBarNotification
- ): Pair<List<MediaAction>, List<Int>> {
- val notif = sbn.notification
- val actionIcons: MutableList<MediaAction> = ArrayList()
- val actions = notif.actions
- var actionsToShowCollapsed =
- notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList()
- ?: mutableListOf()
- if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
- Log.e(
- TAG,
- "Too many compact actions for ${sbn.key}," +
- "limiting to first $MAX_COMPACT_ACTIONS"
- )
- actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
- }
-
- if (actions != null) {
- for ((index, action) in actions.withIndex()) {
- if (index == MAX_NOTIFICATION_ACTIONS) {
- Log.w(
- TAG,
- "Too many notification actions for ${sbn.key}," +
- " limiting to first $MAX_NOTIFICATION_ACTIONS"
- )
- break
- }
- if (action.getIcon() == null) {
- if (DEBUG) Log.i(TAG, "No icon for action $index ${action.title}")
- actionsToShowCollapsed.remove(index)
- continue
- }
- val runnable =
- if (action.actionIntent != null) {
- Runnable {
- if (action.actionIntent.isActivity) {
- activityStarter.startPendingIntentDismissingKeyguard(
- action.actionIntent
- )
- } else if (action.isAuthenticationRequired()) {
- activityStarter.dismissKeyguardThenExecute(
- {
- var result = sendPendingIntent(action.actionIntent)
- result
- },
- {},
- true
- )
- } else {
- sendPendingIntent(action.actionIntent)
- }
- }
- } else {
- null
- }
- val mediaActionIcon =
- if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
- Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
- } else {
- action.getIcon()
- }
- .setTint(themeText)
- .loadDrawable(context)
- val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null)
- actionIcons.add(mediaAction)
- }
- }
- return Pair(actionIcons, actionsToShowCollapsed)
- }
-
/**
* Generates action button info for this media session based on the PlaybackState
*
@@ -1036,174 +1131,14 @@
controller: MediaController,
user: UserHandle
): MediaButton? {
- val state = controller.playbackState
- if (state == null || !mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
+ if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
return null
}
-
- // First, check for standard actions
- val playOrPause =
- if (isConnectingState(state.state)) {
- // Spinner needs to be animating to render anything. Start it here.
- val drawable = MediaControlDrawables.getProgress(context)
- (drawable as Animatable).start()
- MediaAction(
- drawable,
- null, // no action to perform when clicked
- context.getString(R.string.controls_media_button_connecting),
- MediaControlDrawables.getConnecting(context),
- // Specify a rebind id to prevent the spinner from restarting on later binds.
- com.android.internal.R.drawable.progress_small_material
- )
- } else if (isPlayingState(state.state)) {
- getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
- } else {
- getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
- }
- val prevButton =
- getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
- val nextButton =
- getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT)
-
- // Then, create a way to build any custom actions that will be needed
- val customActions =
- state.customActions
- .asSequence()
- .filterNotNull()
- .map { getCustomAction(packageName, controller, it) }
- .iterator()
- fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
-
- // Finally, assign the remaining button slots: play/pause A B C D
- // A = previous, else custom action (if not reserved)
- // B = next, else custom action (if not reserved)
- // C and D are always custom actions
- val reservePrev =
- controller.extras?.getBoolean(
- MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
- ) == true
- val reserveNext =
- controller.extras?.getBoolean(
- MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
- ) == true
-
- val prevOrCustom =
- if (prevButton != null) {
- prevButton
- } else if (!reservePrev) {
- nextCustomAction()
- } else {
- null
- }
-
- val nextOrCustom =
- if (nextButton != null) {
- nextButton
- } else if (!reserveNext) {
- nextCustomAction()
- } else {
- null
- }
-
- return MediaButton(
- playOrPause,
- nextOrCustom,
- prevOrCustom,
- nextCustomAction(),
- nextCustomAction(),
- reserveNext,
- reservePrev
- )
- }
-
- /**
- * Create a [MediaAction] for a given action and media session
- *
- * @param controller MediaController for the session
- * @param stateActions The actions included with the session's [PlaybackState]
- * @param action A [PlaybackState.Actions] value representing what action to generate. One of:
- * ```
- * [PlaybackState.ACTION_PLAY]
- * [PlaybackState.ACTION_PAUSE]
- * [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
- * [PlaybackState.ACTION_SKIP_TO_NEXT]
- * @return
- * ```
- *
- * A [MediaAction] with correct values set, or null if the state doesn't support it
- */
- private fun getStandardAction(
- controller: MediaController,
- stateActions: Long,
- @PlaybackState.Actions action: Long
- ): MediaAction? {
- if (!includesAction(stateActions, action)) {
- return null
- }
-
- return when (action) {
- PlaybackState.ACTION_PLAY -> {
- MediaAction(
- MediaControlDrawables.getPlayIcon(context),
- { controller.transportControls.play() },
- context.getString(R.string.controls_media_button_play),
- MediaControlDrawables.getPlayBackground(context)
- )
- }
- PlaybackState.ACTION_PAUSE -> {
- MediaAction(
- MediaControlDrawables.getPauseIcon(context),
- { controller.transportControls.pause() },
- context.getString(R.string.controls_media_button_pause),
- MediaControlDrawables.getPauseBackground(context)
- )
- }
- PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
- MediaAction(
- MediaControlDrawables.getPrevIcon(context),
- { controller.transportControls.skipToPrevious() },
- context.getString(R.string.controls_media_button_prev),
- null
- )
- }
- PlaybackState.ACTION_SKIP_TO_NEXT -> {
- MediaAction(
- MediaControlDrawables.getNextIcon(context),
- { controller.transportControls.skipToNext() },
- context.getString(R.string.controls_media_button_next),
- null
- )
- }
- else -> null
- }
- }
-
- /** Check whether the actions from a [PlaybackState] include a specific action */
- private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
- if (
- (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
- (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)
- ) {
- return true
- }
- return (stateActions and action != 0L)
- }
-
- /** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
- private fun getCustomAction(
- packageName: String,
- controller: MediaController,
- customAction: PlaybackState.CustomAction
- ): MediaAction {
- return MediaAction(
- Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
- { controller.transportControls.sendCustomAction(customAction, customAction.extras) },
- customAction.name,
- null
- )
+ return createActionsFromState(context, packageName, controller)
}
/** Load a bitmap from the various Art metadata URIs */
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
for (uri in ART_URIS) {
val uriString = metadata.getString(uri)
@@ -1218,21 +1153,6 @@
return null
}
- private fun sendPendingIntent(intent: PendingIntent): Boolean {
- return try {
- val options = BroadcastOptions.makeBasic()
- options.setInteractive(true)
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- )
- intent.send(options.toBundle())
- true
- } catch (e: PendingIntent.CanceledException) {
- Log.d(TAG, "Intent canceled", e)
- false
- }
- }
-
/** Returns a bitmap if the user can access the given URI, else null */
private fun loadBitmapFromUriForUser(
uri: Uri,
@@ -1313,6 +1233,7 @@
)
}
+ @MainThread
fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) =
traceSection("MediaDataProcessor#onMediaDataLoaded") {
Assert.isMainThread()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLogger.kt
new file mode 100644
index 0000000..f886166
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLogger.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.media.controls.domain.pipeline
+
+import android.media.session.MediaController
+import com.android.settingslib.media.MediaDevice
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.MediaDeviceLog
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import javax.inject.Inject
+
+/** A [LogBuffer] for media device changes */
+class MediaDeviceLogger @Inject constructor(@MediaDeviceLog private val buffer: LogBuffer) {
+
+ fun logBroadcastEvent(event: String, reason: Int, broadcastId: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = event
+ int1 = reason
+ int2 = broadcastId
+ },
+ { "$str1, reason = $int1, broadcastId = $int2" }
+ )
+ }
+
+ fun logBroadcastEvent(event: String, reason: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = event
+ int1 = reason
+ },
+ { "$str1, reason = $int1" }
+ )
+ }
+
+ fun logBroadcastMetadataChanged(broadcastId: Int, metadata: String) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = broadcastId
+ str1 = metadata
+ },
+ { "onBroadcastMetadataChanged, broadcastId = $int1, metadata = $str1" }
+ )
+ }
+
+ fun logNewDeviceName(name: String?) {
+ buffer.log(TAG, LogLevel.DEBUG, { str1 = name }, { "New device name $str1" })
+ }
+
+ fun logLocalDevice(sassDevice: MediaDeviceData?, connectedDevice: MediaDeviceData?) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = sassDevice?.name?.toString()
+ str2 = connectedDevice?.name?.toString()
+ },
+ { "Local device: $str1 or $str2" }
+ )
+ }
+
+ fun logRemoteDevice(routingSessionName: CharSequence?, connectedDevice: MediaDeviceData?) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = routingSessionName?.toString()
+ str2 = connectedDevice?.name?.toString()
+ },
+ { "Remote device: $str1 or $str2 or unknown" }
+ )
+ }
+
+ fun logDeviceName(
+ device: MediaDevice?,
+ controller: MediaController?,
+ routingSessionName: CharSequence?,
+ selectedRouteName: CharSequence?
+ ) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = "device $device, controller: $controller"
+ str2 = routingSessionName?.toString()
+ str3 = selectedRouteName?.toString()
+ },
+ { "$str1, routingSession $str2 or selected route $str3" }
+ )
+ }
+
+ companion object {
+ private const val TAG = "MediaDeviceLog"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index a193f7f..49b53c2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -71,6 +71,7 @@
private val localBluetoothManager: Lazy<LocalBluetoothManager?>,
@Main private val fgExecutor: Executor,
@Background private val bgExecutor: Executor,
+ private val logger: MediaDeviceLogger,
) : MediaDataManager.Listener {
private val listeners: MutableSet<Listener> = mutableSetOf()
@@ -281,59 +282,38 @@
}
override fun onBroadcastStarted(reason: Int, broadcastId: Int) {
- if (DEBUG) {
- Log.d(TAG, "onBroadcastStarted(), reason = $reason , broadcastId = $broadcastId")
- }
+ logger.logBroadcastEvent("onBroadcastStarted", reason, broadcastId)
updateCurrent()
}
override fun onBroadcastStartFailed(reason: Int) {
- if (DEBUG) {
- Log.d(TAG, "onBroadcastStartFailed(), reason = $reason")
- }
+ logger.logBroadcastEvent("onBroadcastStartFailed", reason)
}
override fun onBroadcastMetadataChanged(
broadcastId: Int,
metadata: BluetoothLeBroadcastMetadata
) {
- if (DEBUG) {
- Log.d(
- TAG,
- "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
- "metadata = $metadata"
- )
- }
+ logger.logBroadcastMetadataChanged(broadcastId, metadata.toString())
updateCurrent()
}
override fun onBroadcastStopped(reason: Int, broadcastId: Int) {
- if (DEBUG) {
- Log.d(TAG, "onBroadcastStopped(), reason = $reason , broadcastId = $broadcastId")
- }
+ logger.logBroadcastEvent("onBroadcastStopped", reason, broadcastId)
updateCurrent()
}
override fun onBroadcastStopFailed(reason: Int) {
- if (DEBUG) {
- Log.d(TAG, "onBroadcastStopFailed(), reason = $reason")
- }
+ logger.logBroadcastEvent("onBroadcastStopFailed", reason)
}
override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {
- if (DEBUG) {
- Log.d(TAG, "onBroadcastUpdated(), reason = $reason , broadcastId = $broadcastId")
- }
+ logger.logBroadcastEvent("onBroadcastUpdated", reason, broadcastId)
updateCurrent()
}
override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {
- if (DEBUG) {
- Log.d(
- TAG,
- "onBroadcastUpdateFailed(), reason = $reason , " + "broadcastId = $broadcastId"
- )
- }
+ logger.logBroadcastEvent("onBroadcastUpdateFailed", reason, broadcastId)
}
override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
@@ -381,12 +361,16 @@
name = context.getString(R.string.media_seamless_other_device),
showBroadcastButton = false
)
+ logger.logRemoteDevice(routingSession?.name, connectedDevice)
} else {
// Prefer SASS if available when playback is local.
- activeDevice = getSassDevice() ?: connectedDevice
+ val sassDevice = getSassDevice()
+ activeDevice = sassDevice ?: connectedDevice
+ logger.logLocalDevice(sassDevice, connectedDevice)
}
current = activeDevice ?: EMPTY_AND_DISABLED_MEDIA_DEVICE_DATA
+ logger.logNewDeviceName(current?.name?.toString())
} else {
val aboutToConnect = aboutToConnectDeviceOverride
if (
@@ -407,9 +391,7 @@
val enabled = device != null && (controller == null || routingSession != null)
val name = getDeviceName(device, routingSession)
- if (DEBUG) {
- Log.d(TAG, "new device name $name")
- }
+ logger.logNewDeviceName(name)
current =
MediaDeviceData(
enabled,
@@ -463,14 +445,12 @@
): String? {
val selectedRoutes = routingSession?.let { mr2manager.get().getSelectedRoutes(it) }
- if (DEBUG) {
- Log.d(
- TAG,
- "device is $device, controller $controller," +
- " routingSession ${routingSession?.name}" +
- " or ${selectedRoutes?.firstOrNull()?.name}"
- )
- }
+ logger.logDeviceName(
+ device,
+ controller,
+ routingSession?.name,
+ selectedRoutes?.firstOrNull()?.name
+ )
if (controller == null) {
// In resume state, we don't have a controller - just use the device name
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 8fd578e..bf9ef8c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -49,7 +49,6 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -105,12 +104,14 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -166,6 +167,9 @@
/** Is the player currently visible (at the end of the transformation */
private var playersVisible: Boolean = false
+ /** Are we currently disabling pagination only allowing one media session to show */
+ private var currentlyDisablePagination: Boolean = false
+
/**
* The desired location where we'll be at the end of the transformation. Usually this matches
* the end location, except when we're still waiting on a state update call.
@@ -329,6 +333,11 @@
private val controllerById = mutableMapOf<String, MediaViewController>()
private val commonViewModels = mutableListOf<MediaCommonViewModel>()
+ private val isOnGone =
+ keyguardTransitionInteractor
+ .isFinishedIn(Scenes.Gone, GONE)
+ .stateIn(applicationScope, SharingStarted.Eagerly, true)
+
init {
dumpManager.registerDumpable(TAG, this)
mediaFrame = inflateMediaCarousel()
@@ -910,9 +919,7 @@
if (SceneContainerFlag.isEnabled) {
!deviceEntryInteractor.isDeviceEntered.value
} else {
- KeyguardState.lockscreenVisibleInState(
- keyguardTransitionInteractor.getFinishedState()
- )
+ !isOnGone.value
}
return !allowMediaPlayerOnLockScreen && isOnLockscreen
}
@@ -1355,14 +1362,20 @@
val endShowsActive = hostStates[currentEndLocation]?.showsOnlyActiveMedia ?: true
val startShowsActive =
hostStates[currentStartLocation]?.showsOnlyActiveMedia ?: endShowsActive
+ val startDisablePagination = hostStates[currentStartLocation]?.disablePagination ?: false
+ val endDisablePagination = hostStates[currentEndLocation]?.disablePagination ?: false
+
if (
currentlyShowingOnlyActive != endShowsActive ||
+ currentlyDisablePagination != endDisablePagination ||
((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
- startShowsActive != endShowsActive)
+ (startShowsActive != endShowsActive ||
+ startDisablePagination != endDisablePagination))
) {
// Whenever we're transitioning from between differing states or the endstate differs
// we reset the translation
currentlyShowingOnlyActive = endShowsActive
+ currentlyDisablePagination = endDisablePagination
mediaCarouselScrollHandler.resetTranslation(animate = true)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index 91050c8..09a6181 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
@@ -44,6 +44,7 @@
lateinit var hostView: UniqueObjectHostView
var location: Int = -1
private set
+
private var visibleChangedListeners: ArraySet<(Boolean) -> Unit> = ArraySet()
private val tmpLocationOnScreen: IntArray = intArrayOf(0, 0)
@@ -287,6 +288,15 @@
changedListener?.invoke()
}
+ override var disablePagination: Boolean = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ changedListener?.invoke()
+ }
+
private var lastDisappearHash = disappearParameters.hashCode()
/** A listener for all changes. This won't be copied over when invoking [copy] */
@@ -303,6 +313,7 @@
mediaHostState.visible = visible
mediaHostState.disappearParameters = disappearParameters.deepCopy()
mediaHostState.falsingProtectionNeeded = falsingProtectionNeeded
+ mediaHostState.disablePagination = disablePagination
return mediaHostState
}
@@ -331,6 +342,9 @@
if (!disappearParameters.equals(other.disappearParameters)) {
return false
}
+ if (disablePagination != other.disablePagination) {
+ return false
+ }
return true
}
@@ -342,6 +356,7 @@
result = 31 * result + showsOnlyActiveMedia.hashCode()
result = 31 * result + if (visible) 1 else 2
result = 31 * result + disappearParameters.hashCode()
+ result = 31 * result + disablePagination.hashCode()
return result
}
}
@@ -400,6 +415,12 @@
*/
var disappearParameters: DisappearParameters
+ /**
+ * Whether pagination should be disabled for this host, meaning that when there are multiple
+ * media sessions, only the first one will appear.
+ */
+ var disablePagination: Boolean
+
/** Get a copy of this view state, deepcopying all appropriate members */
fun copy(): MediaHostState
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 6f82d5d..173a964 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -80,6 +80,7 @@
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.Flags;
import com.android.systemui.shared.rotation.RotationPolicyUtil;
import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.shared.system.QuickStepContract;
@@ -501,9 +502,11 @@
Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, gestureDefault ? 1 : 0,
mUserTracker.getUserId()) != 0;
+ boolean supportsSwipeGesture = QuickStepContract.isGesturalMode(mNavBarMode)
+ || (QuickStepContract.isLegacyMode(mNavBarMode) && Flags.threeButtonCornerSwipe());
mAssistantAvailable = assistantAvailableForUser
&& mAssistantTouchGestureEnabled
- && QuickStepContract.isGesturalMode(mNavBarMode);
+ && supportsSwipeGesture;
dispatchAssistantEventUpdate(mAssistantAvailable, mLongPressHomeEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 0f82e02..6bd880d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -78,6 +78,7 @@
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
import com.android.systemui.plugins.PluginListener;
@@ -474,9 +475,14 @@
} else {
String[] gestureBlockingActivities = resources.getStringArray(resId);
for (String gestureBlockingActivity : gestureBlockingActivities) {
- mGestureInteractor.addGestureBlockedActivity(
- ComponentName.unflattenFromString(gestureBlockingActivity),
- GestureInteractor.Scope.Local);
+ final ComponentName component =
+ ComponentName.unflattenFromString(gestureBlockingActivity);
+
+ if (component != null) {
+ mGestureInteractor.addGestureBlockedMatcher(
+ new TaskMatcher.TopActivityComponent(component),
+ GestureInteractor.Scope.Local);
+ }
}
}
} catch (NameNotFoundException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt
index 8f35343..c1f238a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt
@@ -16,10 +16,9 @@
package com.android.systemui.navigationbar.gestural.data.respository
-import android.content.ComponentName
-import android.util.ArraySet
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
@@ -28,36 +27,43 @@
/** A repository for storing gesture related information */
interface GestureRepository {
- /** A {@link StateFlow} tracking activities currently blocked from gestures. */
- val gestureBlockedActivities: StateFlow<Set<ComponentName>>
+ /** A {@link StateFlow} tracking matchers that can block gestures. */
+ val gestureBlockedMatchers: StateFlow<Set<TaskMatcher>>
- /** Adds an activity to be blocked from gestures. */
- suspend fun addGestureBlockedActivity(activity: ComponentName)
+ /** Adds a matcher to determine whether a gesture should be blocked. */
+ suspend fun addGestureBlockedMatcher(matcher: TaskMatcher)
- /** Removes an activity from being blocked from gestures. */
- suspend fun removeGestureBlockedActivity(activity: ComponentName)
+ /** Removes a matcher from blocking from gestures. */
+ suspend fun removeGestureBlockedMatcher(matcher: TaskMatcher)
}
@SysUISingleton
class GestureRepositoryImpl
@Inject
constructor(@Main private val mainDispatcher: CoroutineDispatcher) : GestureRepository {
- private val _gestureBlockedActivities = MutableStateFlow<Set<ComponentName>>(ArraySet())
+ private val _gestureBlockedMatchers = MutableStateFlow<Set<TaskMatcher>>(emptySet())
- override val gestureBlockedActivities: StateFlow<Set<ComponentName>>
- get() = _gestureBlockedActivities
+ override val gestureBlockedMatchers: StateFlow<Set<TaskMatcher>>
+ get() = _gestureBlockedMatchers
- override suspend fun addGestureBlockedActivity(activity: ComponentName) =
+ override suspend fun addGestureBlockedMatcher(matcher: TaskMatcher) =
withContext(mainDispatcher) {
- _gestureBlockedActivities.emit(
- _gestureBlockedActivities.value.toMutableSet().apply { add(activity) }
- )
+ val existingMatchers = _gestureBlockedMatchers.value
+ if (existingMatchers.contains(matcher)) {
+ return@withContext
+ }
+
+ _gestureBlockedMatchers.value = existingMatchers.toMutableSet().apply { add(matcher) }
}
- override suspend fun removeGestureBlockedActivity(activity: ComponentName) =
+ override suspend fun removeGestureBlockedMatcher(matcher: TaskMatcher) =
withContext(mainDispatcher) {
- _gestureBlockedActivities.emit(
- _gestureBlockedActivities.value.toMutableSet().apply { remove(activity) }
- )
+ val existingMatchers = _gestureBlockedMatchers.value
+ if (!existingMatchers.contains(matcher)) {
+ return@withContext
+ }
+
+ _gestureBlockedMatchers.value =
+ existingMatchers.toMutableSet().apply { remove(matcher) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt
index 6182878..96386e5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt
@@ -16,7 +16,6 @@
package com.android.systemui.navigationbar.gestural.domain
-import android.content.ComponentName
import com.android.app.tracing.coroutines.flow.flowOn
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -25,7 +24,6 @@
import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.shared.system.TaskStackChangeListener
import com.android.systemui.shared.system.TaskStackChangeListeners
-import com.android.systemui.util.kotlin.combine
import com.android.systemui.util.kotlin.emitOnStart
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
@@ -60,7 +58,7 @@
Global
}
- private val _localGestureBlockedActivities = MutableStateFlow<Set<ComponentName>>(setOf())
+ private val _localGestureBlockedMatchers = MutableStateFlow<Set<TaskMatcher>>(setOf())
private val _topActivity =
conflatedCallbackFlow {
@@ -79,53 +77,47 @@
.mapLatest { getTopActivity() }
.distinctUntilChanged()
- private suspend fun getTopActivity(): ComponentName? =
+ private suspend fun getTopActivity(): TaskInfo? =
withContext(backgroundCoroutineContext) {
- val runningTask = activityManagerWrapper.runningTask
- runningTask?.topActivity
+ activityManagerWrapper.runningTask?.let { TaskInfo(it.topActivity, it.activityType) }
}
val topActivityBlocked =
combine(
_topActivity,
- gestureRepository.gestureBlockedActivities,
- _localGestureBlockedActivities.asStateFlow()
- ) { activity, global, local ->
- activity != null && (global + local).contains(activity)
+ gestureRepository.gestureBlockedMatchers,
+ _localGestureBlockedMatchers.asStateFlow()
+ ) { runningTask, global, local ->
+ runningTask != null && (global + local).any { it.matches(runningTask) }
}
- /**
- * Adds an {@link Activity} to be blocked based on component when the topmost, focused {@link
- * Activity}.
- */
- fun addGestureBlockedActivity(activity: ComponentName, gestureScope: Scope) {
+ /** Adds an [TaskMatcher] to decide whether gestures should be blocked. */
+ fun addGestureBlockedMatcher(matcher: TaskMatcher, gestureScope: Scope) {
scope.launch {
when (gestureScope) {
Scope.Local -> {
- _localGestureBlockedActivities.emit(
- _localGestureBlockedActivities.value.toMutableSet().apply { add(activity) }
+ _localGestureBlockedMatchers.emit(
+ _localGestureBlockedMatchers.value.toMutableSet().apply { add(matcher) }
)
}
Scope.Global -> {
- gestureRepository.addGestureBlockedActivity(activity)
+ gestureRepository.addGestureBlockedMatcher(matcher)
}
}
}
}
- /** Removes an {@link Activity} from being blocked from gestures. */
- fun removeGestureBlockedActivity(activity: ComponentName, gestureScope: Scope) {
+ /** Removes a gesture from deciding whether gestures should be blocked */
+ fun removeGestureBlockedMatcher(matcher: TaskMatcher, gestureScope: Scope) {
scope.launch {
when (gestureScope) {
Scope.Local -> {
- _localGestureBlockedActivities.emit(
- _localGestureBlockedActivities.value.toMutableSet().apply {
- remove(activity)
- }
+ _localGestureBlockedMatchers.emit(
+ _localGestureBlockedMatchers.value.toMutableSet().apply { remove(matcher) }
)
}
Scope.Global -> {
- gestureRepository.removeGestureBlockedActivity(activity)
+ gestureRepository.removeGestureBlockedMatcher(matcher)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/TaskMatcher.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/TaskMatcher.kt
new file mode 100644
index 0000000..d62b2c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/TaskMatcher.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.navigationbar.gestural.domain
+
+import android.content.ComponentName
+
+/**
+ * A simple data class for capturing details around a task. Implements equality to ensure changes
+ * can be identified between emitted values.
+ */
+data class TaskInfo(val topActivity: ComponentName?, val topActivityType: Int) {
+ override fun equals(other: Any?): Boolean {
+ return other is TaskInfo &&
+ other.topActivityType == topActivityType &&
+ other.topActivity == topActivity
+ }
+}
+
+/**
+ * [TaskMatcher] provides a way to identify a task based on particular attributes, such as the top
+ * activity type or component name.
+ */
+sealed interface TaskMatcher {
+ fun matches(info: TaskInfo): Boolean
+
+ class TopActivityType(private val type: Int) : TaskMatcher {
+ override fun matches(info: TaskInfo): Boolean {
+ return info.topActivity != null && info.topActivityType == type
+ }
+ }
+
+ class TopActivityComponent(private val component: ComponentName) : TaskMatcher {
+ override fun matches(info: TaskInfo): Boolean {
+ return component == info.topActivity
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index c3274b7..c39ff55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -32,6 +32,7 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.Composable
@@ -403,30 +404,40 @@
onDispose { qqsVisible.value = false }
}
Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) {
- QuickQuickSettings(
- viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel,
- modifier =
- Modifier.onGloballyPositioned { coordinates ->
- val (leftFromRoot, topFromRoot) = coordinates.positionInRoot().round()
- val (width, height) = coordinates.size
- qqsPositionOnRoot.set(
- leftFromRoot,
- topFromRoot,
- leftFromRoot + width,
- topFromRoot + height
- )
- }
- .layout { measurable, constraints ->
- val placeable = measurable.measure(constraints)
- qqsHeight.value = placeable.height
+ Box(modifier = Modifier.fillMaxWidth()) {
+ val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle()
+ if (qsEnabled) {
+ QuickQuickSettings(
+ viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel,
+ modifier =
+ Modifier.onGloballyPositioned { coordinates ->
+ val (leftFromRoot, topFromRoot) =
+ coordinates.positionInRoot().round()
+ val (width, height) = coordinates.size
+ qqsPositionOnRoot.set(
+ leftFromRoot,
+ topFromRoot,
+ leftFromRoot + width,
+ topFromRoot + height
+ )
+ }
+ .layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ qqsHeight.value = placeable.height
- layout(placeable.width, placeable.height) { placeable.place(0, 0) }
- }
- .padding(top = { qqsPadding })
- .collapseExpandSemanticAction(
- stringResource(id = R.string.accessibility_quick_settings_expand)
- )
- )
+ layout(placeable.width, placeable.height) {
+ placeable.place(0, 0)
+ }
+ }
+ .padding(top = { qqsPadding })
+ .collapseExpandSemanticAction(
+ stringResource(
+ id = R.string.accessibility_quick_settings_expand
+ )
+ )
+ )
+ }
+ }
Spacer(modifier = Modifier.weight(1f))
}
}
@@ -441,18 +452,23 @@
stringResource(id = R.string.accessibility_quick_settings_collapse)
)
) {
- Box(modifier = Modifier.fillMaxSize().weight(1f)) {
- Column {
- Spacer(modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() })
- ShadeBody(viewModel = viewModel.containerViewModel)
+ val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle()
+ if (qsEnabled) {
+ Box(modifier = Modifier.fillMaxSize().weight(1f)) {
+ Column {
+ Spacer(
+ modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() }
+ )
+ ShadeBody(viewModel = viewModel.containerViewModel)
+ }
}
- }
- QuickSettingsTheme {
- FooterActions(
- viewModel = viewModel.footerActionsViewModel,
- qsVisibilityLifecycleOwner = this@QSFragmentCompose,
- modifier = Modifier.sysuiResTag("qs_footer_actions")
- )
+ QuickSettingsTheme {
+ FooterActions(
+ viewModel = viewModel.footerActionsViewModel,
+ qsVisibilityLifecycleOwner = this@QSFragmentCompose,
+ modifier = Modifier.sysuiResTag("qs_footer_actions")
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index df77878..16133f4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -132,13 +132,17 @@
_stackScrollerOverscrolling.value = value
}
- private val qsDisabled =
+ /**
+ * Whether QS is enabled by policy. This is normally true, except when it's disabled by some
+ * policy. See [DisableFlagsRepository].
+ */
+ val qsEnabled =
disableFlagsRepository.disableFlags
- .map { !it.isQuickSettingsEnabled() }
+ .map { it.isQuickSettingsEnabled() }
.stateIn(
lifecycleScope,
SharingStarted.WhileSubscribed(),
- !disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled()
+ disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled()
)
private val _showCollapsedOnKeyguard = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
index 12f3c9c..93bf73f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.qs.ui.viewmodel
import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -44,7 +43,7 @@
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
val mediaCarouselInteractor: MediaCarouselInteractor,
-) : SysUiViewModel {
+) {
val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
index cb99be4..924a939 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
@@ -18,7 +18,6 @@
package com.android.systemui.qs.ui.viewmodel
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -35,7 +34,7 @@
constructor(
val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory,
val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-) : SysUiViewModel {
+) {
@AssistedFactory
interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
index ea61bd3..04620d6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
@@ -118,7 +118,7 @@
get() =
when (this) {
is ObservableTransitionState.Idle -> currentScene.canBeOccluded
- is ObservableTransitionState.Transition.ChangeCurrentScene ->
+ is ObservableTransitionState.Transition.ChangeScene ->
fromScene.canBeOccluded && toScene.canBeOccluded
is ObservableTransitionState.Transition.ReplaceOverlay,
is ObservableTransitionState.Transition.ShowOrHideOverlay ->
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index bdb148a..a2142b6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -131,7 +131,7 @@
.map { state ->
when (state) {
is ObservableTransitionState.Idle -> null
- is ObservableTransitionState.Transition.ChangeCurrentScene -> state.toScene
+ is ObservableTransitionState.Transition.ChangeScene -> state.toScene
is ObservableTransitionState.Transition.ShowOrHideOverlay,
is ObservableTransitionState.Transition.ReplaceOverlay ->
TODO("b/359173565: Handle overlay transitions")
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index cc46216..7eb48d6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -479,7 +479,7 @@
switchToScene(
targetSceneKey = Scenes.Lockscreen,
loggingReason = "device is starting to sleep",
- sceneState = keyguardTransitionInteractor.asleepKeyguardState.value,
+ sceneState = keyguardInteractor.asleepKeyguardState.value,
)
} else {
val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
index ec743ba..d1629c7 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
@@ -112,7 +112,7 @@
// It
// happens only when unlocking or when dismissing a dismissible lockscreen.
val isTransitioningAwayFromKeyguard =
- transitionState is ObservableTransitionState.Transition.ChangeCurrentScene &&
+ transitionState is ObservableTransitionState.Transition.ChangeScene &&
transitionState.fromScene.isKeyguard() &&
transitionState.toScene == Scenes.Gone
@@ -120,7 +120,7 @@
val isCurrentSceneShade = currentScene.isShade()
// This is true when moving into one of the shade scenes when a non-shade scene.
val isTransitioningToShade =
- transitionState is ObservableTransitionState.Transition.ChangeCurrentScene &&
+ transitionState is ObservableTransitionState.Transition.ChangeScene &&
!transitionState.fromScene.isShade() &&
transitionState.toScene.isShade()
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 b870a4e..ec6513a 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
@@ -104,6 +104,7 @@
view.repeatWhenAttached {
view.viewModel(
+ traceName = "SceneWindowRootViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
factory = { viewModelFactory.create(motionEventHandlerReceiver) },
) { viewModel ->
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
index 9144f16d..368e4fa 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
@@ -19,7 +19,6 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.SysUiViewModel
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -33,7 +32,7 @@
* need to worry about resetting the value of [actions] when the view-model is deactivated/canceled,
* this base class takes care of it.
*/
-abstract class SceneActionsViewModel : SysUiViewModel, ExclusiveActivatable() {
+abstract class SceneActionsViewModel : ExclusiveActivatable() {
private val _actions = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
/**
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index e2947d3..a73c39d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -26,7 +26,6 @@
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.logger.SceneLogger
@@ -47,14 +46,15 @@
private val powerInteractor: PowerInteractor,
private val logger: SceneLogger,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
-) : SysUiViewModel, ExclusiveActivatable() {
+) : ExclusiveActivatable() {
+
/** The scene that should be rendered. */
val currentScene: StateFlow<SceneKey> = sceneInteractor.currentScene
- private val hydrator = Hydrator()
+ private val hydrator = Hydrator("SceneContainerViewModel.hydrator")
/** Whether the container is visible. */
- val isVisible: Boolean by hydrator.hydratedStateOf(sceneInteractor.isVisible)
+ val isVisible: Boolean by hydrator.hydratedStateOf("isVisible", sceneInteractor.isVisible)
override suspend fun onActivated(): Nothing {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
index cc470a6..05f19ef 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
@@ -67,7 +67,7 @@
@Background CoroutineDispatcher backgroundDispatcher,
@Background Handler handler
) {
- int startingUser = userManager.getBootUser().getIdentifier();
+ int startingUser = ActivityManager.getCurrentUser();
UserTrackerImpl tracker = new UserTrackerImpl(context, featureFlagsProvider, userManager,
iActivityManager, dumpManager, appScope, backgroundDispatcher, handler);
tracker.initialize(startingUser);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
index 706797d..52bc25d 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
@@ -20,7 +20,6 @@
import android.util.Log
import android.view.View
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.res.R
import com.android.systemui.settings.brightness.BrightnessSliderController
import com.android.systemui.settings.brightness.MirrorController
@@ -37,7 +36,7 @@
private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
@Main private val resources: Resources,
val sliderControllerFactory: BrightnessSliderController.Factory,
-) : SysUiViewModel, MirrorController {
+) : MirrorController {
private val tempPosition = IntArray(2)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
index 7d67121..e276f88 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -64,7 +64,7 @@
0f
}
)
- is ObservableTransitionState.Transition.ChangeCurrentScene ->
+ is ObservableTransitionState.Transition.ChangeScene ->
when {
state.fromScene == Scenes.Gone ->
if (state.toScene.isExpandable()) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
index f270e82..9c4bf1f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
@@ -26,6 +26,7 @@
import com.android.systemui.util.kotlin.BooleanFlowOperators.any
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
/** Models UI state for the shade window. */
@SysUISingleton
@@ -42,9 +43,11 @@
val isKeyguardOccluded: Flow<Boolean> =
listOf(
// Finished in state...
- keyguardTransitionInteractor.isFinishedIn(OCCLUDED),
- keyguardTransitionInteractor.isFinishedIn(DREAMING),
- keyguardTransitionInteractor.isFinishedIn(Scenes.Communal, GLANCEABLE_HUB),
+ keyguardTransitionInteractor.transitionValue(OCCLUDED).map { it == 1f },
+ keyguardTransitionInteractor.transitionValue(DREAMING).map { it == 1f },
+ keyguardTransitionInteractor.transitionValue(Scenes.Communal, GLANCEABLE_HUB).map {
+ it == 1f
+ },
// ... or transitions between those states
keyguardTransitionInteractor.isInTransition(Edge.create(OCCLUDED, DREAMING)),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
index 25ae44e..c210fc5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
@@ -18,7 +18,6 @@
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
@@ -37,8 +36,10 @@
*/
class OverlayShadeViewModel
@AssistedInject
-constructor(private val sceneInteractor: SceneInteractor, shadeInteractor: ShadeInteractor) :
- SysUiViewModel, ExclusiveActivatable() {
+constructor(
+ private val sceneInteractor: SceneInteractor,
+ shadeInteractor: ShadeInteractor,
+) : ExclusiveActivatable() {
private val _backgroundScene = MutableStateFlow(Scenes.Lockscreen)
/** The scene to show in the background when the overlay shade is open. */
val backgroundScene: StateFlow<SceneKey> = _backgroundScene.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index edfe79a..c8abb1f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -25,7 +25,6 @@
import android.provider.Settings
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItem
@@ -66,7 +65,7 @@
private val privacyChipInteractor: PrivacyChipInteractor,
private val clockInteractor: ShadeHeaderClockInteractor,
private val broadcastDispatcher: BroadcastDispatcher,
-) : SysUiViewModel, ExclusiveActivatable() {
+) : ExclusiveActivatable() {
/** True if there is exactly one mobile connection. */
val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
index f0f2a65..7a79a22 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
@@ -21,7 +21,6 @@
import androidx.lifecycle.LifecycleOwner
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -62,7 +61,7 @@
private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val sceneInteractor: SceneInteractor,
-) : SysUiViewModel, ExclusiveActivatable() {
+) : ExclusiveActivatable() {
val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
index 173ff37..be733d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
@@ -16,14 +16,24 @@
package com.android.systemui.statusbar.chips
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModel
+import dagger.Binds
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
@Module
abstract class StatusBarChipsModule {
+ @Binds
+ @IntoMap
+ @ClassKey(DemoRonChipViewModel::class)
+ abstract fun binds(impl: DemoRonChipViewModel): CoreStartable
+
companion object {
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 18ea0b4..e825258 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -69,7 +69,7 @@
state.notificationIconView
)
} else {
- OngoingActivityChipModel.ChipIcon.Basic(phoneIcon)
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
}
// This block mimics OngoingCallController#updateChip.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index cf4e707..d4ad6ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -190,7 +190,7 @@
): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown.Timer(
icon =
- OngoingActivityChipModel.ChipIcon.Basic(
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(
Icon.Resource(
CAST_TO_OTHER_DEVICE_ICON,
// This string is "Casting screen"
@@ -215,7 +215,7 @@
private fun createIconOnlyCastChip(deviceName: String?): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown.IconOnly(
icon =
- OngoingActivityChipModel.ChipIcon.Basic(
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(
Icon.Resource(
CAST_TO_OTHER_DEVICE_ICON,
// This string is just "Casting"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt
new file mode 100644
index 0000000..84ccaec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
+
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.graphics.drawable.Drawable
+import com.android.systemui.CoreStartable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.commandline.ParseableCommand
+import com.android.systemui.statusbar.commandline.Type
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A view model that will emit demo RON chips (rich ongoing notification chips) from [chip] based on
+ * adb commands sent by the user.
+ *
+ * Example adb commands:
+ *
+ * To show a chip with the SysUI icon and custom text:
+ * ```
+ * adb shell cmd statusbar demo-ron -p com.android.systemui -t 10min
+ * ```
+ *
+ * To hide the chip:
+ * ```
+ * adb shell cmd statusbar demo-ron --hide
+ * ```
+ *
+ * See [DemoRonCommand] for more information on the adb command spec.
+ */
+@SysUISingleton
+class DemoRonChipViewModel
+@Inject
+constructor(
+ private val commandRegistry: CommandRegistry,
+ private val packageManager: PackageManager,
+ private val systemClock: SystemClock,
+) : OngoingActivityChipViewModel, CoreStartable {
+ override fun start() {
+ commandRegistry.registerCommand("demo-ron") { DemoRonCommand() }
+ }
+
+ private val _chip =
+ MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden())
+ override val chip: StateFlow<OngoingActivityChipModel> = _chip.asStateFlow()
+
+ private inner class DemoRonCommand : ParseableCommand("demo-ron") {
+ private val packageName: String? by
+ param(
+ longName = "packageName",
+ shortName = "p",
+ description = "The package name for the demo RON app",
+ valueParser = Type.String,
+ )
+
+ private val text: String? by
+ param(
+ longName = "text",
+ shortName = "t",
+ description = "Text to display in the chip",
+ valueParser = Type.String,
+ )
+
+ private val hide by
+ flag(
+ longName = "hide",
+ description = "Hides any existing demo RON chip",
+ )
+
+ override fun execute(pw: PrintWriter) {
+ if (!StatusBarRonChips.isEnabled) {
+ pw.println(
+ "Error: com.android.systemui.status_bar_ron_chips must be enabled " +
+ "before using this demo feature"
+ )
+ return
+ }
+
+ if (hide) {
+ _chip.value = OngoingActivityChipModel.Hidden()
+ return
+ }
+
+ val currentPackageName = packageName
+ if (currentPackageName == null) {
+ pw.println("--packageName (or -p) must be included")
+ return
+ }
+
+ val appIcon = getAppIcon(currentPackageName)
+ if (appIcon == null) {
+ pw.println("Package $currentPackageName could not be found")
+ return
+ }
+
+ val currentText = text
+ if (currentText != null) {
+ _chip.value =
+ OngoingActivityChipModel.Shown.Text(
+ icon = appIcon,
+ // TODO(b/361346412): Include a demo with a custom color theme.
+ colors = ColorsModel.Themed,
+ text = currentText,
+ )
+ } else {
+ _chip.value =
+ OngoingActivityChipModel.Shown.Timer(
+ icon = appIcon,
+ // TODO(b/361346412): Include a demo with a custom color theme.
+ colors = ColorsModel.Themed,
+ startTimeMs = systemClock.elapsedRealtime(),
+ onClickListener = null,
+ )
+ }
+ }
+
+ private fun getAppIcon(packageName: String): OngoingActivityChipModel.ChipIcon? {
+ lateinit var iconDrawable: Drawable
+ try {
+ // Note: For the real implementation, we should check if applicationInfo exists
+ // before fetching the icon, so that we either don't show the chip or show a good
+ // backup icon in case the app info can't be found for some reason.
+ iconDrawable = packageManager.getApplicationIcon(packageName)
+ } catch (e: NameNotFoundException) {
+ return null
+ }
+ return OngoingActivityChipModel.ChipIcon.FullColorAppIcon(
+ Icon.Loaded(drawable = iconDrawable, contentDescription = null),
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt
new file mode 100644
index 0000000..4ef1909
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ron.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the status bar RON chips flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object StatusBarRonChips {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_STATUS_BAR_RON_CHIPS
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.statusBarRonChips()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 9e6cacb..eb73521 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -80,7 +80,7 @@
is ScreenRecordChipModel.Recording -> {
OngoingActivityChipModel.Shown.Timer(
icon =
- OngoingActivityChipModel.ChipIcon.Basic(
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(
Icon.Resource(
ICON,
ContentDescription.Resource(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 7897f93..d99a916 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -110,7 +110,7 @@
): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown.Timer(
icon =
- OngoingActivityChipModel.ChipIcon.Basic(
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(
Icon.Resource(
SHARE_TO_APP_ICON,
ContentDescription.Resource(R.string.share_to_app_chip_accessibility_label),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 26a2f91..62622a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -89,6 +89,16 @@
) : Shown(icon = null, colors, onClickListener = null) {
override val logName = "Shown.Countdown"
}
+
+ /** This chip shows the specified [text] in the chip. */
+ data class Text(
+ override val icon: ChipIcon,
+ override val colors: ColorsModel,
+ // TODO(b/361346412): Enforce a max length requirement?
+ val text: String,
+ ) : Shown(icon, colors, onClickListener = null) {
+ override val logName = "Shown.Text"
+ }
}
/** Represents an icon to show on the chip. */
@@ -106,7 +116,13 @@
}
}
- /** The icon is a basic resource or drawable icon that System UI created internally. */
- data class Basic(val impl: Icon) : ChipIcon
+ /**
+ * This icon is a single color and it came from basic resource or drawable icon that System
+ * UI created internally.
+ */
+ data class SingleColorIcon(val impl: Icon) : ChipIcon
+
+ /** This icon is an app icon in full color (so it should not get tinted in any way). */
+ data class FullColorAppIcon(val impl: Icon) : ChipIcon
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index b0d897d..04c4516 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -23,6 +23,8 @@
import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModel
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModel
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -51,6 +53,7 @@
shareToAppChipViewModel: ShareToAppChipViewModel,
castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel,
callChipViewModel: CallChipViewModel,
+ demoRonChipViewModel: DemoRonChipViewModel,
@StatusBarChipsLog private val logger: LogBuffer,
) {
private enum class ChipType {
@@ -58,6 +61,8 @@
ShareToApp,
CastToOtherDevice,
Call,
+ /** A demo of a RON chip (rich ongoing notification chip), used just for testing. */
+ DemoRon,
}
/** Model that helps us internally track the various chip states from each of the types. */
@@ -78,6 +83,7 @@
val shareToApp: OngoingActivityChipModel.Hidden,
val castToOtherDevice: OngoingActivityChipModel.Hidden,
val call: OngoingActivityChipModel.Hidden,
+ val demoRon: OngoingActivityChipModel.Hidden,
) : InternalChipModel
}
@@ -87,7 +93,8 @@
shareToAppChipViewModel.chip,
castToOtherDeviceChipViewModel.chip,
callChipViewModel.chip,
- ) { screenRecord, shareToApp, castToOtherDevice, call ->
+ demoRonChipViewModel.chip,
+ ) { screenRecord, shareToApp, castToOtherDevice, call, demoRon ->
logger.log(
TAG,
LogLevel.INFO,
@@ -98,7 +105,15 @@
},
{ "Chips: ScreenRecord=$str1 > ShareToApp=$str2 > CastToOther=$str3..." },
)
- logger.log(TAG, LogLevel.INFO, { str1 = call.logName }, { "... > Call=$str1" })
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = call.logName
+ str2 = demoRon.logName
+ },
+ { "... > Call=$str1 > DemoRon=$str2" }
+ )
// This `when` statement shows the priority order of the chips.
when {
// Screen recording also activates the media projection APIs, so whenever the
@@ -113,17 +128,23 @@
InternalChipModel.Shown(ChipType.CastToOtherDevice, castToOtherDevice)
call is OngoingActivityChipModel.Shown ->
InternalChipModel.Shown(ChipType.Call, call)
+ demoRon is OngoingActivityChipModel.Shown -> {
+ StatusBarRonChips.assertInNewMode()
+ InternalChipModel.Shown(ChipType.DemoRon, demoRon)
+ }
else -> {
// We should only get here if all chip types are hidden
check(screenRecord is OngoingActivityChipModel.Hidden)
check(shareToApp is OngoingActivityChipModel.Hidden)
check(castToOtherDevice is OngoingActivityChipModel.Hidden)
check(call is OngoingActivityChipModel.Hidden)
+ check(demoRon is OngoingActivityChipModel.Hidden)
InternalChipModel.Hidden(
screenRecord = screenRecord,
shareToApp = shareToApp,
castToOtherDevice = castToOtherDevice,
call = call,
+ demoRon = demoRon,
)
}
}
@@ -154,6 +175,7 @@
ChipType.ShareToApp -> new.shareToApp
ChipType.CastToOtherDevice -> new.castToOtherDevice
ChipType.Call -> new.call
+ ChipType.DemoRon -> new.demoRon
}
} else if (new is InternalChipModel.Shown) {
// If we have a chip to show, always show it.
@@ -179,6 +201,7 @@
shareToApp = OngoingActivityChipModel.Hidden(),
castToOtherDevice = OngoingActivityChipModel.Hidden(),
call = OngoingActivityChipModel.Hidden(),
+ demoRon = OngoingActivityChipModel.Hidden(),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 669349a..1f767aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -2315,6 +2315,7 @@
private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
boolean isRubberbanded) {
+ SceneContainerFlag.assertInLegacyMode();
amount = Math.max(0, amount);
if (animate) {
mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 3cc6e81..6d5553f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -67,6 +67,7 @@
suspend fun bind(): Nothing =
view.asView().viewModel(
+ traceName = "NotificationScrollViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
factory = viewModelFactory::create,
) { viewModel ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 3999578..3e42413 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -19,12 +19,11 @@
import com.android.compose.animation.scene.ObservableTransitionState.Idle
import com.android.compose.animation.scene.ObservableTransitionState.Transition
-import com.android.compose.animation.scene.ObservableTransitionState.Transition.ChangeCurrentScene
+import com.android.compose.animation.scene.ObservableTransitionState.Transition.ChangeScene
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.SceneFamilies
@@ -63,7 +62,6 @@
keyguardInteractor: Lazy<KeyguardInteractor>,
) :
ActivatableFlowDumper by ActivatableFlowDumperImpl(dumpManager, "NotificationScrollViewModel"),
- SysUiViewModel,
ExclusiveActivatable() {
override suspend fun onActivated(): Nothing {
@@ -79,7 +77,7 @@
}
}
- private fun fullyExpandedDuringSceneChange(change: ChangeCurrentScene): Boolean {
+ private fun fullyExpandedDuringSceneChange(change: ChangeScene): Boolean {
// The lockscreen stack is visible during all transitions away from the lockscreen, so keep
// the stack expanded until those transitions finish.
return (expandedInScene(change.fromScene) && expandedInScene(change.toScene)) ||
@@ -87,7 +85,7 @@
}
private fun expandFractionDuringSceneChange(
- change: ChangeCurrentScene,
+ change: ChangeScene,
shadeExpansion: Float,
qsExpansion: Float,
): Float {
@@ -120,7 +118,7 @@
) { shadeExpansion, _, qsExpansion, transitionState, _ ->
when (transitionState) {
is Idle -> if (expandedInScene(transitionState.currentScene)) 1f else 0f
- is ChangeCurrentScene ->
+ is ChangeScene ->
expandFractionDuringSceneChange(
transitionState,
shadeExpansion,
@@ -250,7 +248,7 @@
}
}
-private fun ChangeCurrentScene.isBetween(
+private fun ChangeScene.isBetween(
a: (SceneKey) -> Boolean,
- b: (SceneKey) -> Boolean
+ b: (SceneKey) -> Boolean,
): Boolean = (a(fromScene) && b(toScene)) || (b(fromScene) && a(toScene))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index d891f62..69c1bf3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -21,7 +21,6 @@
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -53,7 +52,6 @@
featureFlags: FeatureFlagsClassic,
dumpManager: DumpManager,
) :
- SysUiViewModel,
ExclusiveActivatable(),
ActivatableFlowDumper by ActivatableFlowDumperImpl(
dumpManager = dumpManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index f63ee7b..aed00d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -20,6 +20,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import androidx.annotation.VisibleForTesting
+import com.android.compose.animation.scene.SceneKey
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -141,10 +142,6 @@
private val communalSceneInteractor: CommunalSceneInteractor,
unfoldTransitionInteractor: UnfoldTransitionInteractor,
) : FlowDumperImpl(dumpManager) {
- // TODO(b/349784682): Transform deprecated states for Flexiglass
- private val statesForConstrainedNotifications: Set<KeyguardState> =
- setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
- private val statesForHiddenKeyguard: Set<KeyguardState> = setOf(GONE, OCCLUDED)
/**
* Is either shade/qs expanded? This intentionally does not use the [ShadeInteractor] version,
@@ -217,14 +214,16 @@
/** If the user is visually on one of the unoccluded lockscreen states. */
val isOnLockscreen: Flow<Boolean> =
- combine(
- keyguardTransitionInteractor.finishedKeyguardState.map {
- statesForConstrainedNotifications.contains(it)
- },
+ anyOf(
+ keyguardTransitionInteractor.isFinishedIn(AOD),
+ keyguardTransitionInteractor.isFinishedIn(DOZING),
+ keyguardTransitionInteractor.isFinishedIn(ALTERNATE_BOUNCER),
+ keyguardTransitionInteractor.isFinishedIn(
+ scene = Scenes.Bouncer,
+ stateWithoutSceneContainer = PRIMARY_BOUNCER
+ ),
keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f },
- ) { constrainedNotificationState, transitioningToOrFromLockscreen ->
- constrainedNotificationState || transitioningToOrFromLockscreen
- }
+ )
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
@@ -250,9 +249,10 @@
/** If the user is visually on the glanceable hub or transitioning to/from it */
private val isOnGlanceableHub: Flow<Boolean> =
combine(
- keyguardTransitionInteractor.finishedKeyguardState.map { state ->
- state == GLANCEABLE_HUB
- },
+ keyguardTransitionInteractor.isFinishedIn(
+ scene = Scenes.Communal,
+ stateWithoutSceneContainer = GLANCEABLE_HUB
+ ),
anyOf(
keyguardTransitionInteractor.isInTransition(
edge = Edge.create(to = Scenes.Communal),
@@ -424,32 +424,19 @@
.onStart { emit(1f) }
.dumpWhileCollecting("alphaForShadeAndQsExpansion")
- private fun toFlowArray(
- states: Set<KeyguardState>,
- flow: (KeyguardState) -> Flow<Boolean>
- ): Array<Flow<Boolean>> {
- return states.map { flow(it) }.toTypedArray()
- }
-
private val isTransitioningToHiddenKeyguard: Flow<Boolean> =
flow {
while (currentCoroutineContext().isActive) {
emit(false)
// Ensure states are inactive to start
- allOf(
- *toFlowArray(statesForHiddenKeyguard) { state ->
- keyguardTransitionInteractor.transitionValue(state).map { it == 0f }
- }
- )
- .first { it }
+ allOf(isNotOnState(OCCLUDED), isNotOnState(GONE, Scenes.Gone)).first { it }
// Wait for a qualifying transition to begin
anyOf(
- *toFlowArray(statesForHiddenKeyguard) { state ->
- keyguardTransitionInteractor
- .transition(Edge.create(to = state))
- .map { it.value > 0f && it.transitionState == RUNNING }
- .onStart { emit(false) }
- }
+ transitionToIsRunning(Edge.create(to = OCCLUDED)),
+ transitionToIsRunning(
+ edge = Edge.create(to = Scenes.Gone),
+ edgeWithoutSceneContainer = Edge.create(to = GONE)
+ )
)
.first { it }
emit(true)
@@ -458,13 +445,7 @@
// it is considered safe to reset alpha to 1f for HUNs.
combine(
keyguardInteractor.statusBarState,
- allOf(
- *toFlowArray(statesForHiddenKeyguard) { state ->
- keyguardTransitionInteractor.transitionValue(state).map {
- it == 0f
- }
- }
- )
+ allOf(isNotOnState(OCCLUDED), isNotOnState(GONE, Scenes.Gone))
) { statusBarState, stateIsReversed ->
statusBarState == SHADE || stateIsReversed
}
@@ -473,6 +454,17 @@
}
.dumpWhileCollecting("isTransitioningToHiddenKeyguard")
+ private fun isNotOnState(stateWithoutSceneContainer: KeyguardState, scene: SceneKey? = null) =
+ keyguardTransitionInteractor
+ .transitionValue(scene = scene, stateWithoutSceneContainer = stateWithoutSceneContainer)
+ .map { it == 0f }
+
+ private fun transitionToIsRunning(edge: Edge, edgeWithoutSceneContainer: Edge? = null) =
+ keyguardTransitionInteractor
+ .transition(edge = edge, edgeWithoutSceneContainer = edgeWithoutSceneContainer)
+ .map { it.value > 0f && it.transitionState == RUNNING }
+ .onStart { emit(false) }
+
val panelAlpha = keyguardInteractor.panelAlpha
private fun bouncerToGoneNotificationAlpha(viewState: ViewStateAccessor): Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index d46aaf4..c24d694 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -35,6 +35,7 @@
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
import com.android.systemui.statusbar.chips.ui.binder.ChipChronometerBinder
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
@@ -124,10 +125,6 @@
// Colors
val textColor = chipModel.colors.text(chipContext)
- chipDefaultIconView.imageTintList =
- ColorStateList.valueOf(textColor)
- chipBackgroundView.getCustomIconView()?.imageTintList =
- ColorStateList.valueOf(textColor)
chipTimeView.setTextColor(textColor)
chipTextView.setTextColor(textColor)
(chipBackgroundView.background as GradientDrawable).color =
@@ -173,13 +170,22 @@
// it.
backgroundView.removeView(backgroundView.getCustomIconView())
+ val iconTint = chipModel.colors.text(defaultIconView.context)
+
when (val icon = chipModel.icon) {
null -> {
defaultIconView.visibility = View.GONE
}
- is OngoingActivityChipModel.ChipIcon.Basic -> {
+ is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> {
IconViewBinder.bind(icon.impl, defaultIconView)
defaultIconView.visibility = View.VISIBLE
+ defaultIconView.tintView(iconTint)
+ }
+ is OngoingActivityChipModel.ChipIcon.FullColorAppIcon -> {
+ StatusBarRonChips.assertInNewMode()
+ IconViewBinder.bind(icon.impl, defaultIconView)
+ defaultIconView.visibility = View.VISIBLE
+ defaultIconView.untintView()
}
is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
// Hide the default icon since we'll show this custom icon instead.
@@ -194,6 +200,7 @@
// maybe include the app name.
contentDescription =
context.resources.getString(R.string.ongoing_phone_call_content_description)
+ tintView(iconTint)
}
// 2. If we just reinflated the view, we may need to detach the icon view from the
@@ -219,6 +226,14 @@
return this.findViewById(CUSTOM_ICON_VIEW_ID)
}
+ private fun ImageView.tintView(color: Int) {
+ this.imageTintList = ColorStateList.valueOf(color)
+ }
+
+ private fun ImageView.untintView() {
+ this.imageTintList = null
+ }
+
private fun generateCustomIconLayoutParams(iconView: ImageView): FrameLayout.LayoutParams {
val customIconSize =
iconView.context.resources.getDimensionPixelSize(
@@ -237,10 +252,13 @@
chipTextView.text = chipModel.secondsUntilStarted.toString()
chipTextView.visibility = View.VISIBLE
- // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
- // [Chronometer.start].
- chipTimeView.stop()
- chipTimeView.visibility = View.GONE
+ chipTimeView.hide()
+ }
+ is OngoingActivityChipModel.Shown.Text -> {
+ chipTextView.text = chipModel.text
+ chipTextView.visibility = View.VISIBLE
+
+ chipTimeView.hide()
}
is OngoingActivityChipModel.Shown.Timer -> {
ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView)
@@ -250,14 +268,18 @@
}
is OngoingActivityChipModel.Shown.IconOnly -> {
chipTextView.visibility = View.GONE
- // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
- // [Chronometer.start].
- chipTimeView.stop()
- chipTimeView.visibility = View.GONE
+ chipTimeView.hide()
}
}
}
+ private fun ChipChronometer.hide() {
+ // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
+ // [Chronometer.start].
+ this.stop()
+ this.visibility = View.GONE
+ }
+
private fun updateChipPadding(
chipModel: OngoingActivityChipModel.Shown,
backgroundView: View,
@@ -356,6 +378,7 @@
chipView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
}
is OngoingActivityChipModel.Shown.Timer,
+ is OngoingActivityChipModel.Shown.Text,
is OngoingActivityChipModel.Shown.IconOnly -> {
chipView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index c7fc445..9c8ef04 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -89,6 +89,9 @@
import com.google.ux.material.libmonet.dynamiccolor.DynamicColor;
import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors;
+import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.flow.StateFlow;
+
import org.json.JSONException;
import org.json.JSONObject;
@@ -162,6 +165,7 @@
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final JavaAdapter mJavaAdapter;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private final StateFlow<Boolean> mIsKeyguardOnAsleepState;
private final UiModeManager mUiModeManager;
private ColorScheme mDarkColorScheme;
private ColorScheme mLightColorScheme;
@@ -202,8 +206,7 @@
}
boolean currentUser = userId == mUserTracker.getUserId();
boolean isAsleep = themeOverlayControllerWakefulnessDeprecation()
- ? KeyguardState.Companion.deviceIsAsleepInState(
- mKeyguardTransitionInteractor.getFinishedState())
+ ? ThemeOverlayController.this.mIsKeyguardOnAsleepState.getValue()
: mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP;
if (currentUser && !mAcceptColorEvents && isAsleep) {
@@ -434,6 +437,10 @@
mUiModeManager = uiModeManager;
mActivityManager = activityManager;
dumpManager.registerDumpable(TAG, this);
+
+ Flow<Boolean> isFinishedInAsleepStateFlow = mKeyguardTransitionInteractor
+ .isFinishedInStateWhere(KeyguardState.Companion::deviceIsAsleepInState);
+ mIsKeyguardOnAsleepState = mJavaAdapter.stateInApp(isFinishedInAsleepStateFlow, false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
index 727e51f..315a89b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
@@ -20,7 +20,6 @@
import com.android.systemui.Dumpable
import com.android.systemui.dump.DumpManager
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printCollection
import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 055671c..64e056d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -31,7 +31,10 @@
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** A class allowing Java classes to collect on Kotlin flows. */
@@ -58,6 +61,15 @@
): Job {
return scope.launch { flow.collect { consumer.accept(it) } }
}
+
+ @JvmOverloads
+ fun <T> stateInApp(
+ flow: Flow<T>,
+ initialValue: T,
+ started: SharingStarted = SharingStarted.Eagerly
+ ): StateFlow<T> {
+ return flow.stateIn(scope, started, initialValue)
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index d3e8bd3..28effe9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -125,7 +125,6 @@
static final ArrayMap<Integer, Integer> STREAMS = new ArrayMap<>();
static {
STREAMS.put(AudioSystem.STREAM_ALARM, R.string.stream_alarm);
- STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco);
STREAMS.put(AudioSystem.STREAM_DTMF, R.string.stream_dtmf);
STREAMS.put(AudioSystem.STREAM_MUSIC, R.string.stream_music);
STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility);
@@ -654,7 +653,6 @@
private static boolean isLogWorthy(int stream) {
switch (stream) {
case AudioSystem.STREAM_ALARM:
- case AudioSystem.STREAM_BLUETOOTH_SCO:
case AudioSystem.STREAM_MUSIC:
case AudioSystem.STREAM_RING:
case AudioSystem.STREAM_SYSTEM:
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index eb91518..7786453 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -704,8 +704,6 @@
addRow(AudioManager.STREAM_VOICE_CALL,
com.android.internal.R.drawable.ic_phone,
com.android.internal.R.drawable.ic_phone, false, false);
- addRow(AudioManager.STREAM_BLUETOOTH_SCO,
- R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false, false);
addRow(AudioManager.STREAM_SYSTEM, R.drawable.ic_volume_system,
R.drawable.ic_volume_system_mute, false, false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
index 0451ce6..4be680e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
@@ -16,9 +16,7 @@
package com.android.systemui.volume.panel.component.volume.domain.interactor
-import android.media.AudioDeviceInfo
import android.media.AudioManager
-import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
@@ -42,7 +40,6 @@
constructor(
@VolumePanelScope scope: CoroutineScope,
mediaOutputInteractor: MediaOutputInteractor,
- audioRepository: AudioRepository,
audioModeInteractor: AudioModeInteractor,
) {
@@ -50,13 +47,12 @@
combineTransform(
mediaOutputInteractor.activeMediaDeviceSessions,
mediaOutputInteractor.defaultActiveMediaSession.filterData(),
- audioRepository.communicationDevice,
audioModeInteractor.isOngoingCall,
- ) { activeSessions, defaultSession, communicationDevice, isOngoingCall ->
+ ) { activeSessions, defaultSession, isOngoingCall ->
coroutineScope {
val viewModels = buildList {
if (isOngoingCall) {
- addCall(communicationDevice?.type)
+ addStream(AudioManager.STREAM_VOICE_CALL)
}
if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
@@ -68,7 +64,7 @@
}
if (!isOngoingCall) {
- addCall(communicationDevice?.type)
+ addStream(AudioManager.STREAM_VOICE_CALL)
}
addStream(AudioManager.STREAM_RING)
@@ -80,14 +76,6 @@
}
.stateIn(scope, SharingStarted.Eagerly, emptyList())
- private fun MutableList<SliderType>.addCall(communicationDeviceType: Int?) {
- if (communicationDeviceType == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
- addStream(AudioManager.STREAM_BLUETOOTH_SCO)
- } else {
- addStream(AudioManager.STREAM_VOICE_CALL)
- }
- }
-
private fun MutableList<SliderType>.addSession(remoteMediaDeviceSession: MediaDeviceSession?) {
if (remoteMediaDeviceSession?.canAdjustVolume == true) {
add(SliderType.MediaDeviceCast(remoteMediaDeviceSession))
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 521f608..ffb1f11 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -66,7 +66,6 @@
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_music_note,
AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_call,
- AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to R.drawable.ic_call,
AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_ring_volume,
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
@@ -75,7 +74,6 @@
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
AudioStream(AudioManager.STREAM_VOICE_CALL) to R.string.stream_voice_call,
- AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to R.string.stream_voice_call,
AudioStream(AudioManager.STREAM_RING) to R.string.stream_ring,
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.string.stream_notification,
AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm,
@@ -91,8 +89,6 @@
VolumePanelUiEvent.VOLUME_PANEL_MUSIC_SLIDER_TOUCHED,
AudioStream(AudioManager.STREAM_VOICE_CALL) to
VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
- AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to
- VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
AudioStream(AudioManager.STREAM_RING) to
VolumePanelUiEvent.VOLUME_PANEL_RING_SLIDER_TOUCHED,
AudioStream(AudioManager.STREAM_NOTIFICATION) to
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
index 74bc928..681ea75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
@@ -59,6 +59,7 @@
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var activeMediaDeviceItem: DeviceItem
private lateinit var notConnectedDeviceItem: DeviceItem
+ private lateinit var connectedAudioSharingMediaDeviceItem: DeviceItem
private lateinit var connectedMediaDeviceItem: DeviceItem
private lateinit var connectedOtherDeviceItem: DeviceItem
@Mock private lateinit var dialog: SystemUIDialog
@@ -100,6 +101,15 @@
iconWithDescription = null,
background = null
)
+ connectedAudioSharingMediaDeviceItem =
+ DeviceItem(
+ type = DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ cachedBluetoothDevice = cachedBluetoothDevice,
+ deviceName = DEVICE_NAME,
+ connectionSummary = DEVICE_CONNECTION_SUMMARY,
+ iconWithDescription = null,
+ background = null
+ )
connectedOtherDeviceItem =
DeviceItem(
type = DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
@@ -186,6 +196,21 @@
}
@Test
+ fun testOnClick_connectedAudioSharingMediaDevice_logClick() {
+ with(kosmos) {
+ testScope.runTest {
+ whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
+ actionInteractorImpl.onClick(connectedAudioSharingMediaDeviceItem, dialog)
+ verify(bluetoothTileDialogLogger)
+ .logDeviceClick(
+ cachedBluetoothDevice.address,
+ DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE
+ )
+ }
+ }
+ }
+
+ @Test
fun testOnClick_audioSharingDisabled_shouldNotLaunchSettings() {
with(kosmos) {
testScope.runTest {
@@ -415,7 +440,7 @@
}
@Test
- fun testOnClick_hasTwoConnectedLeDevice_clickedConnectedLe_shouldLaunchSettings() {
+ fun testOnClick_hasTwoConnectedLeDevice_clickedActiveLe_shouldLaunchSettings() {
with(kosmos) {
testScope.runTest {
whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
@@ -438,7 +463,7 @@
if (device == bluetoothDevice) GROUP_ID_1 else GROUP_ID_2
}
- actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
+ actionInteractorImpl.onClick(activeMediaDeviceItem, dialog)
verify(activityStarter)
.postStartActivityDismissingKeyguard(
ArgumentMatchers.any(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index a27ccc6..ef441c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -17,18 +17,24 @@
package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothDevice
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
import android.media.AudioManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
+import android.util.Pair
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.flags.Flags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -37,6 +43,7 @@
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -44,9 +51,11 @@
class DeviceItemFactoryTest : SysuiTestCase() {
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private lateinit var mockitoSession: StaticMockitoSession
@Mock private lateinit var cachedDevice: CachedBluetoothDevice
@Mock private lateinit var bluetoothDevice: BluetoothDevice
- @Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+ @Mock private lateinit var drawable: Drawable
private val availableMediaDeviceItemFactory = AvailableMediaDeviceItemFactory()
private val connectedDeviceItemFactory = ConnectedDeviceItemFactory()
@@ -56,16 +65,21 @@
@Before
fun setup() {
- `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
- `when`(cachedDevice.address).thenReturn(DEVICE_ADDRESS)
- `when`(cachedDevice.device).thenReturn(bluetoothDevice)
- `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+ mockitoSession =
+ mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
+ }
- context.setMockPackageManager(packageManager)
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
}
@Test
fun testAvailableMediaDeviceItemFactory_createFromCachedDevice() {
+ `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
+ `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+ `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
+ .thenReturn(Pair.create(drawable, ""))
val deviceItem = availableMediaDeviceItemFactory.create(context, cachedDevice)
assertDeviceItem(deviceItem, DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
@@ -73,6 +87,10 @@
@Test
fun testConnectedDeviceItemFactory_createFromCachedDevice() {
+ `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
+ `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+ `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
+ .thenReturn(Pair.create(drawable, ""))
val deviceItem = connectedDeviceItemFactory.create(context, cachedDevice)
assertDeviceItem(deviceItem, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
@@ -80,6 +98,10 @@
@Test
fun testSavedDeviceItemFactory_createFromCachedDevice() {
+ `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
+ `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+ `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
+ .thenReturn(Pair.create(drawable, ""))
val deviceItem = savedDeviceItemFactory.create(context, cachedDevice)
assertDeviceItem(deviceItem, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
@@ -87,6 +109,90 @@
}
@Test
+ fun testAvailableAudioSharingMediaDeviceItemFactory_createFromCachedDevice() {
+ `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
+ `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
+ .thenReturn(Pair.create(drawable, ""))
+ val deviceItem =
+ AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
+ .create(context, cachedDevice)
+
+ assertThat(deviceItem).isNotNull()
+ assertThat(deviceItem.type)
+ .isEqualTo(DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE)
+ assertThat(deviceItem.cachedBluetoothDevice).isEqualTo(cachedDevice)
+ assertThat(deviceItem.deviceName).isEqualTo(DEVICE_NAME)
+ assertThat(deviceItem.isActive).isFalse()
+ assertThat(deviceItem.connectionSummary)
+ .isEqualTo(
+ context.getString(
+ R.string.quick_settings_bluetooth_device_audio_sharing_or_switch_active
+ )
+ )
+ }
+
+ @Test
+ fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_flagOff_returnsFalse() {
+ // Flags.FLAG_ENABLE_LE_AUDIO_SHARING off or the device doesn't support broadcast
+ // source or assistant.
+ `when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
+
+ assertThat(
+ AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
+ .isFilterMatched(context, cachedDevice, audioManager)
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_isActiveDevice_returnsFalse() {
+ // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
+ // assistant.
+ `when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
+ `when`(BluetoothUtils.isActiveMediaDevice(any())).thenReturn(true)
+
+ assertThat(
+ AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
+ .isFilterMatched(context, cachedDevice, audioManager)
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_isNotAvailable_returnsFalse() {
+ // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
+ // assistant.
+ `when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
+ `when`(BluetoothUtils.isActiveMediaDevice(any())).thenReturn(false)
+ `when`(BluetoothUtils.isAvailableMediaBluetoothDevice(any(), any())).thenReturn(true)
+ `when`(BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(any(), any()))
+ .thenReturn(false)
+
+ assertThat(
+ AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
+ .isFilterMatched(context, cachedDevice, audioManager)
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_returnsTrue() {
+ // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
+ // assistant.
+ `when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
+ `when`(BluetoothUtils.isActiveMediaDevice(any())).thenReturn(false)
+ `when`(BluetoothUtils.isAvailableMediaBluetoothDevice(any(), any())).thenReturn(true)
+ `when`(BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(any(), any()))
+ .thenReturn(true)
+
+ assertThat(
+ AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
+ .isFilterMatched(context, cachedDevice, audioManager)
+ )
+ .isTrue()
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testSavedFactory_isFilterMatched_bondedAndNotConnected_returnsTrue() {
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
@@ -110,7 +216,6 @@
@DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testSavedFactory_isFilterMatched_notBonded_returnsFalse() {
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
- `when`(cachedDevice.isConnected).thenReturn(false)
assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
.isFalse()
@@ -119,12 +224,8 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testSavedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
- `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
- `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
- .thenReturn(ApplicationInfo())
- `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
- `when`(cachedDevice.isConnected).thenReturn(false)
+ `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+ `when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(true)
assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
.isFalse()
@@ -132,7 +233,9 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testSavedFactory_isFilterMatched_noExclusiveManager_returnsTrue() {
+ fun testSavedFactory_isFilterMatched_notExclusiveManaged_returnsTrue() {
+ `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+ `when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(cachedDevice.isConnected).thenReturn(false)
@@ -142,45 +245,9 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testSavedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() {
- `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
- `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
- .thenReturn(ApplicationInfo().also { it.enabled = false })
- `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
- `when`(cachedDevice.isConnected).thenReturn(false)
-
- assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
- .isTrue()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testSavedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() {
- `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
- `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
- .thenThrow(PackageManager.NameNotFoundException("Test!"))
- `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
- `when`(cachedDevice.isConnected).thenReturn(false)
-
- assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
- .isTrue()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testSavedFactory_isFilterMatched_notExclusivelyManaged_notBonded_returnsFalse() {
- `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
- `when`(cachedDevice.isConnected).thenReturn(false)
-
- assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
- .isFalse()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testSavedFactory_isFilterMatched_notExclusivelyManaged_connected_returnsFalse() {
+ `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+ `when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(cachedDevice.isConnected).thenReturn(true)
@@ -191,9 +258,7 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testConnectedFactory_isFilterMatched_bondedAndConnected_returnsTrue() {
- `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
- `when`(bluetoothDevice.isConnected).thenReturn(true)
- audioManager.setMode(AudioManager.MODE_NORMAL)
+ `when`(BluetoothUtils.isConnectedBluetoothDevice(any(), any())).thenReturn(true)
assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
.isTrue()
@@ -202,21 +267,6 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testConnectedFactory_isFilterMatched_notConnected_returnsFalse() {
- `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
- `when`(bluetoothDevice.isConnected).thenReturn(false)
- audioManager.setMode(AudioManager.MODE_NORMAL)
-
- assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
- .isFalse()
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testConnectedFactory_isFilterMatched_notBonded_returnsFalse() {
- `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
- `when`(bluetoothDevice.isConnected).thenReturn(true)
- audioManager.setMode(AudioManager.MODE_NORMAL)
-
assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
.isFalse()
}
@@ -224,13 +274,8 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testConnectedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
- `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
- `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
- .thenReturn(ApplicationInfo())
- `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
- `when`(bluetoothDevice.isConnected).thenReturn(true)
- audioManager.setMode(AudioManager.MODE_NORMAL)
+ `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+ `when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(true)
assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
.isFalse()
@@ -239,9 +284,9 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testConnectedFactory_isFilterMatched_noExclusiveManager_returnsTrue() {
- `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
- `when`(bluetoothDevice.isConnected).thenReturn(true)
- audioManager.setMode(AudioManager.MODE_NORMAL)
+ `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+ `when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
+ `when`(BluetoothUtils.isConnectedBluetoothDevice(any(), any())).thenReturn(true)
assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
.isTrue()
@@ -249,51 +294,10 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testConnectedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() {
- `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
- `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
- .thenReturn(ApplicationInfo().also { it.enabled = false })
- `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
- `when`(bluetoothDevice.isConnected).thenReturn(true)
- audioManager.setMode(AudioManager.MODE_NORMAL)
-
- assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
- .isTrue()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testConnectedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() {
- `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
- `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
- .thenThrow(PackageManager.NameNotFoundException("Test!"))
- `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
- `when`(bluetoothDevice.isConnected).thenReturn(true)
- audioManager.setMode(AudioManager.MODE_NORMAL)
-
- assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
- .isTrue()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testConnectedFactory_isFilterMatched_notExclusivelyManaged_notBonded_returnsFalse() {
- `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
- `when`(bluetoothDevice.isConnected).thenReturn(true)
- audioManager.setMode(AudioManager.MODE_NORMAL)
-
- assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
- .isFalse()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testConnectedFactory_isFilterMatched_notExclusivelyManaged_notConnected_returnsFalse() {
- `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
- `when`(bluetoothDevice.isConnected).thenReturn(false)
- audioManager.setMode(AudioManager.MODE_NORMAL)
+ `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+ `when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
+ `when`(BluetoothUtils.isConnectedBluetoothDevice(any(), any())).thenReturn(false)
assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
.isFalse()
@@ -310,7 +314,5 @@
companion object {
const val DEVICE_NAME = "DeviceName"
const val CONNECTION_SUMMARY = "ConnectionSummary"
- private const val TEST_EXCLUSIVE_MANAGER = "com.test.manager"
- private const val DEVICE_ADDRESS = "04:52:C7:0B:D8:3C"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
index 8e215f9..fd550b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
@@ -83,7 +83,9 @@
PlatformTheme {
BouncerContent(
viewModel =
- rememberViewModel { kosmos.bouncerSceneContentViewModelFactory.create() },
+ rememberViewModel("test") {
+ kosmos.bouncerSceneContentViewModelFactory.create()
+ },
layout = BouncerSceneLayout.BESIDE_USER_SWITCHER,
modifier = Modifier.fillMaxSize().testTag("BouncerContent"),
dialogFactory = bouncerDialogFactory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 73b9f57..07f7557 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -47,8 +47,11 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
@@ -56,7 +59,10 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -144,7 +150,6 @@
@Mock
private lateinit var glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel
- @Mock private lateinit var transitionInteractor: KeyguardTransitionInteractor
private val kosmos = testKosmos()
@@ -163,8 +168,6 @@
// the viewModel does a `map { 1 - it }` on this value, which is why it's different
private val intendedShadeAlphaMutableStateFlow: MutableStateFlow<Float> = MutableStateFlow(0f)
- private val intendedFinishedKeyguardStateFlow = MutableStateFlow(KeyguardState.LOCKSCREEN)
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -256,7 +259,6 @@
intendedAlphaMutableStateFlow.value = 1f
intendedShadeAlphaMutableStateFlow.value = 0f
- intendedFinishedKeyguardStateFlow.value = KeyguardState.LOCKSCREEN
whenever(aodToLockscreenTransitionViewModel.shortcutsAlpha)
.thenReturn(intendedAlphaMutableStateFlow)
whenever(dozingToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
@@ -283,8 +285,6 @@
whenever(glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha)
.thenReturn(emptyFlow())
whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
- whenever(transitionInteractor.finishedKeyguardState)
- .thenReturn(intendedFinishedKeyguardStateFlow)
underTest =
KeyguardQuickAffordancesCombinedViewModel(
@@ -334,7 +334,7 @@
lockscreenToPrimaryBouncerTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel =
lockscreenToGlanceableHubTransitionViewModel,
- transitionInteractor = transitionInteractor,
+ transitionInteractor = kosmos.keyguardTransitionInteractor,
)
}
@@ -776,7 +776,10 @@
@Test
fun shadeExpansionAlpha_changes_whenOnLockscreen() =
testScope.runTest {
- intendedFinishedKeyguardStateFlow.value = KeyguardState.LOCKSCREEN
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition = TransitionStep(from = AOD, to = LOCKSCREEN)
+ )
intendedShadeAlphaMutableStateFlow.value = 0.25f
val underTest = collectLastValue(underTest.transitionAlpha)
assertEquals(0.75f, underTest())
@@ -788,7 +791,10 @@
@Test
fun shadeExpansionAlpha_alwaysZero_whenNotOnLockscreen() =
testScope.runTest {
- intendedFinishedKeyguardStateFlow.value = KeyguardState.GONE
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition = TransitionStep(from = AOD, to = GONE)
+ )
intendedShadeAlphaMutableStateFlow.value = 0.5f
val underTest = collectLastValue(underTest.transitionAlpha)
assertEquals(0f, underTest())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
index 67517a2..2ba670c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
@@ -40,7 +40,7 @@
composeRule.setContent {
val keepAlive by keepAliveMutable
if (keepAlive) {
- rememberActivated {
+ rememberActivated("test") {
FakeActivatable(
onActivation = { isActive = true },
onDeactivation = { isActive = false },
@@ -58,7 +58,7 @@
composeRule.setContent {
val keepAlive by keepAliveMutable
if (keepAlive) {
- rememberActivated {
+ rememberActivated("name") {
FakeActivatable(
onActivation = { isActive = true },
onDeactivation = { isActive = false },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
index c7acd78..73f724e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
@@ -51,7 +51,7 @@
composeRule.setContent {
val keepAlive by keepAliveMutable
if (keepAlive) {
- rememberViewModel {
+ rememberViewModel("test") {
FakeSysUiViewModel(
onActivation = { isActive = true },
onDeactivation = { isActive = false },
@@ -69,21 +69,25 @@
var isActive2 = false
composeRule.setContent {
val key by keyMutable
- rememberViewModel(key) {
- when (key) {
- 1 ->
- FakeSysUiViewModel(
- onActivation = { isActive1 = true },
- onDeactivation = { isActive1 = false },
- )
- 2 ->
- FakeSysUiViewModel(
- onActivation = { isActive2 = true },
- onDeactivation = { isActive2 = false },
- )
- else -> error("unsupported key $key")
+ // Need to explicitly state the type to avoid a weird issue where the factory seems to
+ // return Unit instead of FakeSysUiViewModel. It might be an issue with the compose
+ // compiler.
+ val unused: FakeSysUiViewModel =
+ rememberViewModel("test", key) {
+ when (key) {
+ 1 ->
+ FakeSysUiViewModel(
+ onActivation = { isActive1 = true },
+ onDeactivation = { isActive1 = false },
+ )
+ 2 ->
+ FakeSysUiViewModel(
+ onActivation = { isActive2 = true },
+ onDeactivation = { isActive2 = false },
+ )
+ else -> error("unsupported key $key")
+ }
}
- }
}
assertThat(isActive1).isTrue()
assertThat(isActive2).isFalse()
@@ -106,7 +110,7 @@
composeRule.setContent {
val keepAlive by keepAliveMutable
if (keepAlive) {
- rememberViewModel {
+ rememberViewModel("test") {
FakeSysUiViewModel(
onActivation = { isActive = true },
onDeactivation = { isActive = false },
@@ -130,6 +134,7 @@
val viewModel = FakeViewModel()
backgroundScope.launch {
view.viewModel(
+ traceName = "test",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
factory = { viewModel },
) {
@@ -151,7 +156,7 @@
}
}
-private class FakeViewModel : SysUiViewModel, ExclusiveActivatable() {
+private class FakeViewModel : ExclusiveActivatable() {
var isActivated = false
override suspend fun onActivated(): Nothing {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 99c5b7c..9eccd9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -41,12 +41,11 @@
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.service.notification.StatusBarNotification
-import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.logging.InstanceId
@@ -58,9 +57,15 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.Flags.MEDIA_REMOTE_RESUME
+import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS
+import com.android.systemui.flags.Flags.MEDIA_RETAIN_RECOMMENDATIONS
+import com.android.systemui.flags.Flags.MEDIA_RETAIN_SESSIONS
+import com.android.systemui.flags.Flags.MEDIA_SESSION_ACTIONS
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.data.repository.MediaDataRepository
-import com.android.systemui.media.controls.data.repository.MediaFilterRepository
+import com.android.systemui.media.controls.data.repository.mediaDataRepository
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.domain.resume.MediaResumeListener
@@ -70,10 +75,10 @@
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.util.MediaControllerFactory
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.statusbar.notificationLockscreenUserManager
@@ -81,13 +86,10 @@
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.runCurrent
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -111,6 +113,8 @@
import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
private const val KEY = "KEY"
private const val KEY_2 = "KEY_2"
@@ -134,13 +138,10 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@EnableSceneContainer
-class MediaDataProcessorTest : SysuiTestCase() {
- val kosmos = testKosmos()
-
+class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
@JvmField @Rule val mockito = MockitoJUnit.rule()
- @Mock lateinit var mediaControllerFactory: MediaControllerFactory
@Mock lateinit var controller: MediaController
@Mock lateinit var transportControls: MediaController.TransportControls
@Mock lateinit var playbackInfo: MediaController.PlaybackInfo
@@ -158,7 +159,6 @@
@Mock lateinit var mediaDataCombineLatest: MediaDataCombineLatest
@Mock lateinit var listener: MediaDataManager.Listener
@Mock lateinit var pendingIntent: PendingIntent
- @Mock lateinit var activityStarter: ActivityStarter
@Mock lateinit var smartspaceManager: SmartspaceManager
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@@ -166,7 +166,6 @@
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
private lateinit var validRecommendationList: List<SmartspaceAction>
@Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
- @Mock private lateinit var mediaFlags: MediaFlags
@Mock private lateinit var logger: MediaUiEventLogger
private lateinit var mediaCarouselInteractor: MediaCarouselInteractor
private lateinit var mediaDataProcessor: MediaDataProcessor
@@ -179,11 +178,30 @@
@Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
@Mock private lateinit var ugm: IUriGrantsManager
@Mock private lateinit var imageSource: ImageDecoder.Source
- private lateinit var mediaDataRepository: MediaDataRepository
- private lateinit var testScope: TestScope
- private lateinit var testDispatcher: TestDispatcher
- private lateinit var testableLooper: TestableLooper
- private lateinit var fakeHandler: FakeHandler
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.progressionOf(
+ Flags.FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER
+ )
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ private val kosmos = testKosmos()
+ private val testDispatcher = kosmos.testDispatcher
+ private val testScope = kosmos.testScope
+ private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
+ private val activityStarter = kosmos.activityStarter
+ private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
+ private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
+ private val mediaFilterRepository = kosmos.mediaFilterRepository
+ private val mediaDataFilter = kosmos.mediaDataFilter
private val settings = FakeSettings()
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
@@ -194,9 +212,6 @@
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
1
)
- private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
- private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository
- private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
private lateinit var staticMockSession: MockitoSession
@@ -212,17 +227,12 @@
foregroundExecutor = FakeExecutor(clock)
backgroundExecutor = FakeExecutor(clock)
uiExecutor = FakeExecutor(clock)
- testableLooper = TestableLooper.get(this)
- fakeHandler = FakeHandler(testableLooper.looper)
smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
Settings.Secure.putInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
1
)
- testDispatcher = UnconfinedTestDispatcher()
- testScope = TestScope(testDispatcher)
- mediaDataRepository = MediaDataRepository(mediaFlags, dumpManager)
mediaDataProcessor =
MediaDataProcessor(
context = context,
@@ -231,7 +241,7 @@
backgroundExecutor = backgroundExecutor,
uiExecutor = uiExecutor,
foregroundExecutor = foregroundExecutor,
- handler = fakeHandler,
+ mainDispatcher = testDispatcher,
mediaControllerFactory = mediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
@@ -241,13 +251,15 @@
useQsMediaPlayer = true,
systemClock = clock,
secureSettings = settings,
- mediaFlags = mediaFlags,
+ mediaFlags = kosmos.mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
keyguardUpdateMonitor = keyguardUpdateMonitor,
- mediaDataRepository = mediaDataRepository,
+ mediaDataRepository = kosmos.mediaDataRepository,
+ mediaDataLoader = { kosmos.mediaDataLoader },
)
mediaDataProcessor.start()
+ testScope.runCurrent()
mediaCarouselInteractor =
MediaCarouselInteractor(
applicationScope = testScope.backgroundScope,
@@ -259,7 +271,7 @@
mediaDataCombineLatest = mediaDataCombineLatest,
mediaDataFilter = mediaDataFilter,
mediaFilterRepository = mediaFilterRepository,
- mediaFlags = mediaFlags
+ mediaFlags = kosmos.mediaFlags
)
mediaCarouselInteractor.start()
verify(mediaTimeoutListener).stateCallback = capture(stateCallbackCaptor)
@@ -295,7 +307,7 @@
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
- whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+ mediaControllerFactory.setControllerForToken(session.sessionToken, controller)
whenever(controller.transportControls).thenReturn(transportControls)
whenever(controller.playbackInfo).thenReturn(playbackInfo)
whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -325,10 +337,11 @@
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
- whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, false)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, false)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, false)
+ fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, false)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, false)
whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(false)
}
@@ -374,6 +387,7 @@
PACKAGE_NAME
)
+ testScope.runCurrent()
backgroundExecutor.runAllReady()
foregroundExecutor.runAllReady()
verify(listener)
@@ -399,7 +413,7 @@
@Test
fun testLoadsMetadataOnBackground() {
mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.numPending()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 0, background = 1)
}
@Test
@@ -416,8 +430,7 @@
mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -434,8 +447,7 @@
fun testOnMetaDataLoaded_withoutExplicitIndicator() {
mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -462,10 +474,8 @@
@Test
fun testOnMetaDataLoaded_conservesActiveFlag() {
- whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -509,8 +519,7 @@
}
mediaDataProcessor.onNotificationAdded(KEY, notif)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -596,8 +605,7 @@
mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
// Then a media control is created with a placeholder title string
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -627,8 +635,7 @@
mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
// Then a media control is created with a placeholder title string
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -669,8 +676,7 @@
mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
// Then the media control is added using the notification's title
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -778,8 +784,7 @@
// GIVEN that the manager has two notifications with resume actions
mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
mediaDataProcessor.onNotificationAdded(KEY_2, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
+ testScope.assertRunAllReady(foreground = 2, background = 2)
verify(listener)
.onMediaDataLoaded(
@@ -866,7 +871,7 @@
@Test
fun testOnNotificationRemoved_withResumption_isRemoteAndRemoteAllowed() {
// With the flag enabled to allow remote media to resume
- whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, true)
// GIVEN that the manager has a notification with a resume action, but is not local
whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -897,7 +902,7 @@
@Test
fun testOnNotificationRemoved_withResumption_isRcnAndRemoteAllowed() {
// With the flag enabled to allow remote media to resume
- whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, true)
// GIVEN that the manager has a remote cast notification
addNotificationAndLoad(remoteCastNotification)
@@ -1016,7 +1021,7 @@
@Test
fun testAddResumptionControls_hasPartialProgress() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added with partial progress
val progress = 0.5
@@ -1043,7 +1048,7 @@
@Test
fun testAddResumptionControls_hasNotPlayedProgress() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that have not been played
val extras =
@@ -1068,7 +1073,7 @@
@Test
fun testAddResumptionControls_hasFullProgress() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added with progress info
val extras =
@@ -1094,7 +1099,7 @@
@Test
fun testAddResumptionControls_hasNoExtras() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that do not have any extras
val desc =
@@ -1112,7 +1117,7 @@
@Test
fun testAddResumptionControls_hasEmptyTitle() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that have empty title
val desc =
@@ -1131,8 +1136,7 @@
)
// Resumption controls are not added.
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(0)
+ testScope.assertRunAllReady(foreground = 0, background = 1)
verify(listener, never())
.onMediaDataLoaded(
eq(PACKAGE_NAME),
@@ -1146,7 +1150,7 @@
@Test
fun testAddResumptionControls_hasBlankTitle() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that have a blank title
val desc =
@@ -1165,8 +1169,7 @@
)
// Resumption controls are not added.
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(0)
+ testScope.assertRunAllReady(foreground = 0, background = 1)
verify(listener, never())
.onMediaDataLoaded(
eq(PACKAGE_NAME),
@@ -1233,8 +1236,7 @@
mediaDataProcessor.onNotificationAdded(KEY, notif)
// THEN it still loads
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -1351,7 +1353,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
val instanceId = instanceIdSequence.lastInstanceId
@@ -1377,7 +1379,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
val extras =
Bundle().apply {
putString("package_name", PACKAGE_NAME)
@@ -1411,7 +1413,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
val instanceId = instanceIdSequence.lastInstanceId
@@ -1443,7 +1445,7 @@
@Test
fun testSetRecommendationInactive_notifiesListeners() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
val instanceId = instanceIdSequence.lastInstanceId
@@ -1476,6 +1478,7 @@
fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
// WHEN media recommendation setting is off
settings.putInt(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
+ testScope.runCurrent()
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
@@ -1493,6 +1496,7 @@
// WHEN the media recommendation setting is turned off
settings.putInt(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
+ testScope.runCurrent()
// THEN listeners are notified
uiExecutor.advanceClockToLast()
@@ -1513,8 +1517,7 @@
fun testOnMediaDataTimedOut_updatesLastActiveTime() {
// GIVEN that the manager has a notification
mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
// WHEN the notification times out
clock.advanceTime(100)
@@ -1622,8 +1625,7 @@
// WHEN the notification is loaded
mediaDataProcessor.onNotificationAdded(KEY, notif)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
// THEN only the first MAX_COMPACT_ACTIONS are actually set
verify(listener)
@@ -1658,8 +1660,7 @@
// WHEN the notification is loaded
mediaDataProcessor.onNotificationAdded(KEY, notif)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
// THEN only the first MAX_NOTIFICATION_ACTIONS are actually included
verify(listener)
@@ -1678,7 +1679,7 @@
@Test
fun testPlaybackActions_noState_usesNotification() {
val desc = "Notification Action"
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
whenever(controller.playbackState).thenReturn(null)
val notifWithAction =
@@ -1693,8 +1694,7 @@
}
mediaDataProcessor.onNotificationAdded(KEY, notifWithAction)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -1713,7 +1713,7 @@
@Test
fun testPlaybackActions_hasPrevNext() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions =
PlaybackState.ACTION_PLAY or
PlaybackState.ACTION_SKIP_TO_PREVIOUS or
@@ -1757,7 +1757,7 @@
@Test
fun testPlaybackActions_noPrevNext_usesCustom() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder = PlaybackState.Builder().setActions(stateActions)
customDesc.forEach {
@@ -1789,7 +1789,7 @@
@Test
fun testPlaybackActions_connecting() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder =
PlaybackState.Builder()
@@ -1809,87 +1809,85 @@
@Test
@EnableFlags(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
- fun postWithPlaybackActions_drawablesReused() =
- kosmos.testScope.runTest {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
- whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
- whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
- val stateActions =
- PlaybackState.ACTION_PAUSE or
- PlaybackState.ACTION_SKIP_TO_PREVIOUS or
- PlaybackState.ACTION_SKIP_TO_NEXT
- val stateBuilder =
- PlaybackState.Builder()
- .setState(PlaybackState.STATE_PLAYING, 0, 10f)
- .setActions(stateActions)
- whenever(controller.playbackState).thenReturn(stateBuilder.build())
- val userEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+ fun postWithPlaybackActions_drawablesReused() {
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ val stateActions =
+ PlaybackState.ACTION_PAUSE or
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+ PlaybackState.ACTION_SKIP_TO_NEXT
+ val stateBuilder =
+ PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PLAYING, 0, 10f)
+ .setActions(stateActions)
+ whenever(controller.playbackState).thenReturn(stateBuilder.build())
+ val userEntries by testScope.collectLastValue(mediaFilterRepository.selectedUserEntries)
- mediaDataProcessor.addInternalListener(mediaDataFilter)
- mediaDataFilter.mediaDataProcessor = mediaDataProcessor
- addNotificationAndLoad()
+ mediaDataProcessor.addInternalListener(mediaDataFilter)
+ mediaDataFilter.mediaDataProcessor = mediaDataProcessor
+ addNotificationAndLoad()
- assertThat(userEntries).hasSize(1)
- val firstSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+ assertThat(userEntries).hasSize(1)
+ val firstSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
- addNotificationAndLoad()
+ addNotificationAndLoad()
- assertThat(userEntries).hasSize(1)
- val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
- assertThat(secondSemanticActions.playOrPause?.icon)
- .isEqualTo(firstSemanticActions.playOrPause?.icon)
- assertThat(secondSemanticActions.playOrPause?.background)
- .isEqualTo(firstSemanticActions.playOrPause?.background)
- assertThat(secondSemanticActions.nextOrCustom?.icon)
- .isEqualTo(firstSemanticActions.nextOrCustom?.icon)
- assertThat(secondSemanticActions.prevOrCustom?.icon)
- .isEqualTo(firstSemanticActions.prevOrCustom?.icon)
- }
+ assertThat(userEntries).hasSize(1)
+ val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+ assertThat(secondSemanticActions.playOrPause?.icon)
+ .isEqualTo(firstSemanticActions.playOrPause?.icon)
+ assertThat(secondSemanticActions.playOrPause?.background)
+ .isEqualTo(firstSemanticActions.playOrPause?.background)
+ assertThat(secondSemanticActions.nextOrCustom?.icon)
+ .isEqualTo(firstSemanticActions.nextOrCustom?.icon)
+ assertThat(secondSemanticActions.prevOrCustom?.icon)
+ .isEqualTo(firstSemanticActions.prevOrCustom?.icon)
+ }
@Test
@DisableFlags(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
- fun postWithPlaybackActions_drawablesNotReused() =
- kosmos.testScope.runTest {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
- whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
- whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
- val stateActions =
- PlaybackState.ACTION_PAUSE or
- PlaybackState.ACTION_SKIP_TO_PREVIOUS or
- PlaybackState.ACTION_SKIP_TO_NEXT
- val stateBuilder =
- PlaybackState.Builder()
- .setState(PlaybackState.STATE_PLAYING, 0, 10f)
- .setActions(stateActions)
- whenever(controller.playbackState).thenReturn(stateBuilder.build())
- val userEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+ fun postWithPlaybackActions_drawablesNotReused() {
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ val stateActions =
+ PlaybackState.ACTION_PAUSE or
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+ PlaybackState.ACTION_SKIP_TO_NEXT
+ val stateBuilder =
+ PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PLAYING, 0, 10f)
+ .setActions(stateActions)
+ whenever(controller.playbackState).thenReturn(stateBuilder.build())
+ val userEntries by testScope.collectLastValue(mediaFilterRepository.selectedUserEntries)
- mediaDataProcessor.addInternalListener(mediaDataFilter)
- mediaDataFilter.mediaDataProcessor = mediaDataProcessor
- addNotificationAndLoad()
+ mediaDataProcessor.addInternalListener(mediaDataFilter)
+ mediaDataFilter.mediaDataProcessor = mediaDataProcessor
+ addNotificationAndLoad()
- assertThat(userEntries).hasSize(1)
- val firstSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+ assertThat(userEntries).hasSize(1)
+ val firstSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
- addNotificationAndLoad()
+ addNotificationAndLoad()
- assertThat(userEntries).hasSize(1)
- val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+ assertThat(userEntries).hasSize(1)
+ val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
- assertThat(secondSemanticActions.playOrPause?.icon)
- .isNotEqualTo(firstSemanticActions.playOrPause?.icon)
- assertThat(secondSemanticActions.playOrPause?.background)
- .isNotEqualTo(firstSemanticActions.playOrPause?.background)
- assertThat(secondSemanticActions.nextOrCustom?.icon)
- .isNotEqualTo(firstSemanticActions.nextOrCustom?.icon)
- assertThat(secondSemanticActions.prevOrCustom?.icon)
- .isNotEqualTo(firstSemanticActions.prevOrCustom?.icon)
- }
+ assertThat(secondSemanticActions.playOrPause?.icon)
+ .isNotEqualTo(firstSemanticActions.playOrPause?.icon)
+ assertThat(secondSemanticActions.playOrPause?.background)
+ .isNotEqualTo(firstSemanticActions.playOrPause?.background)
+ assertThat(secondSemanticActions.nextOrCustom?.icon)
+ .isNotEqualTo(firstSemanticActions.nextOrCustom?.icon)
+ assertThat(secondSemanticActions.prevOrCustom?.icon)
+ .isNotEqualTo(firstSemanticActions.prevOrCustom?.icon)
+ }
@Test
fun testPlaybackActions_reservedSpace() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder = PlaybackState.Builder().setActions(stateActions)
customDesc.forEach {
@@ -1927,7 +1925,7 @@
@Test
fun testPlaybackActions_playPause_hasButton() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY_PAUSE
val stateBuilder = PlaybackState.Builder().setActions(stateActions)
whenever(controller.playbackState).thenReturn(stateBuilder.build())
@@ -1964,8 +1962,7 @@
// update to remote cast
mediaDataProcessor.onNotificationAdded(KEY, remoteCastNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(logger)
.logPlaybackLocationChange(
anyInt(),
@@ -2027,7 +2024,7 @@
@Test
fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val state = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 1f).build()
whenever(controller.playbackState).thenReturn(state)
@@ -2071,6 +2068,7 @@
pendingIntent,
PACKAGE_NAME
)
+ testScope.runCurrent()
backgroundExecutor.runAllReady()
foregroundExecutor.runAllReady()
@@ -2149,7 +2147,7 @@
@Test
fun testRetain_notifPlayer_notifRemoved_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control based on notification is added, times out, and then removed
addNotificationAndLoad()
@@ -2179,7 +2177,7 @@
@Test
fun testRetain_notifPlayer_sessionDestroyed_doesNotChange() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control based on notification is added and times out
addNotificationAndLoad()
@@ -2197,7 +2195,7 @@
@Test
fun testRetain_notifPlayer_removeWhileActive_fullyRemoved() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control based on notification is added and then removed, without timing out
addNotificationAndLoad()
@@ -2214,7 +2212,7 @@
@Test
fun testRetain_canResume_removeWhileActive_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control that supports resumption is added
addNotificationAndLoad()
@@ -2246,8 +2244,8 @@
@Test
fun testRetain_sessionPlayer_notifRemoved_doesNotChange() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control with PlaybackState actions is added, times out,
@@ -2266,8 +2264,8 @@
@Test
fun testRetain_sessionPlayer_sessionDestroyed_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control with PlaybackState actions is added, times out,
@@ -2300,8 +2298,8 @@
@Test
fun testRetain_sessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions is added, and then the session is destroyed
@@ -2320,8 +2318,8 @@
@Test
fun testRetain_sessionPlayer_canResume_destroyedWhileActive_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions and that does allow resumption is added,
@@ -2354,7 +2352,7 @@
@Test
fun testSessionPlayer_sessionDestroyed_noResume_fullyRemoved() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control with PlaybackState actions is added, times out,
@@ -2381,7 +2379,7 @@
@Test
fun testSessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions is added, and then the session is destroyed
@@ -2400,7 +2398,7 @@
@Test
fun testSessionPlayer_canResume_destroyedWhileActive_setToResume() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions and that does allow resumption is added,
@@ -2433,8 +2431,8 @@
@Test
fun testSessionDestroyed_noNotificationKey_stillRemoved() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
// When a notiifcation is added and then removed before it is fully processed
mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
@@ -2505,6 +2503,23 @@
assertThat(mediaDataCaptor.value.artwork).isNull()
}
+ private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) {
+ runCurrent()
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ // It doesn't make much sense to count tasks when we use coroutines in loader
+ // so this check is skipped in that scenario.
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+ } else {
+ if (background > 0) {
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(background)
+ }
+ if (foreground > 0) {
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(foreground)
+ }
+ }
+ }
+
/** Helper function to add a basic media notification and capture the resulting MediaData */
private fun addNotificationAndLoad() {
addNotificationAndLoad(mediaNotification)
@@ -2513,8 +2528,7 @@
/** Helper function to add the given notification and capture the resulting MediaData */
private fun addNotificationAndLoad(sbn: StatusBarNotification) {
mediaDataProcessor.onNotificationAdded(KEY, sbn)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -2548,8 +2562,7 @@
pendingIntent,
packageName
)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index 6a66c40..0c8d880 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -54,6 +54,7 @@
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -125,6 +126,8 @@
private lateinit var mediaData: MediaData
@JvmField @Rule val mockito = MockitoJUnit.rule()
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
fakeFgExecutor = FakeExecutor(FakeSystemClock())
@@ -141,6 +144,7 @@
{ localBluetoothManager },
fakeFgExecutor,
fakeBgExecutor,
+ kosmos.mediaDeviceLogger,
)
manager.addListener(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
index a82e5c4..263b001 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
@@ -14,7 +14,6 @@
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -30,6 +29,7 @@
import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -73,8 +73,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(context.user).thenReturn(UserHandle.SYSTEM)
- whenever(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context)
+ `when`(context.user).thenReturn(UserHandle.SYSTEM)
+ `when`(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context)
}
@Test
@@ -94,7 +94,7 @@
tracker.addCallback(callback, executor)
val profileID = tracker.userId + 10
- whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
val id = invocation.getArgument<Int>(0)
val info = UserInfo(id, "", UserInfo.FLAG_FULL)
val infoProfile =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index 2e2ac3e..774aa51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -33,11 +33,9 @@
import com.android.systemui.flags.Flags
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.TruthJUnit.assume
-import java.util.concurrent.Executor
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -56,7 +54,10 @@
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
+
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -70,19 +71,27 @@
fun isBackgroundUserTrackerEnabled(): Iterable<Boolean> = listOf(true, false)
}
- @Mock private lateinit var context: Context
+ @Mock
+ private lateinit var context: Context
- @Mock private lateinit var userManager: UserManager
+ @Mock
+ private lateinit var userManager: UserManager
- @Mock private lateinit var iActivityManager: IActivityManager
+ @Mock
+ private lateinit var iActivityManager: IActivityManager
- @Mock private lateinit var userSwitchingReply: IRemoteCallback
+ @Mock
+ private lateinit var userSwitchingReply: IRemoteCallback
- @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager
+ @Mock(stubOnly = true)
+ private lateinit var dumpManager: DumpManager
- @Mock(stubOnly = true) private lateinit var handler: Handler
+ @Mock(stubOnly = true)
+ private lateinit var handler: Handler
- @Parameterized.Parameter @JvmField var isBackgroundUserTrackerEnabled: Boolean = false
+ @Parameterized.Parameter
+ @JvmField
+ var isBackgroundUserTrackerEnabled: Boolean = false
private val testScope = TestScope()
private val testDispatcher = StandardTestDispatcher(testScope.testScheduler)
@@ -95,379 +104,365 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(context.userId).thenReturn(UserHandle.USER_SYSTEM)
- whenever(context.user).thenReturn(UserHandle.SYSTEM)
- whenever(context.createContextAsUser(any(), anyInt())).thenAnswer { invocation ->
+ `when`(context.userId).thenReturn(UserHandle.USER_SYSTEM)
+ `when`(context.user).thenReturn(UserHandle.SYSTEM)
+ `when`(context.createContextAsUser(any(), anyInt())).thenAnswer { invocation ->
val user = invocation.getArgument<UserHandle>(0)
- whenever(context.user).thenReturn(user)
- whenever(context.userId).thenReturn(user.identifier)
+ `when`(context.user).thenReturn(user)
+ `when`(context.userId).thenReturn(user.identifier)
context
}
- whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
val info = UserInfo(invocation.getArgument<Int>(0), "", UserInfo.FLAG_FULL)
listOf(info)
}
featureFlags.set(Flags.USER_TRACKER_BACKGROUND_CALLBACKS, isBackgroundUserTrackerEnabled)
tracker =
- UserTrackerImpl(
- context,
- { featureFlags },
- userManager,
- iActivityManager,
- dumpManager,
- testScope.backgroundScope,
- testDispatcher,
- handler,
- )
+ UserTrackerImpl(
+ context,
+ { featureFlags },
+ userManager,
+ iActivityManager,
+ dumpManager,
+ testScope.backgroundScope,
+ testDispatcher,
+ handler,
+ )
}
- @Test fun testNotInitialized() = testScope.runTest { assertThat(tracker.initialized).isFalse() }
+ @Test
+ fun testNotInitialized() = testScope.runTest {
+ assertThat(tracker.initialized).isFalse()
+ }
@Test(expected = IllegalStateException::class)
- fun testGetUserIdBeforeInitThrowsException() = testScope.runTest { tracker.userId }
+ fun testGetUserIdBeforeInitThrowsException() = testScope.runTest {
+ tracker.userId
+ }
@Test(expected = IllegalStateException::class)
- fun testGetUserHandleBeforeInitThrowsException() = testScope.runTest { tracker.userHandle }
+ fun testGetUserHandleBeforeInitThrowsException() = testScope.runTest {
+ tracker.userHandle
+ }
@Test(expected = IllegalStateException::class)
- fun testGetUserContextBeforeInitThrowsException() = testScope.runTest { tracker.userContext }
+ fun testGetUserContextBeforeInitThrowsException() = testScope.runTest {
+ tracker.userContext
+ }
@Test(expected = IllegalStateException::class)
- fun testGetUserContentResolverBeforeInitThrowsException() =
- testScope.runTest { tracker.userContentResolver }
+ fun testGetUserContentResolverBeforeInitThrowsException() = testScope.runTest {
+ tracker.userContentResolver
+ }
@Test(expected = IllegalStateException::class)
- fun testGetUserProfilesBeforeInitThrowsException() = testScope.runTest { tracker.userProfiles }
+ fun testGetUserProfilesBeforeInitThrowsException() = testScope.runTest {
+ tracker.userProfiles
+ }
@Test
- fun testInitialize() =
- testScope.runTest {
- tracker.initialize(0)
+ fun testInitialize() = testScope.runTest {
+ tracker.initialize(0)
- assertThat(tracker.initialized).isTrue()
- }
+ assertThat(tracker.initialized).isTrue()
+ }
@Test
- fun testReceiverRegisteredOnInitialize() =
- testScope.runTest {
- tracker.initialize(0)
+ fun testReceiverRegisteredOnInitialize() = testScope.runTest {
+ tracker.initialize(0)
- val captor = ArgumentCaptor.forClass(IntentFilter::class.java)
+ val captor = ArgumentCaptor.forClass(IntentFilter::class.java)
- verify(context)
+ verify(context)
.registerReceiverForAllUsers(eq(tracker), capture(captor), isNull(), eq(handler))
- with(captor.value) {
- assertThat(countActions()).isEqualTo(11)
- assertThat(hasAction(Intent.ACTION_LOCALE_CHANGED)).isTrue()
- assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
- assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
- assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
- assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
- assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
- assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
- assertThat(hasAction(Intent.ACTION_PROFILE_ADDED)).isTrue()
- assertThat(hasAction(Intent.ACTION_PROFILE_REMOVED)).isTrue()
- assertThat(hasAction(Intent.ACTION_PROFILE_AVAILABLE)).isTrue()
- assertThat(hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)).isTrue()
- }
+ with(captor.value) {
+ assertThat(countActions()).isEqualTo(11)
+ assertThat(hasAction(Intent.ACTION_LOCALE_CHANGED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_ADDED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_REMOVED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_AVAILABLE)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)).isTrue()
}
+ }
@Test
- fun testInitialValuesSet() =
- testScope.runTest {
- val testID = 4
- tracker.initialize(testID)
+ fun testInitialValuesSet() = testScope.runTest {
+ val testID = 4
+ tracker.initialize(testID)
- verify(userManager).getProfiles(testID)
+ verify(userManager).getProfiles(testID)
- assertThat(tracker.userId).isEqualTo(testID)
- assertThat(tracker.userHandle).isEqualTo(UserHandle.of(testID))
- assertThat(tracker.userContext.userId).isEqualTo(testID)
- assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(testID))
- assertThat(tracker.userProfiles).hasSize(1)
+ assertThat(tracker.userId).isEqualTo(testID)
+ assertThat(tracker.userHandle).isEqualTo(UserHandle.of(testID))
+ assertThat(tracker.userContext.userId).isEqualTo(testID)
+ assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(testID))
+ assertThat(tracker.userProfiles).hasSize(1)
- val info = tracker.userProfiles[0]
- assertThat(info.id).isEqualTo(testID)
- }
+ val info = tracker.userProfiles[0]
+ assertThat(info.id).isEqualTo(testID)
+ }
@Test
- fun testUserSwitch() =
- testScope.runTest {
- tracker.initialize(0)
- val newID = 5
+ fun testUserSwitch() = testScope.runTest {
+ tracker.initialize(0)
+ val newID = 5
- val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
- verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onBeforeUserSwitching(newID)
- captor.value.onUserSwitching(newID, userSwitchingReply)
- runCurrent()
- verify(userSwitchingReply).sendResult(any())
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onBeforeUserSwitching(newID)
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+ runCurrent()
+ verify(userSwitchingReply).sendResult(any())
- verify(userManager).getProfiles(newID)
+ verify(userManager).getProfiles(newID)
- assertThat(tracker.userId).isEqualTo(newID)
- assertThat(tracker.userHandle).isEqualTo(UserHandle.of(newID))
- assertThat(tracker.userContext.userId).isEqualTo(newID)
- assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(newID))
- assertThat(tracker.userProfiles).hasSize(1)
+ assertThat(tracker.userId).isEqualTo(newID)
+ assertThat(tracker.userHandle).isEqualTo(UserHandle.of(newID))
+ assertThat(tracker.userContext.userId).isEqualTo(newID)
+ assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(newID))
+ assertThat(tracker.userProfiles).hasSize(1)
- val info = tracker.userProfiles[0]
- assertThat(info.id).isEqualTo(newID)
- }
+ val info = tracker.userProfiles[0]
+ assertThat(info.id).isEqualTo(newID)
+ }
@Test
- fun testManagedProfileAvailable() =
- testScope.runTest {
- tracker.initialize(0)
- val profileID = tracker.userId + 10
+ fun testManagedProfileAvailable() = testScope.runTest {
+ tracker.initialize(0)
+ val profileID = tracker.userId + 10
- whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
- val id = invocation.getArgument<Int>(0)
- val info = UserInfo(id, "", UserInfo.FLAG_FULL)
- val infoProfile =
- UserInfo(
- id + 10,
- "",
- "",
- UserInfo.FLAG_MANAGED_PROFILE,
- UserManager.USER_TYPE_PROFILE_MANAGED
- )
- infoProfile.profileGroupId = id
- listOf(info, infoProfile)
- }
-
- val intent =
- Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
- tracker.onReceive(context, intent)
-
- assertThat(tracker.userProfiles.map { it.id })
- .containsExactly(tracker.userId, profileID)
+ `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ val id = invocation.getArgument<Int>(0)
+ val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+ val infoProfile = UserInfo(
+ id + 10,
+ "",
+ "",
+ UserInfo.FLAG_MANAGED_PROFILE,
+ UserManager.USER_TYPE_PROFILE_MANAGED
+ )
+ infoProfile.profileGroupId = id
+ listOf(info, infoProfile)
}
+ val intent = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+ tracker.onReceive(context, intent)
+
+ assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
+ }
+
@Test
- fun testManagedProfileUnavailable() =
- testScope.runTest {
- tracker.initialize(0)
- val profileID = tracker.userId + 10
+ fun testManagedProfileUnavailable() = testScope.runTest {
+ tracker.initialize(0)
+ val profileID = tracker.userId + 10
- whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
- val id = invocation.getArgument<Int>(0)
- val info = UserInfo(id, "", UserInfo.FLAG_FULL)
- val infoProfile =
- UserInfo(
- id + 10,
- "",
- "",
- UserInfo.FLAG_MANAGED_PROFILE or UserInfo.FLAG_QUIET_MODE,
- UserManager.USER_TYPE_PROFILE_MANAGED
- )
- infoProfile.profileGroupId = id
- listOf(info, infoProfile)
- }
-
- val intent =
- Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
- tracker.onReceive(context, intent)
-
- assertThat(tracker.userProfiles.map { it.id })
- .containsExactly(tracker.userId, profileID)
+ `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ val id = invocation.getArgument<Int>(0)
+ val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+ val infoProfile = UserInfo(
+ id + 10,
+ "",
+ "",
+ UserInfo.FLAG_MANAGED_PROFILE or UserInfo.FLAG_QUIET_MODE,
+ UserManager.USER_TYPE_PROFILE_MANAGED
+ )
+ infoProfile.profileGroupId = id
+ listOf(info, infoProfile)
}
+ val intent = Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+ tracker.onReceive(context, intent)
+
+ assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
+ }
+
@Test
- fun testManagedProfileStartedAndRemoved() =
- testScope.runTest {
- tracker.initialize(0)
- val profileID = tracker.userId + 10
+ fun testManagedProfileStartedAndRemoved() = testScope.runTest {
+ tracker.initialize(0)
+ val profileID = tracker.userId + 10
- whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
- val id = invocation.getArgument<Int>(0)
- val info = UserInfo(id, "", UserInfo.FLAG_FULL)
- val infoProfile =
- UserInfo(
- id + 10,
- "",
- "",
- UserInfo.FLAG_MANAGED_PROFILE,
- UserManager.USER_TYPE_PROFILE_MANAGED
- )
- infoProfile.profileGroupId = id
- listOf(info, infoProfile)
- }
-
- // Managed profile started
- val intent =
- Intent(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
- tracker.onReceive(context, intent)
-
- assertThat(tracker.userProfiles.map { it.id })
- .containsExactly(tracker.userId, profileID)
-
- whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
- listOf(UserInfo(invocation.getArgument(0), "", UserInfo.FLAG_FULL))
- }
-
- val intent2 =
- Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
- tracker.onReceive(context, intent2)
-
- assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId)
+ `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ val id = invocation.getArgument<Int>(0)
+ val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+ val infoProfile = UserInfo(
+ id + 10,
+ "",
+ "",
+ UserInfo.FLAG_MANAGED_PROFILE,
+ UserManager.USER_TYPE_PROFILE_MANAGED
+ )
+ infoProfile.profileGroupId = id
+ listOf(info, infoProfile)
}
+ // Managed profile started
+ val intent = Intent(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+ tracker.onReceive(context, intent)
+
+ assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
+
+ `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ listOf(UserInfo(invocation.getArgument(0), "", UserInfo.FLAG_FULL))
+ }
+
+ val intent2 = Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+ tracker.onReceive(context, intent2)
+
+ assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId)
+ }
+
@Test
- fun testCallbackNotCalledOnAdd() =
- testScope.runTest {
- tracker.initialize(0)
- val callback = TestCallback()
+ fun testCallbackNotCalledOnAdd() = testScope.runTest {
+ tracker.initialize(0)
+ val callback = TestCallback()
- tracker.addCallback(callback, executor)
+ tracker.addCallback(callback, executor)
- assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
- assertThat(callback.calledOnUserChanged).isEqualTo(0)
- }
+ assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
+ assertThat(callback.calledOnUserChanged).isEqualTo(0)
+ }
@Test
- fun testCallbackCalledOnUserChanging() =
- testScope.runTest {
- tracker.initialize(0)
- val callback = TestCallback()
- tracker.addCallback(callback, executor)
+ fun testCallbackCalledOnUserChanging() = testScope.runTest {
+ tracker.initialize(0)
+ val callback = TestCallback()
+ tracker.addCallback(callback, executor)
- val newID = 5
+ val newID = 5
- val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
- verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onBeforeUserSwitching(newID)
- captor.value.onUserSwitching(newID, userSwitchingReply)
- runCurrent()
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onBeforeUserSwitching(newID)
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+ runCurrent()
- verify(userSwitchingReply).sendResult(any())
- assertThat(callback.calledOnUserChanging).isEqualTo(1)
- assertThat(callback.lastUser).isEqualTo(newID)
- assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
- }
+ verify(userSwitchingReply).sendResult(any())
+ assertThat(callback.calledOnUserChanging).isEqualTo(1)
+ assertThat(callback.lastUser).isEqualTo(newID)
+ assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
+ }
@Test
- fun testAsyncCallbackWaitsUserToChange() =
- testScope.runTest {
- // Skip this test for CountDownLatch variation. The problem is that there would be a
- // deadlock if the callbacks processing runs on the same thread as the callback (which
- // is blocked by the latch). Before the change it works because the callbacks are
- // processed on a binder thread which is always distinct.
- // This is the issue that this feature addresses.
- assume().that(isBackgroundUserTrackerEnabled).isTrue()
+ fun testAsyncCallbackWaitsUserToChange() = testScope.runTest {
+ // Skip this test for CountDownLatch variation. The problem is that there would be a
+ // deadlock if the callbacks processing runs on the same thread as the callback (which
+ // is blocked by the latch). Before the change it works because the callbacks are
+ // processed on a binder thread which is always distinct.
+ // This is the issue that this feature addresses.
+ assume().that(isBackgroundUserTrackerEnabled).isTrue()
- tracker.initialize(0)
- val callback = TestCallback()
- val callbackExecutor = FakeExecutor(FakeSystemClock())
- tracker.addCallback(callback, callbackExecutor)
+ tracker.initialize(0)
+ val callback = TestCallback()
+ val callbackExecutor = FakeExecutor(FakeSystemClock())
+ tracker.addCallback(callback, callbackExecutor)
- val newID = 5
+ val newID = 5
- val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
- verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onUserSwitching(newID, userSwitchingReply)
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onUserSwitching(newID, userSwitchingReply)
- assertThat(callback.calledOnUserChanging).isEqualTo(0)
- verify(userSwitchingReply, never()).sendResult(any())
+ assertThat(callback.calledOnUserChanging).isEqualTo(0)
+ verify(userSwitchingReply, never()).sendResult(any())
- FakeExecutor.exhaustExecutors(callbackExecutor)
- runCurrent()
- FakeExecutor.exhaustExecutors(callbackExecutor)
- runCurrent()
+ FakeExecutor.exhaustExecutors(callbackExecutor)
+ runCurrent()
+ FakeExecutor.exhaustExecutors(callbackExecutor)
+ runCurrent()
- assertThat(callback.calledOnUserChanging).isEqualTo(1)
- verify(userSwitchingReply).sendResult(any())
- }
+ assertThat(callback.calledOnUserChanging).isEqualTo(1)
+ verify(userSwitchingReply).sendResult(any())
+ }
@Test
- fun testCallbackCalledOnUserChanged() =
- testScope.runTest {
- tracker.initialize(0)
- val callback = TestCallback()
- tracker.addCallback(callback, executor)
+ fun testCallbackCalledOnUserChanged() = testScope.runTest {
+ tracker.initialize(0)
+ val callback = TestCallback()
+ tracker.addCallback(callback, executor)
- val newID = 5
+ val newID = 5
- val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
- verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onBeforeUserSwitching(newID)
- captor.value.onUserSwitchComplete(newID)
- runCurrent()
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onBeforeUserSwitching(newID)
+ captor.value.onUserSwitchComplete(newID)
+ runCurrent()
- assertThat(callback.calledOnUserChanged).isEqualTo(1)
- assertThat(callback.lastUser).isEqualTo(newID)
- assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
- assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
- assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(newID)
- }
+ assertThat(callback.calledOnUserChanged).isEqualTo(1)
+ assertThat(callback.lastUser).isEqualTo(newID)
+ assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
+ assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
+ assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(newID)
+ }
@Test
- fun testCallbackCalledOnUserInfoChanged() =
- testScope.runTest {
- tracker.initialize(0)
- val callback = TestCallback()
- tracker.addCallback(callback, executor)
- val profileID = tracker.userId + 10
+ fun testCallbackCalledOnUserInfoChanged() = testScope.runTest {
+ tracker.initialize(0)
+ val callback = TestCallback()
+ tracker.addCallback(callback, executor)
+ val profileID = tracker.userId + 10
- whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
- val id = invocation.getArgument<Int>(0)
- val info = UserInfo(id, "", UserInfo.FLAG_FULL)
- val infoProfile =
- UserInfo(
- id + 10,
- "",
- "",
- UserInfo.FLAG_MANAGED_PROFILE,
- UserManager.USER_TYPE_PROFILE_MANAGED
- )
- infoProfile.profileGroupId = id
- listOf(info, infoProfile)
- }
-
- val intent =
- Intent(Intent.ACTION_USER_INFO_CHANGED)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
-
- tracker.onReceive(context, intent)
-
- assertThat(callback.calledOnUserChanged).isEqualTo(0)
- assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
- assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
+ `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ val id = invocation.getArgument<Int>(0)
+ val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+ val infoProfile = UserInfo(
+ id + 10,
+ "",
+ "",
+ UserInfo.FLAG_MANAGED_PROFILE,
+ UserManager.USER_TYPE_PROFILE_MANAGED
+ )
+ infoProfile.profileGroupId = id
+ listOf(info, infoProfile)
}
+ val intent = Intent(Intent.ACTION_USER_INFO_CHANGED)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+
+ tracker.onReceive(context, intent)
+
+ assertThat(callback.calledOnUserChanged).isEqualTo(0)
+ assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
+ assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
+ }
+
@Test
- fun testCallbackRemoved() =
- testScope.runTest {
- tracker.initialize(0)
- val newID = 5
- val profileID = newID + 10
+ fun testCallbackRemoved() = testScope.runTest {
+ tracker.initialize(0)
+ val newID = 5
+ val profileID = newID + 10
- val callback = TestCallback()
- tracker.addCallback(callback, executor)
- tracker.removeCallback(callback)
+ val callback = TestCallback()
+ tracker.addCallback(callback, executor)
+ tracker.removeCallback(callback)
- val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
- verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onUserSwitching(newID, userSwitchingReply)
- runCurrent()
- verify(userSwitchingReply).sendResult(any())
- captor.value.onUserSwitchComplete(newID)
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+ runCurrent()
+ verify(userSwitchingReply).sendResult(any())
+ captor.value.onUserSwitchComplete(newID)
- val intentProfiles =
- Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+ val intentProfiles = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
- tracker.onReceive(context, intentProfiles)
+ tracker.onReceive(context, intentProfiles)
- assertThat(callback.calledOnUserChanging).isEqualTo(0)
- assertThat(callback.calledOnUserChanged).isEqualTo(0)
- assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
- }
+ assertThat(callback.calledOnUserChanging).isEqualTo(0)
+ assertThat(callback.calledOnUserChanged).isEqualTo(0)
+ assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
+ }
private class TestCallback : UserTracker.Callback {
var calledOnUserChanging = 0
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index ce79fbd..7bc6d4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -132,10 +132,10 @@
)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isInstanceOf(OngoingActivityChipModel.ChipIcon.Basic::class.java)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
assertThat(icon.contentDescription).isNotNull()
@@ -170,10 +170,10 @@
)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isInstanceOf(OngoingActivityChipModel.ChipIcon.Basic::class.java)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
assertThat(icon.contentDescription).isNotNull()
@@ -206,10 +206,10 @@
repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = null))
assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isInstanceOf(OngoingActivityChipModel.ChipIcon.Basic::class.java)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
assertThat(icon.contentDescription).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index a8d2c5b..77992db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -127,7 +127,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
assertThat((icon.contentDescription as ContentDescription.Resource).res)
@@ -146,7 +146,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
assertThat((icon.contentDescription as ContentDescription.Resource).res)
@@ -184,7 +184,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
// This content description is just generic "Casting", not "Casting screen"
@@ -214,7 +214,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
// MediaProjection == screen casting, so this content description reflects that we're
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
new file mode 100644
index 0000000..8576893
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
+
+import android.content.packageManager
+import android.graphics.drawable.BitmapDrawable
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+@SmallTest
+class DemoRonChipViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val commandRegistry = kosmos.commandRegistry
+ private val pw = PrintWriter(StringWriter())
+
+ private val underTest = kosmos.demoRonChipViewModel
+
+ @Before
+ fun setUp() {
+ underTest.start()
+ whenever(kosmos.packageManager.getApplicationIcon(any<String>()))
+ .thenReturn(BitmapDrawable())
+ }
+
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+ fun chip_flagOff_hidden() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ addDemoRonChip()
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+ fun chip_noPackage_hidden() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ commandRegistry.onShellCommand(pw, arrayOf("demo-ron"))
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+ fun chip_hasPackage_shown() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "-p", "com.android.systemui"))
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+ fun chip_hasText_shownWithText() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ commandRegistry.onShellCommand(
+ pw,
+ arrayOf("demo-ron", "-p", "com.android.systemui", "-t", "test")
+ )
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Text::class.java)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+ fun chip_hasHideArg_hidden() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ // First, show a chip
+ addDemoRonChip()
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+ // Then, hide the chip
+ commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "--hide"))
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ }
+
+ private fun addDemoRonChip() {
+ Companion.addDemoRonChip(commandRegistry, pw)
+ }
+
+ companion object {
+ fun addDemoRonChip(commandRegistry: CommandRegistry, pw: PrintWriter) {
+ commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "-p", "com.android.systemui"))
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 804eb5c..16101bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -150,7 +150,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_screenrecord)
assertThat(icon.contentDescription).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index a2ef599..791a21d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -135,7 +135,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
assertThat(icon.contentDescription).isNotNull()
@@ -152,7 +152,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
assertThat(icon.contentDescription).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
index a724cfaa..4977c548 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
@@ -154,5 +154,7 @@
}
private fun createIcon(@DrawableRes drawable: Int) =
- OngoingActivityChipModel.ChipIcon.Basic(Icon.Resource(drawable, contentDescription = null))
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(
+ Icon.Resource(drawable, contentDescription = null)
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 556ec6a..bd5df07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -19,9 +19,12 @@
import android.content.DialogInterface
import android.content.packageManager
import android.content.pm.PackageManager
+import android.graphics.drawable.BitmapDrawable
+import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
@@ -36,8 +39,11 @@
import com.android.systemui.screenrecord.data.repository.screenRecordRepository
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModelTest.Companion.addDemoRonChip
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.commandline.commandRegistry
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
@@ -45,6 +51,8 @@
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -68,11 +76,14 @@
private val kosmos = Kosmos().also { it.testCase = this }
private val testScope = kosmos.testScope
private val systemClock = kosmos.fakeSystemClock
+ private val commandRegistry = kosmos.commandRegistry
private val screenRecordState = kosmos.screenRecordRepository.screenRecordState
private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState
private val callRepo = kosmos.ongoingCallRepository
+ private val pw = PrintWriter(StringWriter())
+
private val mockSystemUIDialog = mock<SystemUIDialog>()
private val chipBackgroundView = mock<ChipBackgroundContainer>()
private val chipView =
@@ -90,6 +101,9 @@
@Before
fun setUp() {
setUpPackageManagerForMediaProjection(kosmos)
+ kosmos.demoRonChipViewModel.start()
+ whenever(kosmos.packageManager.getApplicationIcon(any<String>()))
+ .thenReturn(BitmapDrawable())
}
@Test
@@ -169,15 +183,24 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
fun chip_higherPriorityChipAdded_lowerPriorityChipReplaced() =
testScope.runTest {
- // Start with just the lower priority call chip
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ // Start with just the lowest priority chip shown
+ addDemoRonChip(commandRegistry, pw)
+ // And everything else hidden
+ callRepo.setOngoingCallState(OngoingCallModel.NoCall)
mediaProjectionState.value = MediaProjectionState.NotProjecting
screenRecordState.value = ScreenRecordModel.DoingNothing
val latest by collectLastValue(underTest.chip)
+ assertIsDemoRonChip(latest)
+
+ // WHEN the higher priority call chip is added
+ callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+
+ // THEN the higher priority call chip is used
assertIsCallChip(latest)
// WHEN the higher priority media projection chip is added
@@ -199,14 +222,15 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
fun chip_highestPriorityChipRemoved_showsNextPriorityChip() =
testScope.runTest {
// WHEN all chips are active
screenRecordState.value = ScreenRecordModel.Recording
mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
-
callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ addDemoRonChip(commandRegistry, pw)
val latest by collectLastValue(underTest.chip)
@@ -224,6 +248,12 @@
// THEN the lower priority call is used
assertIsCallChip(latest)
+
+ // WHEN the higher priority call is removed
+ callRepo.setOngoingCallState(OngoingCallModel.NoCall)
+
+ // THEN the lower priority demo RON is used
+ assertIsDemoRonChip(latest)
}
/** Regression test for b/347726238. */
@@ -338,7 +368,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_screenrecord)
}
@@ -347,7 +377,7 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
}
@@ -356,9 +386,15 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.Basic)
+ as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
}
+
+ fun assertIsDemoRonChip(latest: OngoingActivityChipModel?) {
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat((latest as OngoingActivityChipModel.Shown).icon)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.FullColorAppIcon::class.java)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index caa1779..1e2648b22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -257,13 +257,13 @@
private State createShellState() {
State state = new VolumeDialogController.State();
- for (int i = AudioManager.STREAM_VOICE_CALL; i <= AudioManager.STREAM_ACCESSIBILITY; i++) {
+ for (int stream : STREAMS.keySet()) {
VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState();
- ss.name = STREAMS.get(i);
+ ss.name = STREAMS.get(stream);
ss.level = 1;
ss.levelMin = 0;
ss.levelMax = 25;
- state.states.append(i, ss);
+ state.states.append(stream, ss);
}
return state;
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 616f2b6..a73c184 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -102,6 +102,45 @@
}
/**
+ * Sends the provided [step] and makes sure that all previous [TransitionState]'s are sent when
+ * [fillInSteps] is true. e.g. when a step FINISHED is provided, a step with STARTED and RUNNING
+ * is also sent.
+ */
+ suspend fun sendTransitionSteps(
+ step: TransitionStep,
+ testScope: TestScope,
+ fillInSteps: Boolean = true,
+ ) {
+ if (fillInSteps && step.transitionState != TransitionState.STARTED) {
+ sendTransitionStep(
+ step =
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = step.from,
+ to = step.to,
+ value = 0f,
+ )
+ )
+ testScope.testScheduler.runCurrent()
+
+ if (step.transitionState != TransitionState.RUNNING) {
+ sendTransitionStep(
+ step =
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = step.from,
+ to = step.to,
+ value = 0.6f,
+ )
+ )
+ testScope.testScheduler.runCurrent()
+ }
+ }
+ sendTransitionStep(step = step)
+ testScope.testScheduler.runCurrent()
+ }
+
+ /**
* Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
*
* By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index b68d6a0..8e8f4b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -27,7 +27,6 @@
KeyguardTransitionInteractor(
scope = applicationCoroutineScope,
repository = keyguardTransitionRepository,
- keyguardRepository = keyguardRepository,
fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
fromPrimaryBouncerTransitionInteractor = { fromPrimaryBouncerTransitionInteractor },
fromAodTransitionInteractor = { fromAodTransitionInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
index 1652462..2eb7ce6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
@@ -26,17 +26,25 @@
class FakeSysUiViewModel(
private val onActivation: () -> Unit = {},
private val onDeactivation: () -> Unit = {},
- private val upstreamFlow: Flow<Boolean> = flowOf(true),
- private val upstreamStateFlow: StateFlow<Boolean> = MutableStateFlow(true).asStateFlow(),
-) : SysUiViewModel, ExclusiveActivatable() {
+ upstreamFlow: Flow<Boolean> = flowOf(true),
+ upstreamStateFlow: StateFlow<Boolean> = MutableStateFlow(true).asStateFlow(),
+) : ExclusiveActivatable() {
var activationCount = 0
var cancellationCount = 0
- private val hydrator = Hydrator()
+ private val hydrator = Hydrator("test")
val stateBackedByFlow: Boolean by
- hydrator.hydratedStateOf(initialValue = true, source = upstreamFlow)
- val stateBackedByStateFlow: Boolean by hydrator.hydratedStateOf(source = upstreamStateFlow)
+ hydrator.hydratedStateOf(
+ traceName = "test",
+ initialValue = true,
+ source = upstreamFlow,
+ )
+ val stateBackedByStateFlow: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "test",
+ source = upstreamStateFlow,
+ )
override suspend fun onActivated(): Nothing {
activationCount++
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
index 2127a88..632436a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
@@ -18,7 +18,6 @@
import android.app.smartspace.SmartspaceManager
import android.content.applicationContext
-import android.os.fakeExecutorHandler
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.concurrency.fakeExecutor
@@ -45,7 +44,7 @@
backgroundExecutor = fakeExecutor,
uiExecutor = fakeExecutor,
foregroundExecutor = fakeExecutor,
- handler = fakeExecutorHandler,
+ mainDispatcher = testDispatcher,
mediaControllerFactory = fakeMediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
@@ -60,5 +59,6 @@
smartspaceManager = SmartspaceManager(applicationContext),
keyguardUpdateMonitor = keyguardUpdateMonitor,
mediaDataRepository = mediaDataRepository,
+ mediaDataLoader = { mediaDataLoader },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLoggerKosmos.kt
new file mode 100644
index 0000000..76d71dd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.media.controls.domain.pipeline
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+var Kosmos.mediaDeviceLogger by
+ Kosmos.Fixture { MediaDeviceLogger(logcatLogBuffer("MediaDeviceLoggerKosmos")) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
index c479ce6..11408d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
@@ -41,5 +41,6 @@
},
fgExecutor = fakeExecutor,
bgExecutor = fakeExecutor,
+ logger = mediaDeviceLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
index 53d3c01..59f2b94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
@@ -19,6 +19,7 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
@@ -36,20 +37,26 @@
suspend fun Kosmos.setTransition(
sceneTransition: ObservableTransitionState,
stateTransition: TransitionStep? = null,
+ fillInStateSteps: Boolean = true,
scope: TestScope = testScope,
repository: SceneContainerRepository = sceneContainerRepository
) {
+ var state: TransitionStep? = stateTransition
if (SceneContainerFlag.isEnabled) {
setSceneTransition(sceneTransition, scope, repository)
- } else {
- if (stateTransition == null) throw IllegalArgumentException("No transitionStep provided")
- fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = stateTransition.from,
- to = stateTransition.to,
- testScope = scope,
- throughTransitionState = stateTransition.transitionState
- )
+
+ if (state != null) {
+ state = getStateWithUndefined(sceneTransition, state)
+ }
}
+
+ if (state == null) return
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ step = state,
+ testScope = scope,
+ fillInSteps = fillInStateSteps,
+ )
+ scope.testScheduler.runCurrent()
}
fun Kosmos.setSceneTransition(
@@ -59,7 +66,7 @@
) {
repository.setTransitionState(mutableTransitionState)
mutableTransitionState.value = transition
- scope.runCurrent()
+ scope.testScheduler.runCurrent()
}
fun Transition(
@@ -87,3 +94,43 @@
fun Idle(currentScene: SceneKey): ObservableTransitionState.Idle {
return ObservableTransitionState.Idle(currentScene)
}
+
+private fun getStateWithUndefined(
+ sceneTransition: ObservableTransitionState,
+ state: TransitionStep
+): TransitionStep {
+ return when (sceneTransition) {
+ is ObservableTransitionState.Idle -> {
+ TransitionStep(
+ from = state.from,
+ to =
+ if (sceneTransition.currentScene != Scenes.Lockscreen) {
+ KeyguardState.UNDEFINED
+ } else {
+ state.to
+ },
+ value = state.value,
+ transitionState = state.transitionState
+ )
+ }
+ is ObservableTransitionState.Transition -> {
+ TransitionStep(
+ from =
+ if (sceneTransition.fromScene != Scenes.Lockscreen) {
+ KeyguardState.UNDEFINED
+ } else {
+ state.from
+ },
+ to =
+ if (sceneTransition.toScene != Scenes.Lockscreen) {
+ KeyguardState.UNDEFINED
+ } else {
+ state.from
+ },
+ value = state.value,
+ transitionState = state.transitionState
+ )
+ }
+ else -> state
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt
new file mode 100644
index 0000000..c0d65a0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
+
+import android.content.packageManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.util.time.fakeSystemClock
+
+val Kosmos.demoRonChipViewModel: DemoRonChipViewModel by
+ Kosmos.Fixture {
+ DemoRonChipViewModel(
+ commandRegistry = commandRegistry,
+ packageManager = packageManager,
+ systemClock = fakeSystemClock,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
index 16e288f..5382c1c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.chips.call.ui.viewmodel.callChipViewModel
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.castToOtherDeviceChipViewModel
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel
import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.screenRecordChipViewModel
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
import com.android.systemui.statusbar.chips.statusBarChipsLogger
@@ -32,6 +33,7 @@
shareToAppChipViewModel = shareToAppChipViewModel,
castToOtherDeviceChipViewModel = castToOtherDeviceChipViewModel,
callChipViewModel = callChipViewModel,
+ demoRonChipViewModel = demoRonChipViewModel,
logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/commandline/CommandRegistryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/commandline/CommandRegistryKosmos.kt
new file mode 100644
index 0000000..14777b4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/commandline/CommandRegistryKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.commandline
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.commandRegistry: CommandRegistry by
+ Kosmos.Fixture {
+ CommandRegistry(
+ context = applicationContext,
+ // Immediately run anything that comes in
+ mainExecutor = { command -> command.run() },
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
index 63386d0..dd5bbf3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
@@ -18,7 +18,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.volume.data.repository.audioRepository
import com.android.systemui.volume.domain.interactor.audioModeInteractor
import com.android.systemui.volume.mediaOutputInteractor
@@ -27,7 +26,6 @@
AudioSlidersInteractor(
applicationCoroutineScope,
mediaOutputInteractor,
- audioRepository,
audioModeInteractor,
)
}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index be4cd76..9b0c8e5 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -160,6 +160,7 @@
"ravenwood-framework",
"services.core.ravenwood",
"junit",
+ "framework-annotations-lib",
],
sdk_version: "core_current",
visibility: ["//frameworks/base"],
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
index 3a24c0e..e8f59db 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
@@ -26,6 +26,10 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+/**
+ * Test to ensure @DisabledOnRavenwood works. Note, now the DisabledOnRavenwood annotation
+ * is handled by the test runner, so it won't really need the class rule.
+ */
@RunWith(AndroidJUnit4.class)
@DisabledOnRavenwood
public class RavenwoodClassRuleDeviceOnlyTest {
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
index 0f8be0e..7ef672e 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
@@ -34,7 +34,12 @@
@BeforeClass
public static void beforeClass() {
- Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+ // This method shouldn't be called -- unless RUN_DISABLED_TESTS is enabled.
+
+ // If we're doing RUN_DISABLED_TESTS, don't throw here, because that'd confuse junit.
+ if (!RavenwoodRule.private$ravenwood().isRunningDisabledTests()) {
+ Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+ }
}
@Test
@@ -46,7 +51,10 @@
public static void afterClass() {
if (RavenwoodRule.isOnRavenwood()) {
Log.e(TAG, "Even @AfterClass shouldn't be executed!");
- System.exit(1);
+
+ if (!RavenwoodRule.private$ravenwood().isRunningDisabledTests()) {
+ System.exit(1);
+ }
}
}
}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
new file mode 100644
index 0000000..c77841b
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for "RAVENWOOD_RUN_DISABLED_TESTS" with "REALLY_DISABLED" set.
+ *
+ * This test is only executed on Ravenwood.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodRunDisabledTestsReallyDisabledTest {
+ private static final String TAG = "RavenwoodRunDisabledTestsTest";
+
+ private static final CallTracker sCallTracker = new CallTracker();
+
+ @RavenwoodTestRunnerInitializing
+ public static void ravenwoodRunnerInitializing() {
+ RavenwoodRule.private$ravenwood().overrideRunDisabledTest(true,
+ "\\#testReallyDisabled$");
+ }
+
+ /**
+ * This test gets to run with RAVENWOOD_RUN_DISABLED_TESTS set.
+ */
+ @Test
+ @DisabledOnRavenwood
+ public void testDisabledTestGetsToRun() {
+ if (!RavenwoodRule.isOnRavenwood()) {
+ return;
+ }
+ sCallTracker.incrementMethodCallCount();
+
+ fail("This test won't pass on Ravenwood.");
+ }
+
+ /**
+ * This will still not be executed due to the "really disabled" pattern.
+ */
+ @Test
+ @DisabledOnRavenwood
+ public void testReallyDisabled() {
+ if (!RavenwoodRule.isOnRavenwood()) {
+ return;
+ }
+ sCallTracker.incrementMethodCallCount();
+
+ fail("This test won't pass on Ravenwood.");
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ if (!RavenwoodRule.isOnRavenwood()) {
+ return;
+ }
+ Log.i(TAG, "afterClass called");
+
+ sCallTracker.assertCallsOrDie(
+ "testDisabledTestGetsToRun", 1,
+ "testReallyDisabled", 0
+ );
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
new file mode 100644
index 0000000..ea1a29d
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for "RAVENWOOD_RUN_DISABLED_TESTS". (with no "REALLY_DISABLED" set.)
+ *
+ * This test is only executed on Ravenwood.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodRunDisabledTestsTest {
+ private static final String TAG = "RavenwoodRunDisabledTestsTest";
+
+ @Rule
+ public ExpectedException mExpectedException = ExpectedException.none();
+
+ private static final CallTracker sCallTracker = new CallTracker();
+
+ @RavenwoodTestRunnerInitializing
+ public static void ravenwoodRunnerInitializing() {
+ RavenwoodRule.private$ravenwood().overrideRunDisabledTest(true, null);
+ }
+
+ @Test
+ @DisabledOnRavenwood
+ public void testDisabledTestGetsToRun() {
+ if (!RavenwoodRule.isOnRavenwood()) {
+ return;
+ }
+ sCallTracker.incrementMethodCallCount();
+
+ fail("This test won't pass on Ravenwood.");
+ }
+
+ @Test
+ @DisabledOnRavenwood
+ public void testDisabledButPass() {
+ if (!RavenwoodRule.isOnRavenwood()) {
+ return;
+ }
+ sCallTracker.incrementMethodCallCount();
+
+ // When a @DisabledOnRavenwood actually passed, the runner should make fail().
+ mExpectedException.expectMessage("it actually passed under Ravenwood");
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ if (!RavenwoodRule.isOnRavenwood()) {
+ return;
+ }
+ Log.i(TAG, "afterClass called");
+
+ sCallTracker.assertCallsOrDie(
+ "testDisabledTestGetsToRun", 1,
+ "testDisabledButPass", 1
+ );
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
index 03600ad..1da93eb 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -17,14 +17,16 @@
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
+import static org.junit.Assert.fail;
+
import android.os.Bundle;
import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order;
import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope;
+import android.platform.test.ravenwood.RavenwoodTestStats.Result;
+import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.ravenwood.common.RavenwoodCommonUtils;
-
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runners.model.TestClass;
@@ -38,12 +40,24 @@
private RavenwoodAwareTestRunnerHook() {
}
- private static void log(String message) {
- RavenwoodCommonUtils.log(TAG, message);
+ private static RavenwoodTestStats sStats; // lazy initialization.
+ private static Description sCurrentClassDescription;
+
+ private static RavenwoodTestStats getStats() {
+ if (sStats == null) {
+ // We don't want to throw in the static initializer, because tradefed may not report
+ // it properly, so we initialize it here.
+ sStats = new RavenwoodTestStats();
+ }
+ return sStats;
}
+ /**
+ * Called when a runner starts, before the inner runner gets a chance to run.
+ */
public static void onRunnerInitializing(Runner runner, TestClass testClass) {
- log("onRunnerStart: testClass=" + testClass + " runner=" + runner);
+ // This log call also ensures the framework JNI is loaded.
+ Log.i(TAG, "onRunnerInitializing: testClass=" + testClass + " runner=" + runner);
// TODO: Move the initialization code to a better place.
@@ -52,26 +66,97 @@
"androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
+
// This is needed to make AndroidJUnit4ClassRunner happy.
InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
}
+ /**
+ * Called when a whole test class is skipped.
+ */
+ public static void onClassSkipped(Description description) {
+ Log.i(TAG, "onClassSkipped: description=" + description);
+ getStats().onClassSkipped(description);
+ }
+
+ /**
+ * Called before a test / class.
+ *
+ * Return false if it should be skipped.
+ */
public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
Scope scope, Order order) {
- log("onBefore: description=" + description + ", " + scope + ", " + order);
+ Log.i(TAG, "onBefore: description=" + description + ", " + scope + ", " + order);
+
+ if (scope == Scope.Class && order == Order.First) {
+ // Keep track of the current class.
+ sCurrentClassDescription = description;
+ }
// Class-level annotations are checked by the runner already, so we only check
// method-level annotations here.
if (scope == Scope.Instance && order == Order.First) {
- if (!RavenwoodRule.shouldEnableOnRavenwood(description)) {
+ if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(
+ description, true)) {
+ getStats().onTestFinished(sCurrentClassDescription, description, Result.Skipped);
return false;
}
}
return true;
}
- public static void onAfter(RavenwoodAwareTestRunner runner, Description description,
+ /**
+ * Called after a test / class.
+ *
+ * Return false if the exception should be ignored.
+ */
+ public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description,
Scope scope, Order order, Throwable th) {
- log("onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
+ Log.i(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
+
+ if (scope == Scope.Instance && order == Order.First) {
+ getStats().onTestFinished(sCurrentClassDescription, description,
+ th == null ? Result.Passed : Result.Failed);
+
+ } else if (scope == Scope.Class && order == Order.Last) {
+ getStats().onClassFinished(sCurrentClassDescription);
+ }
+
+ // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
+ if (RavenwoodRule.private$ravenwood().isRunningDisabledTests()
+ && scope == Scope.Instance && order == Order.First) {
+
+ boolean isTestEnabled = RavenwoodEnablementChecker.shouldEnableOnRavenwood(
+ description, false);
+ if (th == null) {
+ // Test passed. Is the test method supposed to be enabled?
+ if (isTestEnabled) {
+ // Enabled and didn't throw, okay.
+ return true;
+ } else {
+ // Disabled and didn't throw. We should report it.
+ fail("Test wasn't included under Ravenwood, but it actually "
+ + "passed under Ravenwood; consider updating annotations");
+ return true; // unreachable.
+ }
+ } else {
+ // Test failed.
+ if (isTestEnabled) {
+ // Enabled but failed. We should throw the exception.
+ return true;
+ } else {
+ // Disabled and failed. Expected. Don't throw.
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Called by {@link RavenwoodAwareTestRunner} to see if it should run a test class or not.
+ */
+ public static boolean shouldRunClassOnRavenwood(Class<?> clazz) {
+ return RavenwoodEnablementChecker.shouldRunClassOnRavenwood(clazz, true);
}
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
new file mode 100644
index 0000000..77275c4
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
@@ -0,0 +1,117 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.annotations.EnabledOnRavenwood;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+
+import org.junit.runner.Description;
+
+/**
+ * Calculates which tests need to be executed on Ravenwood.
+ */
+public class RavenwoodEnablementChecker {
+ private static final String TAG = "RavenwoodDisablementChecker";
+
+ private RavenwoodEnablementChecker() {
+ }
+
+ /**
+ * Determine if the given {@link Description} should be enabled when running on the
+ * Ravenwood test environment.
+ *
+ * A more specific method-level annotation always takes precedence over any class-level
+ * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
+ * an {@link DisabledOnRavenwood} annotation.
+ */
+ public static boolean shouldEnableOnRavenwood(Description description,
+ boolean takeIntoAccountRunDisabledTestsFlag) {
+ // First, consult any method-level annotations
+ if (description.isTest()) {
+ Boolean result = null;
+
+ // Stopgap for http://g/ravenwood/EPAD-N5ntxM
+ if (description.getMethodName().endsWith("$noRavenwood")) {
+ result = false;
+ } else if (description.getAnnotation(EnabledOnRavenwood.class) != null) {
+ result = true;
+ } else if (description.getAnnotation(DisabledOnRavenwood.class) != null) {
+ result = false;
+ } else if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ result = false;
+ }
+ if (result != null) {
+ if (takeIntoAccountRunDisabledTestsFlag
+ && RavenwoodRule.private$ravenwood().isRunningDisabledTests()) {
+ result = !shouldStillIgnoreInProbeIgnoreMode(
+ description.getTestClass(), description.getMethodName());
+ }
+ }
+ if (result != null) {
+ return result;
+ }
+ }
+
+ // Otherwise, consult any class-level annotations
+ return shouldRunClassOnRavenwood(description.getTestClass(),
+ takeIntoAccountRunDisabledTestsFlag);
+ }
+
+ public static boolean shouldRunClassOnRavenwood(@NonNull Class<?> testClass,
+ boolean takeIntoAccountRunDisabledTestsFlag) {
+ boolean result = true;
+ if (testClass.getAnnotation(EnabledOnRavenwood.class) != null) {
+ result = true;
+ } else if (testClass.getAnnotation(DisabledOnRavenwood.class) != null) {
+ result = false;
+ } else if (testClass.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ result = false;
+ }
+ if (!result) {
+ if (takeIntoAccountRunDisabledTestsFlag
+ && RavenwoodRule.private$ravenwood().isRunningDisabledTests()) {
+ result = !shouldStillIgnoreInProbeIgnoreMode(testClass, null);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Check if a test should _still_ disabled even if {@code RUN_DISABLED_TESTS}
+ * is true, using {@code REALLY_DISABLED_PATTERN}.
+ *
+ * This only works on tests, not on classes.
+ */
+ static boolean shouldStillIgnoreInProbeIgnoreMode(
+ @NonNull Class<?> testClass, @Nullable String methodName) {
+ if (RavenwoodRule.private$ravenwood().getReallyDisabledPattern().pattern().isEmpty()) {
+ return false;
+ }
+
+ final var fullname = testClass.getName() + (methodName != null ? "#" + methodName : "");
+
+ System.out.println("XXX=" + fullname);
+
+ if (RavenwoodRule.private$ravenwood().getReallyDisabledPattern().matcher(fullname).find()) {
+ System.out.println("Still ignoring " + fullname);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 7b4c173..a2088fd 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -40,7 +40,6 @@
import com.android.server.LocalServices;
import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
import java.io.File;
import java.io.IOException;
@@ -226,11 +225,6 @@
}
}
- public static void validate(Statement base, Description description,
- boolean enableOptionalValidation) {
- // Nothing to check, for now.
- }
-
/**
* Set the current configuration to the actual SystemProperties.
*/
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
new file mode 100644
index 0000000..631f68f
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -0,0 +1,163 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import android.util.Log;
+
+import org.junit.runner.Description;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Creats a "stats" CSV file containing the test results.
+ *
+ * The output file is created as `/tmp/Ravenwood-stats_[TEST-MODULE=NAME]_[TIMESTAMP].csv`.
+ * A symlink to the latest result will be created as
+ * `/tmp/Ravenwood-stats_[TEST-MODULE=NAME]_latest.csv`.
+ */
+public class RavenwoodTestStats {
+ private static final String TAG = "RavenwoodTestStats";
+ private static final String HEADER = "Module,Class,ClassDesc,Passed,Failed,Skipped";
+
+ public enum Result {
+ Passed,
+ Failed,
+ Skipped,
+ }
+
+ private final File mOutputFile;
+ private final PrintWriter mOutputWriter;
+ private final String mTestModuleName;
+
+ public final Map<Description, Map<Description, Result>> mStats = new HashMap<>();
+
+ /** Ctor */
+ public RavenwoodTestStats() {
+ mTestModuleName = guessTestModuleName();
+
+ var basename = "Ravenwood-stats_" + mTestModuleName + "_";
+
+ // Get the current time
+ LocalDateTime now = LocalDateTime.now();
+ DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
+
+ var tmpdir = System.getProperty("java.io.tmpdir");
+ mOutputFile = new File(tmpdir, basename + now.format(fmt) + ".csv");
+
+ try {
+ mOutputWriter = new PrintWriter(mOutputFile);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to crete logfile. File=" + mOutputFile, e);
+ }
+
+ // Crete the "latest" symlink.
+ Path symlink = Paths.get(tmpdir, basename + "latest.csv");
+ try {
+ if (Files.exists(symlink)) {
+ Files.delete(symlink);
+ }
+ Files.createSymbolicLink(symlink, Paths.get(mOutputFile.getName()));
+
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to crete logfile. File=" + mOutputFile, e);
+ }
+
+ Log.i(TAG, "Test result stats file: " + mOutputFile);
+
+ // Print the header.
+ mOutputWriter.println(HEADER);
+ mOutputWriter.flush();
+ }
+
+ private String guessTestModuleName() {
+ // Assume the current directory name is the test module name.
+ File cwd;
+ try {
+ cwd = new File(".").getCanonicalFile();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to get the current directory", e);
+ }
+ return cwd.getName();
+ }
+
+ private void addResult(Description classDescription, Description methodDescription,
+ Result result) {
+ mStats.compute(classDescription, (classDesc, value) -> {
+ if (value == null) {
+ value = new HashMap<>();
+ }
+ value.put(methodDescription, result);
+ return value;
+ });
+ }
+
+ public void onClassSkipped(Description classDescription) {
+ addResult(classDescription, Description.EMPTY, Result.Skipped);
+ onClassFinished(classDescription);
+ }
+
+ public void onTestFinished(Description classDescription, Description testDescription,
+ Result result) {
+ addResult(classDescription, testDescription, result);
+ }
+
+ public void onClassFinished(Description classDescription) {
+ int passed = 0;
+ int skipped = 0;
+ int failed = 0;
+ for (var e : mStats.get(classDescription).values()) {
+ switch (e) {
+ case Passed: passed++; break;
+ case Skipped: skipped++; break;
+ case Failed: failed++; break;
+ }
+ }
+
+ var testClass = extractTestClass(classDescription);
+
+ mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
+ mTestModuleName, (testClass == null ? "?" : testClass.getCanonicalName()),
+ classDescription, passed, failed, skipped);
+ mOutputWriter.flush();
+ }
+
+ /**
+ * Try to extract the class from a description, which is needed because
+ * ParameterizedAndroidJunit4's description doesn't contain a class.
+ */
+ private Class<?> extractTestClass(Description desc) {
+ if (desc.getTestClass() != null) {
+ return desc.getTestClass();
+ }
+ // Look into the children.
+ for (var child : desc.getChildren()) {
+ var fromChild = extractTestClass(child);
+ if (fromChild != null) {
+ return fromChild;
+ }
+ }
+ return null;
+ }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index a4fa41a..2b55ac5 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -15,8 +15,6 @@
*/
package android.platform.test.ravenwood;
-import static android.platform.test.ravenwood.RavenwoodRule.shouldRunCassOnRavenwood;
-
import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicVoidMethod;
import static com.android.ravenwood.common.RavenwoodCommonUtils.isOnRavenwood;
@@ -164,7 +162,8 @@
* If the class has @DisabledOnRavenwood, then we'll delegate to ClassSkippingTestRunner,
* which simply skips it.
*/
- if (isOnRavenwood() && !shouldRunCassOnRavenwood(mTestClsas.getJavaClass())) {
+ if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood(
+ mTestClsas.getJavaClass())) {
mRealRunner = new ClassSkippingTestRunner(mTestClsas);
return;
}
@@ -238,6 +237,7 @@
public void run(RunNotifier notifier) {
if (mRealRunner instanceof ClassSkippingTestRunner) {
mRealRunner.run(notifier);
+ RavenwoodAwareTestRunnerHook.onClassSkipped(getDescription());
return;
}
@@ -294,19 +294,23 @@
}
private void runWithHooks(Description description, Scope scope, Order order, Statement s) {
- Throwable th = null;
if (isOnRavenwood()) {
Assume.assumeTrue(
RavenwoodAwareTestRunnerHook.onBefore(this, description, scope, order));
}
try {
s.evaluate();
- } catch (Throwable t) {
- th = t;
- SneakyThrow.sneakyThrow(t);
- } finally {
if (isOnRavenwood()) {
- RavenwoodAwareTestRunnerHook.onAfter(this, description, scope, order, th);
+ RavenwoodAwareTestRunnerHook.onAfter(this, description, scope, order, null);
+ }
+ } catch (Throwable t) {
+ boolean shouldThrow = true;
+ if (isOnRavenwood()) {
+ shouldThrow = RavenwoodAwareTestRunnerHook.onAfter(
+ this, description, scope, order, t);
+ }
+ if (shouldThrow) {
+ SneakyThrow.sneakyThrow(t);
}
}
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
index 6c8d96a..85297fe 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
@@ -16,37 +16,20 @@
package android.platform.test.ravenwood;
-import static android.platform.test.ravenwood.RavenwoodRule.ENABLE_PROBE_IGNORED;
-import static android.platform.test.ravenwood.RavenwoodRule.IS_ON_RAVENWOOD;
-import static android.platform.test.ravenwood.RavenwoodRule.shouldEnableOnRavenwood;
-import static android.platform.test.ravenwood.RavenwoodRule.shouldStillIgnoreInProbeIgnoreMode;
-
-import android.platform.test.annotations.DisabledOnRavenwood;
-import android.platform.test.annotations.EnabledOnRavenwood;
-
-import org.junit.Assume;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
- * {@code @ClassRule} that respects Ravenwood-specific class annotations. This rule has no effect
- * when tests are run on non-Ravenwood test environments.
+ * No longer needed.
*
- * By default, all tests are executed on Ravenwood, but annotations such as
- * {@link DisabledOnRavenwood} and {@link EnabledOnRavenwood} can be used at both the method
- * and class level to "ignore" tests that may not be ready.
+ * @deprecated this class used to be used to handle the class level annotation, which
+ * is now done by the test runner, so this class is not needed.
*/
+@Deprecated
public class RavenwoodClassRule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
- if (!IS_ON_RAVENWOOD) {
- // No check on a real device.
- } else if (ENABLE_PROBE_IGNORED) {
- Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
- } else {
- Assume.assumeTrue(shouldEnableOnRavenwood(description));
- }
return base;
}
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 75faafb..d569896 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -20,22 +20,20 @@
import static android.os.Process.SYSTEM_UID;
import static android.os.UserHandle.SYSTEM;
-import static org.junit.Assert.fail;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.log;
+import android.annotation.Nullable;
import android.app.Instrumentation;
import android.content.Context;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
import com.android.ravenwood.common.RavenwoodCommonUtils;
-import org.junit.Assume;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -56,18 +54,18 @@
* before a test class is fully initialized.
*/
public class RavenwoodRule implements TestRule {
+ private static final String TAG = "RavenwoodRule";
+
static final boolean IS_ON_RAVENWOOD = RavenwoodCommonUtils.isOnRavenwood();
/**
- * When probing is enabled, all tests will be unconditionally run on Ravenwood to detect
+ * When this flag is enabled, all tests will be unconditionally run on Ravenwood to detect
* cases where a test is able to pass despite being marked as {@link DisabledOnRavenwood}.
*
* This is typically helpful for internal maintainers discovering tests that had previously
* been ignored, but now have enough Ravenwood-supported functionality to be enabled.
- *
- * TODO: Rename it to a more descriptive name.
*/
- static final boolean ENABLE_PROBE_IGNORED = "1".equals(
+ private static final boolean RUN_DISABLED_TESTS = "1".equals(
System.getenv("RAVENWOOD_RUN_DISABLED_TESTS"));
/**
@@ -92,23 +90,17 @@
*
* Because we use a regex-find, setting "." would disable all tests.
*/
- private static final Pattern REALLY_DISABLE_PATTERN = Pattern.compile(
- Objects.requireNonNullElse(System.getenv("RAVENWOOD_REALLY_DISABLE"), ""));
+ private static final Pattern REALLY_DISABLED_PATTERN = Pattern.compile(
+ Objects.requireNonNullElse(System.getenv("RAVENWOOD_REALLY_DISABLED"), ""));
- private static final boolean ENABLE_REALLY_DISABLE_PATTERN =
- !REALLY_DISABLE_PATTERN.pattern().isEmpty();
-
- /**
- * If true, enable optional validation on running tests.
- */
- private static final boolean ENABLE_OPTIONAL_VALIDATION = "1".equals(
- System.getenv("RAVENWOOD_OPTIONAL_VALIDATION"));
+ private static final boolean HAS_REALLY_DISABLE_PATTERN =
+ !REALLY_DISABLED_PATTERN.pattern().isEmpty();
static {
- if (ENABLE_PROBE_IGNORED) {
- System.out.println("$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests");
- if (ENABLE_REALLY_DISABLE_PATTERN) {
- System.out.println("$RAVENWOOD_REALLY_DISABLE=" + REALLY_DISABLE_PATTERN.pattern());
+ if (RUN_DISABLED_TESTS) {
+ log(TAG, "$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests");
+ if (HAS_REALLY_DISABLE_PATTERN) {
+ log(TAG, "$RAVENWOOD_REALLY_DISABLED=" + REALLY_DISABLED_PATTERN.pattern());
}
}
}
@@ -275,103 +267,18 @@
"Instrumentation is only available during @Test execution");
}
- /**
- * Determine if the given {@link Description} should be enabled when running on the
- * Ravenwood test environment.
- *
- * A more specific method-level annotation always takes precedence over any class-level
- * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
- * an {@link DisabledOnRavenwood} annotation.
- */
- public static boolean shouldEnableOnRavenwood(Description description) {
- // First, consult any method-level annotations
- if (description.isTest()) {
- // Stopgap for http://g/ravenwood/EPAD-N5ntxM
- if (description.getMethodName().endsWith("$noRavenwood")) {
- return false;
- }
- if (description.getAnnotation(EnabledOnRavenwood.class) != null) {
- return true;
- }
- if (description.getAnnotation(DisabledOnRavenwood.class) != null) {
- return false;
- }
- if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
- return false;
- }
- }
-
- // Otherwise, consult any class-level annotations
- return shouldRunCassOnRavenwood(description.getTestClass());
- }
-
- public static boolean shouldRunCassOnRavenwood(Class<?> clazz) {
- if (clazz != null) {
- if (clazz.getAnnotation(EnabledOnRavenwood.class) != null) {
- return true;
- }
- if (clazz.getAnnotation(DisabledOnRavenwood.class) != null) {
- return false;
- }
- if (clazz.getAnnotation(IgnoreUnderRavenwood.class) != null) {
- return false;
- }
- }
- return true;
- }
-
- static boolean shouldStillIgnoreInProbeIgnoreMode(Description description) {
- if (!ENABLE_REALLY_DISABLE_PATTERN) {
- return false;
- }
-
- final var fullname = description.getTestClass().getName()
- + (description.isTest() ? "#" + description.getMethodName() : "");
-
- if (REALLY_DISABLE_PATTERN.matcher(fullname).find()) {
- System.out.println("Still ignoring " + fullname);
- return true;
- }
- return false;
- }
@Override
public Statement apply(Statement base, Description description) {
- // No special treatment when running outside Ravenwood; run tests as-is
- if (!IS_ON_RAVENWOOD) {
- return base;
- }
-
- if (ENABLE_PROBE_IGNORED) {
- return applyProbeIgnored(base, description);
- } else {
- return applyDefault(base, description);
- }
- }
-
- private void commonPrologue(Statement base, Description description) throws IOException {
- RavenwoodRuleImpl.logTestRunner("started", description);
- RavenwoodRuleImpl.validate(base, description, ENABLE_OPTIONAL_VALIDATION);
- RavenwoodRuleImpl.init(RavenwoodRule.this);
- }
-
- /**
- * Run the given {@link Statement} with no special treatment.
- */
- private Statement applyDefault(Statement base, Description description) {
+ // TODO: Here, we're calling init() / reset() once for each rule.
+ // That means if a test class has multiple rules -- even if they refer to the same
+ // rule instance -- we're calling them multiple times. We need to fix it.
return new Statement() {
@Override
public void evaluate() throws Throwable {
- Assume.assumeTrue(shouldEnableOnRavenwood(description));
-
- commonPrologue(base, description);
+ RavenwoodRuleImpl.init(RavenwoodRule.this);
try {
base.evaluate();
-
- RavenwoodRuleImpl.logTestRunner("finished", description);
- } catch (Throwable t) {
- RavenwoodRuleImpl.logTestRunner("failed", description);
- throw t;
} finally {
RavenwoodRuleImpl.reset(RavenwoodRule.this);
}
@@ -380,44 +287,6 @@
}
/**
- * Run the given {@link Statement} with probing enabled. All tests will be unconditionally
- * run on Ravenwood to detect cases where a test is able to pass despite being marked as
- * {@code IgnoreUnderRavenwood}.
- */
- private Statement applyProbeIgnored(Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
-
- commonPrologue(base, description);
- try {
- base.evaluate();
- } catch (Throwable t) {
- // If the test isn't included, eat the exception and report the
- // assumption failure that test authors expect; otherwise throw
- Assume.assumeTrue(shouldEnableOnRavenwood(description));
- throw t;
- } finally {
- RavenwoodRuleImpl.logTestRunner("finished", description);
- RavenwoodRuleImpl.reset(RavenwoodRule.this);
- }
-
- if (!shouldEnableOnRavenwood(description)) {
- fail("Test wasn't included under Ravenwood, but it actually "
- + "passed under Ravenwood; consider updating annotations");
- }
- }
- };
- }
-
- public static class _$RavenwoodPrivate {
- public static boolean isOptionalValidationEnabled() {
- return ENABLE_OPTIONAL_VALIDATION;
- }
- }
-
- /**
* Returns the "real" result from {@link System#currentTimeMillis()}.
*
* Currently, it's the same thing as calling {@link System#currentTimeMillis()},
@@ -427,4 +296,47 @@
public long realCurrentTimeMillis() {
return System.currentTimeMillis();
}
+
+ // Below are internal to ravenwood. Don't use them from normal tests...
+
+ public static class RavenwoodPrivate {
+ private RavenwoodPrivate() {
+ }
+
+ private volatile Boolean mRunDisabledTestsOverride = null;
+
+ private volatile Pattern mReallyDisabledPattern = null;
+
+ public boolean isRunningDisabledTests() {
+ if (mRunDisabledTestsOverride != null) {
+ return mRunDisabledTestsOverride;
+ }
+ return RUN_DISABLED_TESTS;
+ }
+
+ public Pattern getReallyDisabledPattern() {
+ if (mReallyDisabledPattern != null) {
+ return mReallyDisabledPattern;
+ }
+ return REALLY_DISABLED_PATTERN;
+ }
+
+ public void overrideRunDisabledTest(boolean runDisabledTests,
+ @Nullable String reallyDisabledPattern) {
+ mRunDisabledTestsOverride = runDisabledTests;
+ mReallyDisabledPattern =
+ reallyDisabledPattern == null ? null : Pattern.compile(reallyDisabledPattern);
+ }
+
+ public void resetRunDisabledTest() {
+ mRunDisabledTestsOverride = null;
+ mReallyDisabledPattern = null;
+ }
+ }
+
+ private static final RavenwoodPrivate sRavenwoodPrivate = new RavenwoodPrivate();
+
+ public static RavenwoodPrivate private$ravenwood() {
+ return sRavenwoodPrivate;
+ }
}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
index 6b80e0c..1e4889c 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -23,27 +23,51 @@
import org.junit.runners.model.TestClass;
/**
- * Provide hook points created by {@link RavenwoodAwareTestRunner}.
+ * Provide hook points created by {@link RavenwoodAwareTestRunner}. This is a version
+ * that's used on a device side test.
+ *
+ * All methods are no-op in real device tests.
+ *
+ * TODO: Use some kind of factory to provide different implementation for the device test
+ * and the ravenwood test.
*/
public class RavenwoodAwareTestRunnerHook {
private RavenwoodAwareTestRunnerHook() {
}
/**
- * Called when a runner starts, befre the inner runner gets a chance to run.
+ * Called when a runner starts, before the inner runner gets a chance to run.
*/
public static void onRunnerInitializing(Runner runner, TestClass testClass) {
- // No-op on a real device.
}
+ /**
+ * Called when a whole test class is skipped.
+ */
+ public static void onClassSkipped(Description description) {
+ }
+
+ /**
+ * Called before a test / class.
+ *
+ * Return false if it should be skipped.
+ */
public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
Scope scope, Order order) {
- // No-op on a real device.
return true;
}
- public static void onAfter(RavenwoodAwareTestRunner runner, Description description,
+ /**
+ * Called after a test / class.
+ *
+ * Return false if the exception should be ignored.
+ */
+ public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description,
Scope scope, Order order, Throwable th) {
- // No-op on a real device.
+ return true;
+ }
+
+ public static boolean shouldRunClassOnRavenwood(Class<?> clazz) {
+ return true;
}
}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 483b98a..a470626 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -17,7 +17,6 @@
package android.platform.test.ravenwood;
import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
public class RavenwoodRuleImpl {
public static void init(RavenwoodRule rule) {
@@ -32,10 +31,6 @@
// No-op when running on a real device
}
- public static void validate(Statement base, Description description,
- boolean enableOptionalValidation) {
- }
-
public static long realCurrentTimeMillis() {
return System.currentTimeMillis();
}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 8e2e0ad..08cc9c3 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -121,6 +121,13 @@
}
flag {
+ name: "enable_magnification_keyboard_control"
+ namespace: "accessibility"
+ description: "Whether to enable keyboard control for magnification"
+ bug: "355487062"
+}
+
+flag {
name: "fix_drag_pointer_when_ending_drag"
namespace: "accessibility"
description: "Send the correct pointer id when transitioning from dragging to delegating states."
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index 2bfdd0a..77650eb 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -28,7 +28,6 @@
],
static_libs: [
"ukey2_jni",
- "virtualdevice_flags_lib",
"virtual_camera_service_aidl-java",
],
lint: {
diff --git a/services/companion/java/com/android/server/companion/virtual/Android.bp b/services/companion/java/com/android/server/companion/virtual/Android.bp
deleted file mode 100644
index 66313e6..0000000
--- a/services/companion/java/com/android/server/companion/virtual/Android.bp
+++ /dev/null
@@ -1,17 +0,0 @@
-package {
- default_team: "trendy_team_xr_framework",
-}
-
-java_aconfig_library {
- name: "virtualdevice_flags_lib",
- aconfig_declarations: "virtualdevice_flags",
-}
-
-aconfig_declarations {
- name: "virtualdevice_flags",
- package: "com.android.server.companion.virtual",
- container: "system",
- srcs: [
- "flags.aconfig",
- ],
-}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java
index b0bacfd..fed153f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java
@@ -48,9 +48,6 @@
void logCreated(int deviceId, int ownerUid) {
final long token = Binder.clearCallingIdentity();
try {
- if (!Flags.dumpHistory()) {
- return;
- }
addEntry(new LogEntry(TYPE_CREATED, deviceId, System.currentTimeMillis(), ownerUid));
} finally {
Binder.restoreCallingIdentity(token);
@@ -60,9 +57,6 @@
void logClosed(int deviceId, int ownerUid) {
final long token = Binder.clearCallingIdentity();
try {
- if (!Flags.dumpHistory()) {
- return;
- }
addEntry(new LogEntry(TYPE_CLOSED, deviceId, System.currentTimeMillis(), ownerUid));
} finally {
Binder.restoreCallingIdentity(token);
@@ -79,9 +73,6 @@
void dump(PrintWriter pw) {
final long token = Binder.clearCallingIdentity();
try {
- if (!Flags.dumpHistory()) {
- return;
- }
pw.println("VirtualDevice Log:");
UidToPackageNameCache packageNameCache = new UidToPackageNameCache(
mContext.getPackageManager());
diff --git a/services/companion/java/com/android/server/companion/virtual/flags.aconfig b/services/companion/java/com/android/server/companion/virtual/flags.aconfig
deleted file mode 100644
index 616f5d0..0000000
--- a/services/companion/java/com/android/server/companion/virtual/flags.aconfig
+++ /dev/null
@@ -1,11 +0,0 @@
-# OLD PACKAGE, DO NOT USE: Prefer `flags.aconfig` in core/java/android/companion/virtual
-# (or other custom files) to define your flags
-package: "com.android.server.companion.virtual"
-container: "system"
-
-flag {
- name: "dump_history"
- namespace: "virtual_devices"
- description: "This flag controls if a history of virtual devices is shown in dumpsys virtualdevices"
- bug: "293114719"
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 361b818..fd512a6 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -94,6 +94,8 @@
275534 notification_unautogrouped (key|3)
# when a notification is adjusted via assistant
27535 notification_adjusted (key|3),(adjustment_type|3),(new_value|3)
+# when a notification cancellation is prevented by the system
+27536 notification_cancel_prevented (key|3)
# ---------------------------
# Watchdog.java
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d80b38e..d121535 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16248,43 +16248,55 @@
boolean closeFd = true;
try {
- synchronized (mProcLock) {
- if (fd == null) {
- throw new IllegalArgumentException("null fd");
- }
- mBinderTransactionTrackingEnabled = false;
+ Objects.requireNonNull(fd);
- PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd.getFileDescriptor()));
- pw.println("Binder transaction traces for all processes.\n");
- mProcessList.forEachLruProcessesLOSP(true, process -> {
+ record ProcessToDump(String processName, IApplicationThread thread) { }
+
+ PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd.getFileDescriptor()));
+ pw.println("Binder transaction traces for all processes.\n");
+ final ArrayList<ProcessToDump> processes = new ArrayList<>();
+ synchronized (mProcLock) {
+ // Since dumping binder transactions is a long-running operation, we can't do it
+ // with mProcLock held. Do the initial verification here, and save the processes
+ // to dump later outside the lock.
+ final ArrayList<ProcessRecord> unverifiedProcesses =
+ new ArrayList<>(mProcessList.getLruProcessesLOSP());
+ for (int i = 0, size = unverifiedProcesses.size(); i < size; i++) {
+ ProcessRecord process = unverifiedProcesses.get(i);
final IApplicationThread thread = process.getThread();
if (!processSanityChecksLPr(process, thread)) {
- return;
+ continue;
}
-
- pw.println("Traces for process: " + process.processName);
- pw.flush();
- try {
- TransferPipe tp = new TransferPipe();
- try {
- thread.stopBinderTrackingAndDump(tp.getWriteFd());
- tp.go(fd.getFileDescriptor());
- } finally {
- tp.kill();
- }
- } catch (IOException e) {
- pw.println("Failure while dumping IPC traces from " + process +
- ". Exception: " + e);
- pw.flush();
- } catch (RemoteException e) {
- pw.println("Got a RemoteException while dumping IPC traces from " +
- process + ". Exception: " + e);
- pw.flush();
- }
- });
- closeFd = false;
- return true;
+ processes.add(new ProcessToDump(process.processName, process.getThread()));
+ }
+ mBinderTransactionTrackingEnabled = false;
}
+ for (int i = 0, size = processes.size(); i < size; i++) {
+ final String processName = processes.get(i).processName();
+ final IApplicationThread thread = processes.get(i).thread();
+
+ pw.println("Traces for process: " + processName);
+ pw.flush();
+ try {
+ TransferPipe tp = new TransferPipe();
+ try {
+ thread.stopBinderTrackingAndDump(tp.getWriteFd());
+ tp.go(fd.getFileDescriptor());
+ } finally {
+ tp.kill();
+ }
+ } catch (IOException e) {
+ pw.println("Failure while dumping IPC traces from " + processName +
+ ". Exception: " + e);
+ pw.flush();
+ } catch (RemoteException e) {
+ pw.println("Got a RemoteException while dumping IPC traces from " +
+ processName + ". Exception: " + e);
+ pw.flush();
+ }
+ }
+ closeFd = false;
+ return true;
} finally {
if (fd != null && closeFd) {
try {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ab63e24..0e266f5 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1201,6 +1201,7 @@
>= UNKNOWN_ADJ) {
final ProcessServiceRecord psr = app.mServices;
switch (state.getCurProcState()) {
+ case PROCESS_STATE_LAST_ACTIVITY:
case PROCESS_STATE_CACHED_ACTIVITY:
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
case ActivityManager.PROCESS_STATE_CACHED_RECENT:
@@ -2180,7 +2181,6 @@
procState = PROCESS_STATE_LAST_ACTIVITY;
schedGroup = SCHED_GROUP_BACKGROUND;
state.setAdjType("previous-expired");
- adj = CACHED_APP_MIN_ADJ;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Expire prev adj: " + app);
}
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index 2a9dfa2..9317c1e 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -18,24 +18,7 @@
"name": "FrameworksMockingServicesTests_android_server_appop"
},
{
- "name": "CtsPermissionTestCases",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "include-filter": "android.permission.cts.BackgroundPermissionsTest"
- },
- {
- "include-filter": "android.permission.cts.SplitPermissionTest"
- },
- {
- "include-filter": "android.permission.cts.PermissionFlagsTest"
- },
- {
- "include-filter": "android.permission.cts.SharedUidPermissionsTest"
- }
- ]
+ "name": "CtsPermissionTestCases_Platform"
},
{
"name": "CtsAppTestCases",
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 276ab03..abfbddc 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -221,7 +221,8 @@
mFingerprintSensorProperties = fingerprintSensorProperties;
mCancelled = false;
mBiometricFrameworkStatsLogger = logger;
- mOperationContext = new OperationContextExt(true /* isBP */);
+ mOperationContext = new OperationContextExt(true /* isBP */,
+ preAuthInfo.getIsMandatoryBiometricsAuthentication() /* isMandatoryBiometrics */);
mBiometricManager = mContext.getSystemService(BiometricManager.class);
mSfpsSensorIds = mFingerprintSensorProperties.stream().filter(
@@ -285,7 +286,8 @@
sensor.goToStateWaitingForCookie(requireConfirmation, mToken, mOperationId,
mUserId, mSensorReceiver, mOpPackageName, mRequestId, cookie,
mPromptInfo.isAllowBackgroundAuthentication(),
- mPromptInfo.isForLegacyFingerprintManager());
+ mPromptInfo.isForLegacyFingerprintManager(),
+ mOperationContext.getIsMandatoryBiometrics());
}
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java
index 42dd36a..c7532d4 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensor.java
+++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java
@@ -107,12 +107,13 @@
void goToStateWaitingForCookie(boolean requireConfirmation, IBinder token, long sessionId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
long requestId, int cookie, boolean allowBackgroundAuthentication,
- boolean isForLegacyFingerprintManager)
+ boolean isForLegacyFingerprintManager, boolean isMandatoryBiometrics)
throws RemoteException {
mCookie = cookie;
impl.prepareForAuthentication(requireConfirmation, token,
sessionId, userId, sensorReceiver, opPackageName, requestId, mCookie,
- allowBackgroundAuthentication, isForLegacyFingerprintManager);
+ allowBackgroundAuthentication, isForLegacyFingerprintManager,
+ isMandatoryBiometrics);
mSensorState = STATE_WAITING_FOR_COOKIE;
}
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index f0da67b..eaf4f81 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -447,6 +447,12 @@
getInternalStatus().second));
}
+ /** Returns if mandatory biometrics authentication is in effect */
+ boolean getIsMandatoryBiometricsAuthentication() {
+ return mIsMandatoryBiometricsAuthentication;
+ }
+
+
/**
* For the given request, generate the appropriate reason why authentication cannot be started.
* Note that for some errors, modality is intentionally cleared.
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index 2c52e3d..f5af5ea 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -78,6 +78,7 @@
public void authenticate(OperationContextExt operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
int authState, boolean requireConfirmation, int targetUserId, float ambientLightLux) {
+ Slog.d(TAG, "authenticate logging " + operationContext.getIsMandatoryBiometrics());
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
statsModality,
targetUserId,
@@ -98,7 +99,8 @@
foldType(operationContext.getFoldState()),
operationContext.getOrderAndIncrement(),
toProtoWakeReason(operationContext),
- toProtoWakeReasonDetails(operationContext));
+ toProtoWakeReasonDetails(operationContext),
+ operationContext.getIsMandatoryBiometrics());
}
/** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
@@ -129,6 +131,7 @@
public void error(OperationContextExt operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
int error, int vendorCode, int targetUserId) {
+ Slog.d(TAG, "error logging " + operationContext.getIsMandatoryBiometrics());
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
statsModality,
targetUserId,
@@ -149,7 +152,8 @@
foldType(operationContext.getFoldState()),
operationContext.getOrderAndIncrement(),
toProtoWakeReason(operationContext),
- toProtoWakeReasonDetails(operationContext));
+ toProtoWakeReasonDetails(operationContext),
+ operationContext.getIsMandatoryBiometrics());
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index da4e515..4df63e2 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -50,21 +50,33 @@
@Surface.Rotation private int mOrientation = Surface.ROTATION_0;
private int mFoldState = IBiometricContextListener.FoldState.UNKNOWN;
private final boolean mIsBP;
+ private final boolean mIsMandatoryBiometrics;
/** Create a context. */
public OperationContextExt(boolean isBP) {
this(new OperationContext(), isBP, BiometricAuthenticator.TYPE_NONE);
}
- public OperationContextExt(boolean isBP, @BiometricAuthenticator.Modality int modality) {
- this(new OperationContext(), isBP, modality);
+ public OperationContextExt(boolean isBP, boolean isMandatoryBiometrics) {
+ this(new OperationContext(), isBP, BiometricAuthenticator.TYPE_NONE, isMandatoryBiometrics);
+ }
+
+ public OperationContextExt(boolean isBP, @BiometricAuthenticator.Modality int modality,
+ boolean isMandatoryBiometrics) {
+ this(new OperationContext(), isBP, modality, isMandatoryBiometrics);
}
/** Create a wrapped context. */
public OperationContextExt(@NonNull OperationContext context, boolean isBP,
@BiometricAuthenticator.Modality int modality) {
+ this(context, isBP, modality, false /* isMandatoryBiometrics */);
+ }
+
+ public OperationContextExt(@NonNull OperationContext context, boolean isBP,
+ @BiometricAuthenticator.Modality int modality, boolean isMandatoryBiometrics) {
mAidlContext = context;
mIsBP = isBP;
+ mIsMandatoryBiometrics = isMandatoryBiometrics;
if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
mAidlContext.operationState = OperationState.fingerprintOperationState(
@@ -285,6 +297,11 @@
return mAidlContext.operationState;
}
+ /** If mandatory biometrics is active. */
+ public boolean getIsMandatoryBiometrics() {
+ return mIsMandatoryBiometrics;
+ }
+
/** Update this object with the latest values from the given context. */
OperationContextExt update(@NonNull BiometricContext biometricContext, boolean isCrypto) {
mAidlContext.isAod = biometricContext.isAod();
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index fbd32a6..04522e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -59,9 +59,10 @@
public AcquisitionClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int cookie, int sensorId, boolean shouldVibrate,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ boolean isMandatoryBiometrics) {
super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId,
- logger, biometricContext);
+ logger, biometricContext, isMandatoryBiometrics);
mPowerManager = context.getSystemService(PowerManager.class);
mShouldVibrate = shouldVibrate;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index daaafcb..09386ae28 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -99,7 +99,7 @@
boolean shouldVibrate, int sensorStrength) {
super(context, lazyDaemon, token, listener, options.getUserId(),
options.getOpPackageName(), cookie, options.getSensorId(), shouldVibrate,
- biometricLogger, biometricContext);
+ biometricLogger, biometricContext, options.isMandatoryBiometrics());
mIsStrongBiometric = isStrongBiometric;
mOperationId = operationId;
mRequireConfirmation = requireConfirmation;
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 438367d..32c0bd3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -60,7 +60,7 @@
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
int enrollReason) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- shouldVibrate, logger, biometricContext);
+ shouldVibrate, logger, biometricContext, false /* isMandatoryBiometrics */);
mBiometricUtils = utils;
mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
mTimeoutSec = timeoutSec;
diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
index 2adf0cb..b573b56 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -37,7 +37,7 @@
int userId, @NonNull String owner, int sensorId,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- biometricLogger, biometricContext);
+ biometricLogger, biometricContext, false /* isMandatoryBiometrics */);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
index 0f01510..3bc51a9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -41,26 +41,29 @@
private final OperationContextExt mOperationContext;
/**
- * @param context system_server context
- * @param lazyDaemon pointer for lazy retrieval of the HAL
- * @param token a unique token for the client
- * @param listener recipient of related events (e.g. authentication)
- * @param userId target user id for operation
- * @param owner name of the client that owns this
- * @param cookie BiometricPrompt authentication cookie (to be moved into a subclass soon)
- * @param sensorId ID of the sensor that the operation should be requested of
- * @param biometricLogger framework stats logger
+ * @param context system_server context
+ * @param lazyDaemon pointer for lazy retrieval of the HAL
+ * @param token a unique token for the client
+ * @param listener recipient of related events (e.g. authentication)
+ * @param userId target user id for operation
+ * @param owner name of the client that owns this
+ * @param cookie BiometricPrompt authentication cookie (to be moved into a subclass
+ * soon)
+ * @param sensorId ID of the sensor that the operation should be requested of
+ * @param biometricLogger framework stats logger
* @param biometricContext system context metadata
*/
public HalClientMonitor(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int cookie, int sensorId,
- @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ boolean isMandatoryBiometrics) {
super(context, token, listener, userId, owner, cookie, sensorId,
biometricLogger, biometricContext);
mLazyDaemon = lazyDaemon;
int modality = listener != null ? listener.getModality() : BiometricAuthenticator.TYPE_NONE;
- mOperationContext = new OperationContextExt(isBiometricPrompt(), modality);
+ mOperationContext = new OperationContextExt(isBiometricPrompt(), modality,
+ isMandatoryBiometrics);
}
@Nullable
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 7bd905b..6c30559 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -143,7 +143,8 @@
@NonNull BiometricUtils<S> utils,
@NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, null /* token */, null /* ClientMonitorCallbackConverter */,
- userId, owner, 0 /* cookie */, sensorId, logger, biometricContext);
+ userId, owner, 0 /* cookie */, sensorId, logger, biometricContext,
+ false /* isMandatoryBiometrics */);
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 81ab26d..2c2c685 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -55,7 +55,8 @@
// Internal enumerate does not need to send results to anyone. Cleanup (enumerate + remove)
// is all done internally.
super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
- 0 /* cookie */, sensorId, logger, biometricContext);
+ 0 /* cookie */, sensorId, logger, biometricContext,
+ false /* isMandatoryBiometrics */);
mEnrolledList = enrolledList;
mInitialEnrolledSize = mEnrolledList.size();
mUtils = utils;
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
index d5aa5e2..6c93366 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
@@ -49,7 +49,7 @@
@NonNull IInvalidationCallback callback) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId,
context.getOpPackageName(), 0 /* cookie */, sensorId,
- logger, biometricContext);
+ logger, biometricContext, false /* isMandatoryBiometrics */);
mAuthenticatorIds = authenticatorIds;
mInvalidationCallback = callback;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index d2ef278..ad5877a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -49,7 +49,7 @@
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- logger, biometricContext);
+ logger, biometricContext, false /* isMandatoryBiometrics */);
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
index 88f4da2..0c8a2dd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
@@ -32,7 +32,8 @@
@NonNull IBinder token, int userId, @NonNull String owner, int sensorId,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, biometricLogger, biometricContext);
+ 0 /* cookie */, sensorId, biometricLogger, biometricContext,
+ false /* isMandatoryBiometrics */);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
index 21c9f64..ff694cd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -51,7 +51,8 @@
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull UserStartedCallback<U> callback) {
super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
- 0 /* cookie */, sensorId, logger, biometricContext);
+ 0 /* cookie */, sensorId, logger, biometricContext,
+ false /* isMandatoryBiometrics */);
mUserStartedCallback = callback;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
index e01c4ec..9119bc7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -54,7 +54,8 @@
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull UserStoppedCallback callback) {
super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
- 0 /* cookie */, sensorId, logger, biometricContext);
+ 0 /* cookie */, sensorId, logger, biometricContext,
+ false /* isMandatoryBiometrics */);
mUserStoppedCallback = callback;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 2211003..67d7505 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -63,13 +63,14 @@
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication,
- boolean isForLegacyFingerprintManager)
+ boolean isForLegacyFingerprintManager, boolean isMandatoryBiometrics)
throws RemoteException {
mFaceService.prepareForAuthentication(requireConfirmation, token, operationId,
sensorReceiver, new FaceAuthenticateOptions.Builder()
.setUserId(userId)
.setSensorId(mSensorId)
.setOpPackageName(opPackageName)
+ .setIsMandatoryBiometrics(isMandatoryBiometrics)
.build(),
requestId, cookie, allowBackgroundAuthentication);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 8b4da31..8eb62eb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -83,7 +83,8 @@
boolean isStrongBiometric, SensorPrivacyManager sensorPrivacyManager) {
super(context, lazyDaemon, token, listener, options.getUserId(),
options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
- false /* shouldVibrate */, logger, biometricContext);
+ false /* shouldVibrate */, logger, biometricContext,
+ false /* isMandatoryBiometrics */);
setRequestId(requestId);
mAuthenticationStateListeners = authenticationStateListeners;
mIsStrongBiometric = isStrongBiometric;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
index 1f4f612..dbd293c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
@@ -42,7 +42,8 @@
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, opPackageName,
- 0 /* cookie */, sensorId, logger, biometricContext);
+ 0 /* cookie */, sensorId, logger, biometricContext,
+ false /* isMandatoryBiometrics */);
mAuthenticatorIds = authenticatorIds;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index c41b706..8d1336f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -55,7 +55,7 @@
@NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
@NonNull BiometricContext biometricContext, int feature) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- logger, biometricContext);
+ logger, biometricContext, false /* isMandatoryBiometrics */);
mUserId = userId;
mFeature = feature;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index d02eefa..93bc1f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -60,7 +60,8 @@
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@Authenticators.Types int biometricStrength) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, logger, biometricContext);
+ 0 /* cookie */, sensorId, logger, biometricContext,
+ false /* isMandatoryBiometrics */);
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
index f6da872..fc73e58 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
@@ -52,7 +52,7 @@
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
int feature, boolean enabled, byte[] hardwareAuthToken) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- logger, biometricContext);
+ logger, biometricContext, false /* isMandatoryBiometrics */);
mFeature = feature;
mEnabled = enabled;
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index b6fa0c1..d50ab8d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -63,13 +63,14 @@
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication,
- boolean isForLegacyFingerprintManager)
+ boolean isForLegacyFingerprintManager, boolean isMandatoryBiometrics)
throws RemoteException {
mFingerprintService.prepareForAuthentication(token, operationId, sensorReceiver,
new FingerprintAuthenticateOptions.Builder()
.setSensorId(mSensorId)
.setUserId(userId)
.setOpPackageName(opPackageName)
+ .setIsMandatoryBiometrics(isMandatoryBiometrics)
.build(),
requestId, cookie, allowBackgroundAuthentication, isForLegacyFingerprintManager);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index fb48053..a81ab8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -71,7 +71,8 @@
boolean isStrongBiometric) {
super(context, lazyDaemon, token, listener, options.getUserId(),
options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
- true /* shouldVibrate */, biometricLogger, biometricContext);
+ true /* shouldVibrate */, biometricLogger, biometricContext,
+ false /* isMandatoryBiometrics */);
setRequestId(requestId);
mAuthenticationStateListeners = authenticationStateListeners;
mIsStrongBiometric = isStrongBiometric;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index 0353969..f77e116 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -42,7 +42,8 @@
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, biometricLogger, biometricContext);
+ 0 /* cookie */, sensorId, biometricLogger, biometricContext,
+ false /* isMandatoryBiometrics */);
mAuthenticatorIds = authenticatorIds;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index 387ae12..81a3d83 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -59,7 +59,8 @@
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@Authenticators.Types int biometricStrength) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, biometricLogger, biometricContext);
+ 0 /* cookie */, sensorId, biometricLogger, biometricContext,
+ false /* isMandatoryBiometrics */);
mHardwareAuthToken = hardwareAuthToken == null ? null :
HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
mLockoutCache = lockoutTracker;
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 01d1e378..76b5c4e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -60,7 +60,7 @@
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long sessionId, int userId, IBiometricSensorReceiver sensorReceiver,
String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication,
- boolean isForLegacyFingerprintManager)
+ boolean isForLegacyFingerprintManager, boolean isMandatoryBiometrics)
throws RemoteException {
}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index d0b8990..f44b852 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -142,7 +142,6 @@
ERROR_KEYSTORE_FAILURE,
ERROR_NO_NETWORK,
ERROR_TIMEOUT_EXHAUSTED,
- ERROR_NO_REBOOT_ESCROW_DATA,
})
@Retention(RetentionPolicy.SOURCE)
@interface RebootEscrowErrorCode {
@@ -158,7 +157,6 @@
static final int ERROR_KEYSTORE_FAILURE = 7;
static final int ERROR_NO_NETWORK = 8;
static final int ERROR_TIMEOUT_EXHAUSTED = 9;
- static final int ERROR_NO_REBOOT_ESCROW_DATA = 10;
private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE;
@@ -507,9 +505,6 @@
if (rebootEscrowUsers.isEmpty()) {
Slog.i(TAG, "No reboot escrow data found for users,"
+ " skipping loading escrow data");
- setLoadEscrowDataErrorCode(ERROR_NO_REBOOT_ESCROW_DATA, retryHandler);
- reportMetricOnRestoreComplete(
- /* success= */ false, /* attemptCount= */ 1, retryHandler);
clearMetricsStorage();
return;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 54bc6fb..dbe778e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -11777,6 +11777,8 @@
mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(),
record, isAppForeground, /* isAppProvided= */ false, tracker));
+
+ EventLogTags.writeNotificationCancelPrevented(record.getKey());
}
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 95e5b84..2bc6d53 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -503,26 +503,21 @@
String restriction,
boolean isMainUser,
boolean isProfileOwnerOnOrgOwnedDevice) {
- if (android.app.admin.flags.Flags.esimManagementEnabled()) {
- if (IMMUTABLE_BY_OWNERS.contains(restriction)) {
- return false;
- }
- if (DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction)) {
- return false;
- }
- if (!isMainUser && MAIN_USER_ONLY_RESTRICTIONS.contains(restriction)) {
- return false;
- }
- if (!isProfileOwnerOnOrgOwnedDevice
- && PROFILE_OWNER_ORGANIZATION_OWNED_PROFILE_RESTRICTIONS.contains(
- restriction)) {
- return false;
- }
- return true;
+ if (IMMUTABLE_BY_OWNERS.contains(restriction)) {
+ return false;
}
- return !IMMUTABLE_BY_OWNERS.contains(restriction)
- && !DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction)
- && !(!isMainUser && MAIN_USER_ONLY_RESTRICTIONS.contains(restriction));
+ if (DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction)) {
+ return false;
+ }
+ if (!isMainUser && MAIN_USER_ONLY_RESTRICTIONS.contains(restriction)) {
+ return false;
+ }
+ if (!isProfileOwnerOnOrgOwnedDevice
+ && PROFILE_OWNER_ORGANIZATION_OWNED_PROFILE_RESTRICTIONS.contains(
+ restriction)) {
+ return false;
+ }
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index 24323c8..8a3c74b 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -1,24 +1,7 @@
{
"presubmit": [
{
- "name": "CtsPermissionTestCases",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "include-filter": "android.permission.cts.BackgroundPermissionsTest"
- },
- {
- "include-filter": "android.permission.cts.SplitPermissionTest"
- },
- {
- "include-filter": "android.permission.cts.PermissionFlagsTest"
- },
- {
- "include-filter": "android.permission.cts.SharedUidPermissionsTest"
- }
- ]
+ "name": "CtsPermissionTestCases_Platform"
},
{
"name": "CtsAppSecurityHostTestCases",
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index 338b479..bdb174d 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -46,18 +46,7 @@
]
},
{
- "name": "CtsPermissionTestCases",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "include-filter": "android.permission.cts.SplitPermissionTest"
- },
- {
- "include-filter": "android.permission.cts.BackgroundPermissionsTest"
- }
- ]
+ "name": "CtsPermissionTestCases_Platform"
},
{
"name": "CtsBackupTestCases",
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index d0b70c3..da8b01a 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -176,8 +176,9 @@
final DreamManagerInternal dreamManager =
LocalServices.getService(DreamManagerInternal.class);
-
- dreamManager.registerDreamManagerStateListener(mDreamManagerStateListener);
+ if(dreamManager != null){
+ dreamManager.registerDreamManagerStateListener(mDreamManagerStateListener);
+ }
}
private final ServiceConnection mKeyguardConnection = new ServiceConnection() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a27360d..12e7fd0 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1379,8 +1379,10 @@
new DisplayGroupPowerChangeListener();
mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener);
- // This DreamManager method does not acquire a lock, so it should be safe to call.
- mDreamManager.registerDreamManagerStateListener(new DreamManagerStateListener());
+ if(mDreamManager != null){
+ // This DreamManager method does not acquire a lock, so it should be safe to call.
+ mDreamManager.registerDreamManagerStateListener(new DreamManagerStateListener());
+ }
mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager,
mInjector.createSuspendBlocker(
@@ -3543,7 +3545,7 @@
}
// Stop dream.
- if (isDreaming) {
+ if (isDreaming && mDreamManager != null) {
mDreamManager.stopDream(/* immediate= */ false, "power manager request" /*reason*/);
}
}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index 9a4c60d..68760aa 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -864,7 +864,8 @@
buildNotification(DYNAMIC_MODE_NOTIF_CHANNEL_ID,
R.string.dynamic_mode_notification_title,
R.string.dynamic_mode_notification_summary,
- Settings.ACTION_BATTERY_SAVER_SETTINGS, 0L),
+ Settings.ACTION_BATTERY_SAVER_SETTINGS, 0L,
+ R.drawable.ic_settings),
UserHandle.ALL);
});
}
@@ -889,7 +890,8 @@
R.string.dynamic_mode_notification_summary_v2,
Settings.ACTION_BATTERY_SAVER_SETTINGS,
0L /* timeoutMs */,
- highlightBundle),
+ highlightBundle,
+ R.drawable.ic_qs_battery_saver),
UserHandle.ALL);
});
}
@@ -911,7 +913,8 @@
R.string.battery_saver_off_notification_title,
R.string.battery_saver_charged_notification_summary,
Settings.ACTION_BATTERY_SAVER_SETTINGS,
- STICKY_DISABLED_NOTIFY_TIMEOUT_MS),
+ STICKY_DISABLED_NOTIFY_TIMEOUT_MS,
+ R.drawable.ic_settings),
UserHandle.ALL);
});
}
@@ -926,7 +929,7 @@
}
private Notification buildNotification(@NonNull String channelId, @StringRes int titleId,
- @StringRes int summaryId, @NonNull String intentAction, long timeoutMs) {
+ @StringRes int summaryId, @NonNull String intentAction, long timeoutMs, int iconResId) {
Resources res = mContext.getResources();
Intent intent = new Intent(intentAction);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -937,7 +940,7 @@
final String summary = res.getString(summaryId);
return new Notification.Builder(mContext, channelId)
- .setSmallIcon(R.drawable.ic_battery)
+ .setSmallIcon(iconResId)
.setContentTitle(title)
.setContentText(summary)
.setContentIntent(batterySaverIntent)
@@ -950,7 +953,7 @@
private Notification buildNotificationV2(@NonNull String channelId, @StringRes int titleId,
@StringRes int summaryId, @NonNull String intentAction, long timeoutMs,
- @NonNull Bundle highlightBundle) {
+ @NonNull Bundle highlightBundle, int iconResId) {
Resources res = mContext.getResources();
Intent intent = new Intent(intentAction)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
@@ -963,7 +966,7 @@
final String summary = res.getString(summaryId);
return new Notification.Builder(mContext, channelId)
- .setSmallIcon(R.drawable.ic_battery)
+ .setSmallIcon(iconResId)
.setContentTitle(title)
.setContentText(summary)
.setContentIntent(batterySaverIntent)
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index b45651d..385561d 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -15300,15 +15300,6 @@
mHistory.writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
}
- if (!onBattery &&
- (status == BatteryManager.BATTERY_STATUS_FULL ||
- status == BatteryManager.BATTERY_STATUS_UNKNOWN)) {
- // We don't record history while we are plugged in and fully charged
- // (or when battery is not present). The next time we are
- // unplugged, history will be cleared.
- mHistory.setHistoryRecordingEnabled(DEBUG);
- }
-
mLastLearnedBatteryCapacityUah = chargeFullUah;
if (mMinLearnedBatteryCapacityUah == -1) {
mMinLearnedBatteryCapacityUah = chargeFullUah;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
index d6bf02f..6466519 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
@@ -190,6 +190,9 @@
case "notification-icons":
info.setNotificationIconsDisabled(true);
break;
+ case "quick-settings":
+ info.setQuickSettingsDisabled(true);
+ break;
default:
break;
}
@@ -277,6 +280,7 @@
pw.println(" system-icons - disable system icons appearing in status bar");
pw.println(" clock - disable clock appearing in status bar");
pw.println(" notification-icons - disable notification icons from status bar");
+ pw.println(" quick-settings - disable Quick Settings");
pw.println("");
pw.println(" tracing (start | stop)");
pw.println(" Start or stop SystemUI tracing");
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index ba2594a..f53dda6 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -42,6 +42,7 @@
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
+import static com.android.window.flags.Flags.avoidRebindingIntentionallyDisconnectedWallpaper;
import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.offloadColorExtraction;
@@ -897,6 +898,12 @@
return;
}
+ if (avoidRebindingIntentionallyDisconnectedWallpaper()
+ && mWallpaper.connection == null) {
+ Slog.w(TAG, "Trying to reset an intentionally disconnected wallpaper!");
+ return;
+ }
+
if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) {
Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent
+ ", reverting to built-in wallpaper!");
@@ -1066,6 +1073,13 @@
if (mWallpaper.wallpaperUpdating) {
return;
}
+
+ if (avoidRebindingIntentionallyDisconnectedWallpaper()
+ && mWallpaper.connection == null) {
+ Slog.w(TAG, "Trying to rebind an intentionally disconnected wallpaper!");
+ return;
+ }
+
final ComponentName wpService = mWallpaper.wallpaperComponent;
// The broadcast of package update could be delayed after service disconnected. Try
// to re-bind the service for 10 seconds.
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index e27b2be..c1e859d 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -864,8 +864,9 @@
if (transition != null) {
if (changed) {
// Always set as scene transition because it expects to be a jump-cut.
- transition.setOverrideAnimation(TransitionInfo.AnimationOptions
- .makeSceneTransitionAnimOptions(), null, null);
+ transition.setOverrideAnimation(
+ TransitionInfo.AnimationOptions.makeSceneTransitionAnimOptions(), r,
+ null, null);
r.mTransitionController.requestStartTransition(transition,
null /*startTask */, null /* remoteTransition */,
null /* displayChange */);
@@ -910,8 +911,9 @@
&& under.returningOptions.getAnimationType()
== ANIM_SCENE_TRANSITION) {
// Pass along the scene-transition animation-type
- transition.setOverrideAnimation(TransitionInfo.AnimationOptions
- .makeSceneTransitionAnimOptions(), null, null);
+ transition.setOverrideAnimation(TransitionInfo
+ .AnimationOptions.makeSceneTransitionAnimOptions(), r,
+ null, null);
}
} else {
transition.abort();
@@ -1508,7 +1510,7 @@
r.mOverrideTaskTransition);
r.mTransitionController.setOverrideAnimation(
TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
- enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition),
+ enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition), r,
null /* startCallback */, null /* finishCallback */);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4b0409b..235a211 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -353,6 +353,7 @@
import android.window.SplashScreenView;
import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.TaskSnapshot;
+import android.window.TransitionInfo;
import android.window.TransitionInfo.AnimationOptions;
import android.window.WindowContainerToken;
import android.window.WindowOnBackInvokedDispatcher;
@@ -5034,7 +5035,8 @@
// controller but don't clear the animation information from the options since they
// need to be sent to the animating activity.
mTransitionController.setOverrideAnimation(
- AnimationOptions.makeSceneTransitionAnimOptions(), null, null);
+ TransitionInfo.AnimationOptions.makeSceneTransitionAnimOptions(), this,
+ null, null);
return;
}
applyOptionsAnimation(mPendingOptions, intent);
@@ -5157,7 +5159,8 @@
}
if (options != null) {
- mTransitionController.setOverrideAnimation(options, startCallback, finishCallback);
+ mTransitionController.setOverrideAnimation(options, this, startCallback,
+ finishCallback);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 6479111..bf18a43 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1945,7 +1945,7 @@
&& sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()
&& balVerdict.allows()) {
mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity,
- sourceRecord, "launch-into-pip");
+ sourceRecord, "launch-into-pip", null /* bounds */);
}
mSupervisor.getBackgroundActivityLaunchController()
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index fc087e3..f5476f2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3796,9 +3796,22 @@
r.shortComponentName, Boolean.toString(isAutoEnter));
r.setPictureInPictureParams(params);
r.mAutoEnteringPip = isAutoEnter;
- mRootWindowContainer.moveActivityToPinnedRootTask(r,
- null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
- transition);
+
+ if (transition != null) {
+ mRootWindowContainer.moveActivityToPinnedRootTaskAndRequestStart(r,
+ "enterPictureInPictureMode");
+ } else if (getTransitionController().isCollecting()
+ || !getTransitionController().isShellTransitionsEnabled()) {
+ mRootWindowContainer.moveActivityToPinnedRootTask(r,
+ null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
+ null /* bounds */);
+ } else {
+ // Need to make a transition just for this. This shouldn't really happen
+ // though because if transition == null, we should be part of an existing one.
+ getTransitionController().createTransition(TRANSIT_PIP);
+ mRootWindowContainer.moveActivityToPinnedRootTaskAndRequestStart(r,
+ "enterPictureInPictureMode");
+ }
// Continue the pausing process after entering pip.
if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
r.getTask().schedulePauseActivity(r, false /* userLeaving */,
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index c44f838b..3710f7f 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -574,10 +574,15 @@
private void scheduleAnimation(@NonNull AnimationHandler.ScheduleAnimationBuilder builder) {
mPendingAnimation = builder.build();
- mWindowManagerService.mWindowPlacerLocked.requestTraversal();
- if (mShowWallpaper) {
- mWindowManagerService.getDefaultDisplayContentLocked().mWallpaperController
- .adjustWallpaperWindows();
+ if (mAnimationHandler.mOpenAnimAdaptor != null
+ && mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition != null) {
+ startAnimation();
+ } else {
+ mWindowManagerService.mWindowPlacerLocked.requestTraversal();
+ if (mShowWallpaper) {
+ mWindowManagerService.getDefaultDisplayContentLocked().mWallpaperController
+ .adjustWallpaperWindows();
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a6d9659..866dcd5 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2032,33 +2032,39 @@
onTop);
}
- void moveActivityToPinnedRootTask(@NonNull ActivityRecord r,
- @Nullable ActivityRecord launchIntoPipHostActivity, String reason) {
- moveActivityToPinnedRootTask(r, launchIntoPipHostActivity, reason, null /* transition */);
+ /** Wrapper/Helper for tests */
+ void moveActivityToPinnedRootTask(@NonNull ActivityRecord r, String reason) {
+ Transition newTransit = (r.mTransitionController.isCollecting()
+ || !r.mTransitionController.isShellTransitionsEnabled())
+ ? null : r.mTransitionController.createTransition(TRANSIT_PIP);
+ moveActivityToPinnedRootTaskInner(r, null /* launchIntoPipHostActivity */, reason,
+ null /* bounds */, newTransit != null);
}
void moveActivityToPinnedRootTask(@NonNull ActivityRecord r,
@Nullable ActivityRecord launchIntoPipHostActivity, String reason,
- @Nullable Transition transition) {
- moveActivityToPinnedRootTask(r, launchIntoPipHostActivity, reason, transition,
- null /* bounds */);
+ @Nullable Rect bounds) {
+ moveActivityToPinnedRootTaskInner(r, launchIntoPipHostActivity, reason, bounds,
+ false /* requestStart */);
}
- void moveActivityToPinnedRootTask(@NonNull ActivityRecord r,
+ /**
+ * Moves activity to pinned in the provided transition and also requests start on that
+ * Transition at an appropriate time.
+ */
+ void moveActivityToPinnedRootTaskAndRequestStart(@NonNull ActivityRecord r, String reason) {
+ moveActivityToPinnedRootTaskInner(r, null /* launchIntoPipHostActivity */, reason,
+ null /* bounds */, true /* requestStart */);
+ }
+
+ private void moveActivityToPinnedRootTaskInner(@NonNull ActivityRecord r,
@Nullable ActivityRecord launchIntoPipHostActivity, String reason,
- @Nullable Transition transition, @Nullable Rect bounds) {
+ @Nullable Rect bounds, boolean requestStart) {
final TaskDisplayArea taskDisplayArea = r.getDisplayArea();
final Task task = r.getTask();
final Task rootTask;
- Transition newTransition = transition;
- // Create a transition now (if not provided) to collect the current pinned Task dismiss.
- // Only do the create here as the Task (trigger) to enter PIP is not ready yet.
final TransitionController transitionController = task.mTransitionController;
- if (newTransition == null && !transitionController.isCollecting()
- && transitionController.getTransitionPlayer() != null) {
- newTransition = transitionController.createTransition(TRANSIT_PIP);
- }
transitionController.deferTransitionReady();
Transition.ReadyCondition pipChangesApplied = new Transition.ReadyCondition("movedToPip");
@@ -2273,14 +2279,16 @@
}
}
- if (newTransition != null) {
+ // can be null (for now) if shell transitions are disabled or inactive at this time
+ final Transition transit = transitionController.getCollectingTransition();
+ if (requestStart && transit != null) {
// Request at end since we want task-organizer events from ensureActivitiesVisible
// to be recognized.
- transitionController.requestStartTransition(newTransition, rootTask,
+ transitionController.requestStartTransition(transit, rootTask,
null /* remoteTransition */, null /* displayChange */);
// A new transition was created just for this operations. Since the operation is
// complete, mark it as ready.
- newTransition.setReady(rootTask, true /* ready */);
+ transit.setReady(rootTask, true /* ready */);
}
resumeFocusedTasksTopActivities();
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 0f9c001..52994c7 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -31,6 +31,8 @@
import android.view.WindowManager;
import android.window.TaskSnapshot;
+import com.android.window.flags.Flags;
+
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -150,6 +152,9 @@
if (mOpenActivities.isEmpty()) {
return false;
}
+ if (Flags.alwaysCaptureActivitySnapshot()) {
+ return true;
+ }
for (int i = mOpenActivities.size() - 1; i >= 0; --i) {
if (!mOpenActivities.get(i).mOptInOnBackInvoked) {
return false;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 82ede7e..6bfa32a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -953,10 +953,13 @@
* Set animation options for collecting transition by ActivityRecord.
* @param options AnimationOptions captured from ActivityOptions
*/
- void setOverrideAnimation(@Nullable AnimationOptions options,
+ void setOverrideAnimation(@Nullable AnimationOptions options, @NonNull ActivityRecord r,
@Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
if (!isCollecting()) return;
mOverrideOptions = options;
+ if (mOverrideOptions != null) {
+ mOverrideOptions.setUserId(r.mUserId);
+ }
sendRemoteCallback(mClientAnimationStartCallback);
mClientAnimationStartCallback = startCallback;
mClientAnimationFinishCallback = finishCallback;
@@ -2818,7 +2821,7 @@
}
}
final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
- "Transition Root: " + leashReference.getName())
+ "Transition Root: " + leashReference.getName())
.setCallsite("Transition.calculateTransitionRoots").build();
rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
// Update layers to start transaction because we prevent assignment during collect, so
@@ -2989,7 +2992,8 @@
// Create the options based on this change's custom animations and layout
// parameters
animOptions = getOptions(activityRecord /* customAnimActivity */,
- activityRecord /* animLpActivity */);
+ activityRecord /* animLpActivity */);
+ animOptions.setUserId(activityRecord.mUserId);
if (!change.hasFlags(FLAG_TRANSLUCENT)) {
// If this change is not translucent, its options are going to be
// inherited by the changes below
@@ -2999,6 +3003,7 @@
} else if (activityRecord != null && animOptionsForActivityTransition != null) {
// Use the same options from the top activity for all the activities
animOptions = animOptionsForActivityTransition;
+ animOptions.setUserId(activityRecord.mUserId);
} else if (Flags.activityEmbeddingOverlayPresentationFlag()
&& isEmbeddedTaskFragment) {
final TaskFragmentAnimationParams params = taskFragment.getAnimationParams();
@@ -3011,6 +3016,7 @@
params.getOpenAnimationResId(), params.getChangeAnimationResId(),
params.getCloseAnimationResId(), 0 /* backgroundColor */,
false /* overrideTaskTransition */);
+ animOptions.setUserId(taskFragment.getTask().mUserId);
}
}
if (animOptions != null) {
@@ -3074,6 +3080,9 @@
animOptions);
animOptions = addCustomActivityTransition(customAnimActivity, false /* open */,
animOptions);
+ if (animOptions != null) {
+ animOptions.setUserId(customAnimActivity.mUserId);
+ }
}
// Layout parameters
@@ -3087,10 +3096,12 @@
// are running an app starting animation, in which case we don't want the app to be
// able to change its animation directly.
if (animOptions != null) {
+ animOptions.setUserId(animLpActivity.mUserId);
animOptions.addOptionsFromLayoutParameters(animLp);
} else {
animOptions = TransitionInfo.AnimationOptions
.makeAnimOptionsFromLayoutParameters(animLp);
+ animOptions.setUserId(animLpActivity.mUserId);
}
}
return animOptions;
@@ -3116,6 +3127,7 @@
if (animOptions == null) {
animOptions = TransitionInfo.AnimationOptions
.makeCommonAnimOptions(activity.packageName);
+ animOptions.setUserId(activity.mUserId);
}
animOptions.addCustomActivityTransition(open, customAnim.mEnterAnim,
customAnim.mExitAnim, customAnim.mBackgroundColor);
@@ -3244,7 +3256,7 @@
// Remote animations always win, but fullscreen windows override non-fullscreen windows.
ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
w -> w.getRemoteAnimationDefinition() != null
- && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
+ && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
if (result != null) {
return result;
}
@@ -3291,7 +3303,7 @@
private void validateKeyguardOcclusion() {
if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
mController.mStateValidators.add(
- mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
+ mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
}
}
@@ -3900,9 +3912,9 @@
/** @return true if all tracked subtrees are ready. */
boolean allReady() {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b "
- + "override=%b defer=%d states=[%s]", mUsed, mReadyOverride, mDeferReadyDepth,
- groupsToString());
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " allReady query: used=%b " + "override=%b defer=%d states=[%s]", mUsed,
+ mReadyOverride, mDeferReadyDepth, groupsToString());
// If the readiness has never been touched, mUsed will be false. We never want to
// consider a transition ready if nothing has been reported on it.
if (!mUsed) return false;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 56a24dd..1d2a605 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -880,10 +880,10 @@
}
/** @see Transition#setOverrideAnimation */
- void setOverrideAnimation(TransitionInfo.AnimationOptions options,
+ void setOverrideAnimation(TransitionInfo.AnimationOptions options, ActivityRecord r,
@Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
if (mCollectingTransition == null) return;
- mCollectingTransition.setOverrideAnimation(options, startCallback, finishCallback);
+ mCollectingTransition.setOverrideAnimation(options, r, startCallback, finishCallback);
}
void setNoAnimation(WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 60ccdc7..476443a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1339,7 +1339,7 @@
Rect entryBounds = hop.getBounds();
mService.mRootWindowContainer.moveActivityToPinnedRootTask(
pipActivity, null /* launchIntoPipHostActivity */,
- "moveActivityToPinnedRootTask", null /* transition */, entryBounds);
+ "moveActivityToPinnedRootTask", entryBounds);
if (pipActivity.isState(PAUSING) && pipActivity.mPauseSchedulePendingForPip) {
// Continue the pausing process. This must be done after moving PiP activity to
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 6a0dd5a..5eec012 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -1325,27 +1325,7 @@
pw.print("encryptionRequested=");
pw.println(encryptionRequested);
- if (!Flags.dumpsysPolicyEngineMigrationEnabled()) {
- pw.print("disableCamera=");
- pw.println(disableCamera);
-
- pw.print("disableScreenCapture=");
- pw.println(disableScreenCapture);
-
- pw.print("requireAutoTime=");
- pw.println(requireAutoTime);
-
- if (permittedInputMethods != null) {
- pw.print("permittedInputMethods=");
- pw.println(permittedInputMethods);
- }
-
- pw.println("userRestrictions:");
- UserRestrictionsUtils.dumpRestrictions(pw, " ", userRestrictions);
- }
-
- if (!Flags.policyEngineMigrationV2Enabled()
- || !Flags.dumpsysPolicyEngineMigrationEnabled()) {
+ if (!Flags.policyEngineMigrationV2Enabled()) {
pw.print("mUsbDataSignaling=");
pw.println(mUsbDataSignalingEnabled);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a80ee0f..4abbdee 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -11479,10 +11479,8 @@
pw.println();
mStatLogger.dump(pw);
pw.println();
- if (Flags.dumpsysPolicyEngineMigrationEnabled()) {
- mDevicePolicyEngine.dump(pw);
- pw.println();
- }
+ mDevicePolicyEngine.dump(pw);
+ pw.println();
pw.println("Encryption Status: " + getEncryptionStatusName(getEncryptionStatus()));
pw.println("Logout user: " + getLogoutUserIdUnchecked());
pw.println();
@@ -12682,14 +12680,12 @@
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
- if (Flags.headlessDeviceOwnerSingleUserEnabled()) {
- // Block this method if the device is in headless main user mode
- Preconditions.checkCallAuthorization(
- !mInjector.userManagerIsHeadlessSystemUserMode()
- || getHeadlessDeviceOwnerModeForDeviceOwner()
- != HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER,
- "createAndManageUser was called while in headless single user mode");
- }
+ // Block this method if the device is in headless main user mode
+ Preconditions.checkCallAuthorization(
+ !mInjector.userManagerIsHeadlessSystemUserMode()
+ || getHeadlessDeviceOwnerModeForDeviceOwner()
+ != HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER,
+ "createAndManageUser was called while in headless single user mode");
// Only allow the system user to use this method
Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
@@ -13976,11 +13972,9 @@
UserManager.DISALLOW_THREAD_NETWORK,
new String[]{MANAGE_DEVICE_POLICY_THREAD_NETWORK});
}
- if (Flags.assistContentUserRestrictionEnabled()) {
- USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ASSIST_CONTENT,
- new String[]{MANAGE_DEVICE_POLICY_ASSIST_CONTENT});
- }
+ USER_RESTRICTION_PERMISSIONS.put(
+ UserManager.DISALLOW_ASSIST_CONTENT,
+ new String[]{MANAGE_DEVICE_POLICY_ASSIST_CONTENT});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
USER_RESTRICTION_PERMISSIONS.put(
@@ -17315,7 +17309,7 @@
return STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
}
- if (Flags.headlessDeviceOwnerSingleUserEnabled() && isHeadlessModeSingleUser) {
+ if (isHeadlessModeSingleUser) {
ensureSetUpUser = mUserManagerInternal.getMainUserId();
if (ensureSetUpUser == UserHandle.USER_NULL) {
return STATUS_HEADLESS_ONLY_SYSTEM_USER;
@@ -19759,16 +19753,14 @@
}
private void transferSubscriptionOwnership(ComponentName admin, ComponentName target) {
- if (Flags.esimManagementEnabled()) {
- SubscriptionManager subscriptionManager = mContext.getSystemService(
- SubscriptionManager.class);
- for (int subId : getSubscriptionIdsInternal(admin.getPackageName()).toArray()) {
- try {
- subscriptionManager.setGroupOwner(subId, target.getPackageName());
- } catch (Exception e) {
- // Shouldn't happen.
- Slogf.e(LOG_TAG, e, "Error setting group owner for subId: " + subId);
- }
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ for (int subId : getSubscriptionIdsInternal(admin.getPackageName()).toArray()) {
+ try {
+ subscriptionManager.setGroupOwner(subId, target.getPackageName());
+ } catch (Exception e) {
+ // Shouldn't happen.
+ Slogf.e(LOG_TAG, e, "Error setting group owner for subId: " + subId);
}
}
}
@@ -20666,9 +20658,7 @@
// have OP_RUN_ANY_IN_BACKGROUND app op and won't execute in the background. The
// code below grants that app op, and once the exemption is in place, the user
// won't be able to disable background usage anymore.
- if (Flags.powerExemptionBgUsageFix()
- && exemption == EXEMPT_FROM_POWER_RESTRICTIONS
- && newMode == MODE_ALLOWED) {
+ if (exemption == EXEMPT_FROM_POWER_RESTRICTIONS && newMode == MODE_ALLOWED) {
setBgUsageAppOp(appOpsMgr, appInfo);
}
}
@@ -22065,8 +22055,8 @@
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
- int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
- && isSingleUserMode && mInjector.userManagerIsHeadlessSystemUserMode()
+ int deviceOwnerUserId =
+ isSingleUserMode && mInjector.userManagerIsHeadlessSystemUserMode()
? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM;
if (!removeNonRequiredAppsForManagedDevice(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 19a942c..24ee46f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -536,7 +536,6 @@
USER_RESTRICTION_FLAGS.put(
UserManager.DISALLOW_THREAD_NETWORK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
}
- USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0);
for (String key : USER_RESTRICTION_FLAGS.keySet()) {
createAndAddUserRestrictionPolicyDefinition(key, USER_RESTRICTION_FLAGS.get(key));
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index e1cb37d..8068d46 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -238,9 +238,7 @@
}
for (int user : resolveUsers(userId)) {
- if (Flags.disallowUserControlBgUsageFix()) {
- setBgUsageAppOp(packages, pmi, user, appOpsManager);
- }
+ setBgUsageAppOp(packages, pmi, user, appOpsManager);
if (Flags.disallowUserControlStoppedStateFix()) {
for (String packageName : packages) {
pmi.setPackageStoppedState(packageName, false, user);
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java
index 54f4607..1abc557 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java
@@ -18,7 +18,9 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -27,7 +29,9 @@
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
import android.service.dreams.DreamOverlayService;
+import android.service.dreams.Flags;
import android.service.dreams.IDreamOverlay;
import android.service.dreams.IDreamOverlayCallback;
import android.service.dreams.IDreamOverlayClient;
@@ -136,7 +140,7 @@
// Start the dream.
client.startDream(mLayoutParams, mOverlayCallback,
- FIRST_DREAM_COMPONENT.flattenToString(), false);
+ FIRST_DREAM_COMPONENT.flattenToString(), false, false);
// The callback should not have run yet.
verify(monitor, never()).onStartDream();
@@ -194,22 +198,24 @@
// Start a dream with the first client and ensure the dream is now active from the
// overlay's perspective.
firstClient.startDream(mLayoutParams, mOverlayCallback,
- FIRST_DREAM_COMPONENT.flattenToString(), false);
+ FIRST_DREAM_COMPONENT.flattenToString(), true, false);
verify(monitor).onStartDream();
assertThat(service.getDreamComponent()).isEqualTo(FIRST_DREAM_COMPONENT);
+ assertThat(service.isDreamInPreviewMode()).isTrue();
Mockito.clearInvocations(monitor);
// Start a dream from the second client and verify that the overlay has both cycled to
// the new dream (ended/started).
secondClient.startDream(mLayoutParams, mOverlayCallback,
- SECOND_DREAM_COMPONENT.flattenToString(), false);
+ SECOND_DREAM_COMPONENT.flattenToString(), false, false);
verify(monitor).onEndDream();
verify(monitor).onStartDream();
assertThat(service.getDreamComponent()).isEqualTo(SECOND_DREAM_COMPONENT);
+ assertThat(service.isDreamInPreviewMode()).isFalse();
Mockito.clearInvocations(monitor);
@@ -221,6 +227,47 @@
verify(monitor, never()).onWakeUp();
}
+ /**
+ * Verifies that only the currently started dream is able to affect the overlay.
+ */
+ @Test
+ @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT)
+ public void testRedirectToWakeAcrossClients() throws RemoteException {
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mExecutor).execute(any());
+
+ final TestDreamOverlayService.Monitor monitor = Mockito.mock(
+ TestDreamOverlayService.Monitor.class);
+ final TestDreamOverlayService service = new TestDreamOverlayService(monitor, mExecutor);
+ final IBinder binder = service.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
+
+ service.redirectWake(true);
+
+ final IDreamOverlayClient client = getClient(overlay);
+
+ // Start the dream.
+ client.startDream(mLayoutParams, mOverlayCallback,
+ FIRST_DREAM_COMPONENT.flattenToString(), false, false);
+ // Make sure redirect state is set on dream.
+ verify(mOverlayCallback).onRedirectWake(eq(true));
+
+ // Make sure new changes are propagated.
+ clearInvocations(mOverlayCallback);
+ service.redirectWake(false);
+ verify(mOverlayCallback).onRedirectWake(eq(false));
+
+
+ // Start another dream, make sure new dream is informed of current state.
+ service.redirectWake(true);
+ clearInvocations(mOverlayCallback);
+ client.startDream(mLayoutParams, mOverlayCallback,
+ FIRST_DREAM_COMPONENT.flattenToString(), false, false);
+ verify(mOverlayCallback).onRedirectWake(eq(true));
+ }
+
private static IDreamOverlayClient getClient(IDreamOverlay overlay) throws RemoteException {
final OverlayClientCallback callback = new OverlayClientCallback();
overlay.getClient(callback);
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
index 43aa7fe..7c239ef 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
@@ -385,7 +385,8 @@
final ArgumentCaptor<IDreamOverlayCallback> overlayCallbackCaptor =
ArgumentCaptor.forClass(IDreamOverlayCallback.class);
verify(mDreamOverlayClient, description("dream client not informed of dream start"))
- .startDream(any(), overlayCallbackCaptor.capture(), any(), anyBoolean());
+ .startDream(any(), overlayCallbackCaptor.capture(), any(), anyBoolean(),
+ anyBoolean());
mDreamOverlayCallback = overlayCallbackCaptor.getValue();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 8656b99..51aa528 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -46,6 +46,7 @@
import static com.android.server.am.ActivityManagerService.FOLLOW_UP_OOMADJUSTER_UPDATE_MSG;
import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
+import static com.android.server.am.ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ;
@@ -844,6 +845,49 @@
@SuppressWarnings("GuardedBy")
@Test
+ public void testUpdateOomAdj_DoAll_PreviousApp() {
+ final int numberOfApps = 15;
+ final ProcessRecord[] apps = new ProcessRecord[numberOfApps];
+ for (int i = 0; i < numberOfApps; i++) {
+ apps[i] = spy(makeDefaultProcessRecord(MOCKAPP_PID + i, MOCKAPP_UID + i,
+ MOCKAPP_PROCESSNAME + i, MOCKAPP_PACKAGENAME + i, true));
+ final WindowProcessController wpc = apps[i].getWindowProcessController();
+ doReturn(true).when(wpc).isPreviousProcess();
+ doReturn(true).when(wpc).hasActivities();
+ }
+ mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setProcessesToLru(apps);
+ mService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+
+ for (int i = 0; i < numberOfApps; i++) {
+ assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+ SCHED_GROUP_BACKGROUND, "previous");
+ }
+
+ if (!Flags.followUpOomadjUpdates()) return;
+
+ for (int i = 0; i < numberOfApps; i++) {
+ final ArgumentCaptor<Long> followUpTimeCaptor = ArgumentCaptor.forClass(Long.class);
+ verify(mService.mHandler).sendEmptyMessageAtTime(eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG),
+ followUpTimeCaptor.capture());
+ mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
+ }
+
+ mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+
+ for (int i = 0; i < numberOfApps; i++) {
+ final int mruIndex = numberOfApps - i - 1;
+ int expectedAdj = CACHED_APP_MIN_ADJ + (mruIndex * 2 * CACHED_APP_IMPORTANCE_LEVELS);
+ if (expectedAdj > CACHED_APP_MAX_ADJ) {
+ expectedAdj = CACHED_APP_MAX_ADJ;
+ }
+ assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, expectedAdj,
+ SCHED_GROUP_BACKGROUND, "previous-expired");
+ }
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_DoOne_Backup() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index bc2fd73..2f7b8d2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -200,7 +200,8 @@
eq(TEST_REQUEST_ID),
eq(sensor.getCookie()),
anyBoolean() /* allowBackgroundAuthentication */,
- anyBoolean() /* isForLegacyFingerprintManager */);
+ anyBoolean() /* isForLegacyFingerprintManager */,
+ eq(false) /* isMandatoryBiometrics */);
}
final int cookie1 = session.mPreAuthInfo.eligibleSensors.get(0).getCookie();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index d2961bc..6b8e414 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -636,7 +636,8 @@
eq(TEST_REQUEST_ID),
cookieCaptor.capture() /* cookie */,
anyBoolean() /* allowBackgroundAuthentication */,
- anyBoolean() /* isForLegacyFingerprintManager */);
+ anyBoolean() /* isForLegacyFingerprintManager */,
+ eq(false) /* isMandatoryBiometrics */);
// onReadyForAuthentication, mAuthSession state OK
mBiometricService.mImpl.onReadyForAuthentication(TEST_REQUEST_ID, cookieCaptor.getValue());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
index 4604b31..613cb20 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
@@ -98,7 +98,8 @@
@NonNull ClientMonitorCallbackConverter callback) {
super(context, lazyDaemon, token, callback, 0 /* userId */, "Test", 0 /* cookie */,
TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */,
- mock(BiometricLogger.class), mock(BiometricContext.class));
+ mock(BiometricLogger.class), mock(BiometricContext.class),
+ false /* isMandatoryBiometrics */);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index ffc7811..4f07380 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -62,7 +62,8 @@
extends HalClientMonitor<T> {
public InterruptableMonitor() {
super(null, null, null, null, 0, null, 0, 0,
- mock(BiometricLogger.class), mock(BiometricContext.class));
+ mock(BiometricLogger.class), mock(BiometricContext.class),
+ false /* isMandatoryBiometrics */);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 36a7b3d..90c07d4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -1051,6 +1051,11 @@
public String getAttributionTag() {
return null;
}
+
+ @Override
+ public boolean isMandatoryBiometrics() {
+ return false;
+ }
}
private static class TestAuthenticationClient
@@ -1176,7 +1181,7 @@
@NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum) {
super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */, TAG,
cookie, TEST_SENSOR_ID, mock(BiometricLogger.class),
- mock(BiometricContext.class));
+ mock(BiometricContext.class), false /* isMandatoryBiometrics */);
mProtoEnum = protoEnum;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index 17b499e..d6f7e21 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -625,25 +625,10 @@
// pretend reboot happens here
when(mInjected.getBootCount()).thenReturn(1);
- ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
- doNothing()
- .when(mInjected)
- .reportMetric(
- metricsSuccessCaptor.capture(),
- metricsErrorCodeCaptor.capture(),
- eq(2) /* Server based */,
- eq(1) /* attempt count */,
- anyInt(),
- eq(0) /* vbmeta status */,
- anyInt());
+
mService.loadRebootEscrowDataIfAvailable(null);
verify(mServiceConnection, never()).unwrap(any(), anyLong());
verify(mCallbacks, never()).onRebootEscrowRestored(anyByte(), any(), anyInt());
- assertFalse(metricsSuccessCaptor.getValue());
- assertEquals(
- Integer.valueOf(RebootEscrowManager.ERROR_NO_REBOOT_ESCROW_DATA),
- metricsErrorCodeCaptor.getValue());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
index 55c48e0..f0a5f75 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -36,7 +36,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,11 +54,6 @@
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- @Before
- public void setUp() {
- mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
- }
-
@Test
public void testNonNull() {
Bundle out = UserRestrictionsUtils.nonNull(null);
@@ -144,7 +138,6 @@
@Test
public void testCanProfileOwnerChange_restrictionRequiresOrgOwnedDevice_orgOwned() {
- mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
UserManager.DISALLOW_SIM_GLOBALLY,
false,
@@ -157,7 +150,6 @@
@Test
public void testCanProfileOwnerChange_restrictionRequiresOrgOwnedDevice_notOrgOwned() {
- mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
assertFalse(UserRestrictionsUtils.canProfileOwnerChange(
UserManager.DISALLOW_SIM_GLOBALLY,
false,
@@ -169,22 +161,7 @@
}
@Test
- public void
- testCanProfileOwnerChange_disabled_restrictionRequiresOrgOwnedDevice_notOrgOwned() {
- mSetFlagsRule.disableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
- assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
- UserManager.DISALLOW_SIM_GLOBALLY,
- false,
- false));
- assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
- UserManager.DISALLOW_SIM_GLOBALLY,
- true,
- false));
- }
-
- @Test
public void testCanProfileOwnerChange_restrictionNotRequiresOrgOwnedDevice_orgOwned() {
- mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
UserManager.DISALLOW_ADJUST_VOLUME,
false,
@@ -197,7 +174,6 @@
@Test
public void testCanProfileOwnerChange_restrictionNotRequiresOrgOwnedDevice_notOrgOwned() {
- mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
UserManager.DISALLOW_ADJUST_VOLUME,
false,
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index f1db713..957b5e0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -371,8 +371,7 @@
ensureTaskPlacement(fullscreenTask, firstActivity, secondActivity);
// Move first activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity,
- null /* launchIntoPipHostActivity */, "initialMove");
+ mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity, "initialMove");
final TaskDisplayArea taskDisplayArea = fullscreenTask.getDisplayArea();
Task pinnedRootTask = taskDisplayArea.getRootPinnedTask();
@@ -381,8 +380,7 @@
ensureTaskPlacement(fullscreenTask, secondActivity);
// Move second activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity,
- null /* launchIntoPipHostActivity */, "secondMove");
+ mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "secondMove");
// Need to get root tasks again as a new instance might have been created.
pinnedRootTask = taskDisplayArea.getRootPinnedTask();
@@ -413,8 +411,7 @@
// Move first activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity,
- null /* launchIntoPipHostActivity */, "initialMove");
+ mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "initialMove");
assertTrue(firstActivity.mRequestForceTransition);
}
@@ -434,8 +431,7 @@
transientActivity.setState(RESUMED, "test");
transientActivity.getTask().moveToFront("test");
- mRootWindowContainer.moveActivityToPinnedRootTask(activity2,
- null /* launchIntoPipHostActivity */, "test");
+ mRootWindowContainer.moveActivityToPinnedRootTask(activity2, "test");
assertEquals("Created PiP task must not change focus", transientActivity.getTask(),
mRootWindowContainer.getTopDisplayFocusedRootTask());
final Task newPipTask = activity2.getTask();
@@ -460,8 +456,7 @@
final Task task = activity.getTask();
// Move activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(activity,
- null /* launchIntoPipHostActivity */, "test");
+ mRootWindowContainer.moveActivityToPinnedRootTask(activity, "test");
// Ensure a task has moved over.
ensureTaskPlacement(task, activity);
@@ -499,8 +494,7 @@
final Task task = activity.getTask();
// Move activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(activity,
- null /* launchIntoPipHostActivity */, "test");
+ mRootWindowContainer.moveActivityToPinnedRootTask(activity, "test");
// Ensure a task has moved over.
ensureTaskPlacement(task, activity);
@@ -524,8 +518,7 @@
final ActivityRecord secondActivity = taskFragment.getBottomMostActivity();
// Move first activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity,
- null /* launchIntoPipHostActivity */, "test");
+ mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity, "test");
final TaskDisplayArea taskDisplayArea = fullscreenTask.getDisplayArea();
final Task pinnedRootTask = taskDisplayArea.getRootPinnedTask();
@@ -556,8 +549,7 @@
final ActivityRecord topActivity = taskFragment.getTopMostActivity();
// Move the top activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(topActivity,
- null /* launchIntoPipHostActivity */, "test");
+ mRootWindowContainer.moveActivityToPinnedRootTask(topActivity, "test");
final Task pinnedRootTask = task.getDisplayArea().getRootPinnedTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 6be1af2..cc1805a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -368,8 +368,7 @@
assertNotEquals(TaskFragmentAnimationParams.DEFAULT, taskFragment0.getAnimationParams());
// Move activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(activity,
- null /* launchIntoPipHostActivity */, "test");
+ mRootWindowContainer.moveActivityToPinnedRootTask(activity, "test");
// Ensure taskFragment requested config is reset.
assertEquals(taskFragment0, activity.getOrganizedTaskFragment());
@@ -399,8 +398,7 @@
spyOn(mAtm.mTaskFragmentOrganizerController);
// Move activity to pinned.
- mRootWindowContainer.moveActivityToPinnedRootTask(activity0,
- null /* launchIntoPipHostActivity */, "test");
+ mRootWindowContainer.moveActivityToPinnedRootTask(activity0, "test");
// Ensure taskFragment requested config is reset.
assertTrue(taskFragment0.mClearedTaskFragmentForPip);
@@ -434,8 +432,7 @@
.createActivityCount(1)
.build();
final ActivityRecord activity = taskFragment.getTopMostActivity();
- mRootWindowContainer.moveActivityToPinnedRootTask(activity,
- null /* launchIntoPipHostActivity */, "test");
+ mRootWindowContainer.moveActivityToPinnedRootTask(activity, "test");
spyOn(mAtm.mTaskFragmentOrganizerController);
assertEquals(mIOrganizer, activity.mLastTaskFragmentOrganizerBeforePip);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 55f74e9d..45082d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -230,8 +230,7 @@
final Task originalTask = activityMain.getTask();
final ActivityRecord activityPip = new ActivityBuilder(mAtm).setTask(originalTask).build();
activityPip.setState(RESUMED, "test");
- mAtm.mRootWindowContainer.moveActivityToPinnedRootTask(activityPip,
- null /* launchIntoPipHostActivity */, "test");
+ mAtm.mRootWindowContainer.moveActivityToPinnedRootTask(activityPip, "test");
final Task pinnedActivityTask = activityPip.getTask();
// Simulate pinnedActivityTask unintentionally added to recent during top activity resume.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 52a80b0..7320c0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -2005,10 +2005,10 @@
@DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_disableAnimOptionsPerChange() {
- initializeOverrideAnimationOptionsTest();
+ ActivityRecord r = initializeOverrideAnimationOptionsTest();
TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
.makeCommonAnimOptions("testPackage");
- mTransition.setOverrideAnimation(options, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, r, null /* startCallback */,
null /* finishCallback */);
mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2019,10 +2019,10 @@
@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_fromStyleAnimOptions() {
- initializeOverrideAnimationOptionsTest();
+ ActivityRecord r = initializeOverrideAnimationOptionsTest();
TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
.makeCommonAnimOptions("testPackage");
- mTransition.setOverrideAnimation(options, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, r, null /* startCallback */,
null /* finishCallback */);
mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2045,10 +2045,10 @@
@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_sceneAnimOptions() {
- initializeOverrideAnimationOptionsTest();
+ ActivityRecord r = initializeOverrideAnimationOptionsTest();
TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
.makeSceneTransitionAnimOptions();
- mTransition.setOverrideAnimation(options, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, r, null /* startCallback */,
null /* finishCallback */);
mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2071,10 +2071,10 @@
@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_crossProfileAnimOptions() {
- initializeOverrideAnimationOptionsTest();
+ ActivityRecord r = initializeOverrideAnimationOptionsTest();
TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
.makeCrossProfileAnimOptions();
- mTransition.setOverrideAnimation(options, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, r, null /* startCallback */,
null /* finishCallback */);
final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
@@ -2099,13 +2099,13 @@
@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptions() {
- initializeOverrideAnimationOptionsTest();
+ ActivityRecord r = initializeOverrideAnimationOptionsTest();
TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
.makeCustomAnimOptions("testPackage", Resources.ID_NULL,
TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
Color.GREEN, false /* overrideTaskTransition */);
- mTransition.setOverrideAnimation(options, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, r, null /* startCallback */,
null /* finishCallback */);
mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2132,7 +2132,7 @@
@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_haveTaskFragmentAnimParams() {
- initializeOverrideAnimationOptionsTest();
+ ActivityRecord r = initializeOverrideAnimationOptionsTest();
final TaskFragment embeddedTf = mTransition.mTargets.get(2).mContainer.asTaskFragment();
embeddedTf.setAnimationParams(new TaskFragmentAnimationParams.Builder()
@@ -2145,7 +2145,7 @@
TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
Color.GREEN, false /* overrideTaskTransition */);
- mTransition.setOverrideAnimation(options, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, r, null /* startCallback */,
null /* finishCallback */);
final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
@@ -2181,13 +2181,13 @@
@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptionsWithTaskOverride() {
- initializeOverrideAnimationOptionsTest();
+ ActivityRecord r = initializeOverrideAnimationOptionsTest();
TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
.makeCustomAnimOptions("testPackage", Resources.ID_NULL,
TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
Color.GREEN, true /* overrideTaskTransition */);
- mTransition.setOverrideAnimation(options, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, r, null /* startCallback */,
null /* finishCallback */);
mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2213,7 +2213,7 @@
options.getBackgroundColor(), activityChange.getBackgroundColor());
}
- private void initializeOverrideAnimationOptionsTest() {
+ private ActivityRecord initializeOverrideAnimationOptionsTest() {
mTransition = createTestTransition(TRANSIT_OPEN);
// Test set AnimationOptions for Activity and Task.
@@ -2241,6 +2241,7 @@
embeddedTf.getAnimationLeash()));
mInfo.addChange(new TransitionInfo.Change(null /* container */,
nonEmbeddedActivity.getAnimationLeash()));
+ return nonEmbeddedActivity;
}
@Test
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1ba496d..cba2eea 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9994,6 +9994,17 @@
public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool";
/**
+ * Indicate whether carrier roaming to satellite is using P2P SMS.
+ *
+ * This will need agreement with carriers before enabling this flag.
+ *
+ * The default value is false.
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public static final String KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL =
+ "satellite_roaming_p2p_sms_supported_bool";
+
+ /**
* Defines the NIDD (Non-IP Data Delivery) APN to be used for carrier roaming to satellite
* attachment. For more on NIDD, see 3GPP TS 29.542.
* Note this config is the only source of truth regarding the definition of the APN.
@@ -10075,7 +10086,35 @@
*/
@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public static final String KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT =
- "satellite_screen_off_inactivity_timeout_duration_sec_int";
+ "satellite_screen_off_inactivity_timeout_sec_int";
+
+ /**
+ * An integer key holds the timeout duration in seconds used to determine whether to exit P2P
+ * SMS mode and start TN scanning.
+ *
+ * The timer is started when the device is not connected, user is not pointing to the
+ * satellite and no data transfer is happening.
+ * When the timer expires, the device will move to IDLE mode upon which TN scanning will start.
+ *
+ * The default value is 180 seconds.
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public static final String KEY_SATELLITE_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT =
+ "satellite_p2p_sms_inactivity_timeout_sec_int";
+
+ /**
+ * An integer key holds the timeout duration in seconds used to determine whether to exit ESOS
+ * mode and start TN scanning.
+ *
+ * The timer is started when the device is not connected, user is not pointing to the
+ * satellite and no data transfer is happening.
+ * When the timer expires, the device will move to IDLE mode upon which TN scanning will start.
+ *
+ * The default value is 600 seconds.
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public static final String KEY_SATELLITE_ESOS_INACTIVITY_TIMEOUT_SEC_INT =
+ "satellite_esos_inactivity_timeout_sec_int";
/**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
@@ -11235,12 +11274,15 @@
sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
(int) TimeUnit.SECONDS.toMillis(30));
sDefaults.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false);
+ sDefaults.putBoolean(KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL, false);
sDefaults.putString(KEY_SATELLITE_NIDD_APN_NAME_STRING, "");
sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 0);
sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT,
SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911);
sDefaults.putInt(KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT, 180);
sDefaults.putInt(KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT, 30);
+ sDefaults.putInt(KEY_SATELLITE_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT, 180);
+ sDefaults.putInt(KEY_SATELLITE_ESOS_INACTIVITY_TIMEOUT_SEC_INT, 600);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 6ef953c..c5934a7 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -546,6 +546,16 @@
public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911 = 2;
/**
+ * This intent will be broadcasted if there are any change to list of subscriber informations.
+ * This intent will be sent only to the app with component defined in
+ * config_satellite_carrier_roaming_esos_provisioned_class and package defined in
+ * config_satellite_gateway_service_package
+ * @hide
+ */
+ public static final String ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED =
+ "android.telephony.action.ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED";
+
+ /**
* Request to enable or disable the satellite modem and demo mode.
* If satellite modem and cellular modem cannot work concurrently,
* then this will disable the cellular modem if satellite modem is enabled,
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 7a4f40e..9751459 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -50,21 +50,21 @@
template <typename T>
bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, StringPiece rhs) {
- return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
+ return lhs->name < rhs;
}
template <typename T>
bool greater_than_struct_with_name(StringPiece lhs, const std::unique_ptr<T>& rhs) {
- return rhs->name.compare(0, rhs->name.size(), lhs.data(), lhs.size()) > 0;
+ return rhs->name > lhs;
}
template <typename T>
struct NameEqualRange {
bool operator()(const std::unique_ptr<T>& lhs, StringPiece rhs) const {
- return less_than_struct_with_name<T>(lhs, rhs);
+ return less_than_struct_with_name(lhs, rhs);
}
bool operator()(StringPiece lhs, const std::unique_ptr<T>& rhs) const {
- return greater_than_struct_with_name<T>(lhs, rhs);
+ return greater_than_struct_with_name(lhs, rhs);
}
};
@@ -74,7 +74,7 @@
if (lhs.id != rhs.second) {
return lhs.id < rhs.second;
}
- return lhs.name.compare(0, lhs.name.size(), rhs.first.data(), rhs.first.size()) < 0;
+ return lhs.name < rhs.first;
}
template <typename T, typename Func, typename Elements>
@@ -90,14 +90,16 @@
StringPiece product;
};
-template <typename T>
-bool lt_config_key_ref(const T& lhs, const ConfigKey& rhs) {
- int cmp = lhs->config.compare(*rhs.config);
- if (cmp == 0) {
- cmp = StringPiece(lhs->product).compare(rhs.product);
+struct lt_config_key_ref {
+ template <typename T>
+ bool operator()(const T& lhs, const ConfigKey& rhs) const noexcept {
+ int cmp = lhs->config.compare(*rhs.config);
+ if (cmp == 0) {
+ cmp = lhs->product.compare(rhs.product);
+ }
+ return cmp < 0;
}
- return cmp < 0;
-}
+};
} // namespace
@@ -159,10 +161,10 @@
ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
android::StringPiece product) {
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
- lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
+ lt_config_key_ref());
if (iter != values.end()) {
ResourceConfigValue* value = iter->get();
- if (value->config == config && StringPiece(value->product) == product) {
+ if (value->config == config && value->product == product) {
return value;
}
}
@@ -172,10 +174,10 @@
const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescription& config,
android::StringPiece product) const {
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
- lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
+ lt_config_key_ref());
if (iter != values.end()) {
ResourceConfigValue* value = iter->get();
- if (value->config == config && StringPiece(value->product) == product) {
+ if (value->config == config && value->product == product) {
return value;
}
}
@@ -185,10 +187,10 @@
ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config,
StringPiece product) {
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
- lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
+ lt_config_key_ref());
if (iter != values.end()) {
ResourceConfigValue* value = iter->get();
- if (value->config == config && StringPiece(value->product) == product) {
+ if (value->config == config && value->product == product) {
return value;
}
}
@@ -199,36 +201,21 @@
std::vector<ResourceConfigValue*> ResourceEntry::FindAllValues(const ConfigDescription& config) {
std::vector<ResourceConfigValue*> results;
-
- auto iter = values.begin();
+ auto iter =
+ std::lower_bound(values.begin(), values.end(), ConfigKey{&config, ""}, lt_config_key_ref());
for (; iter != values.end(); ++iter) {
ResourceConfigValue* value = iter->get();
- if (value->config == config) {
- results.push_back(value);
- ++iter;
+ if (value->config != config) {
break;
}
- }
-
- for (; iter != values.end(); ++iter) {
- ResourceConfigValue* value = iter->get();
- if (value->config == config) {
- results.push_back(value);
- }
+ results.push_back(value);
}
return results;
}
bool ResourceEntry::HasDefaultValue() const {
- const ConfigDescription& default_config = ConfigDescription::DefaultConfig();
-
// The default config should be at the top of the list, since the list is sorted.
- for (auto& config_value : values) {
- if (config_value->config == default_config) {
- return true;
- }
- }
- return false;
+ return !values.empty() && values.front()->config == ConfigDescription::DefaultConfig();
}
ResourceTable::CollisionResult ResourceTable::ResolveFlagCollision(FlagStatus existing,
@@ -364,14 +351,14 @@
if (found) {
return &*it;
}
- return &*el.insert(it, std::forward<T>(value));
+ return &*el.insert(it, std::move(value));
}
};
struct PackageViewComparer {
bool operator()(const ResourceTablePackageView& lhs, const ResourceTablePackageView& rhs) {
return less_than_struct_with_name_and_id<ResourceTablePackageView, uint8_t>(
- lhs, std::make_pair(rhs.name, rhs.id));
+ lhs, std::tie(rhs.name, rhs.id));
}
};
@@ -384,7 +371,7 @@
struct EntryViewComparer {
bool operator()(const ResourceTableEntryView& lhs, const ResourceTableEntryView& rhs) {
return less_than_struct_with_name_and_id<ResourceTableEntryView, uint16_t>(
- lhs, std::make_pair(rhs.name, rhs.id));
+ lhs, std::tie(rhs.name, rhs.id));
}
};
@@ -429,10 +416,10 @@
const ResourceConfigValue* ResourceTableEntryView::FindValue(const ConfigDescription& config,
android::StringPiece product) const {
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
- lt_config_key_ref<const ResourceConfigValue*>);
+ lt_config_key_ref());
if (iter != values.end()) {
const ResourceConfigValue* value = *iter;
- if (value->config == config && StringPiece(value->product) == product) {
+ if (value->config == config && value->product == product) {
return value;
}
}
@@ -615,11 +602,15 @@
result = ResolveValueCollision(config_value->value.get(), res.value.get());
}
switch (result) {
- case CollisionResult::kKeepBoth:
+ case CollisionResult::kKeepBoth: {
// Insert the value ignoring for duplicate configurations
- entry->values.push_back(util::make_unique<ResourceConfigValue>(res.config, res.product));
- entry->values.back()->value = std::move(res.value);
+ auto it = entry->values.insert(
+ std::lower_bound(entry->values.begin(), entry->values.end(),
+ ConfigKey{&res.config, res.product}, lt_config_key_ref()),
+ util::make_unique<ResourceConfigValue>(res.config, res.product));
+ (*it)->value = std::move(res.value);
break;
+ }
case CollisionResult::kTakeNew:
// Take the incoming value.