diff options
127 files changed, 1982 insertions, 1098 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index ddfd364cc55d..4e6dacff290e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -7964,13 +7964,13 @@ package android.app.admin { field public static final String LOCK_TASK_POLICY = "lockTask"; field public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended"; field public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked"; - field public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity"; + field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity"; field public static final String PERMISSION_GRANT_POLICY = "permissionGrant"; field public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY = "persistentPreferredActivity"; field public static final String RESET_PASSWORD_TOKEN_POLICY = "resetPasswordToken"; field public static final String SECURITY_LOGGING_POLICY = "securityLogging"; field public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled"; - field public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; + field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; field public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY = "userControlDisabledPackages"; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index a1aa679f01a9..009d08245da2 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -597,19 +597,19 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int); method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs(); - method @RequiresPermission("android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT") public void forceSetMaxPolicyStorageLimit(int); + method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_internal_bug_fix_enabled") @RequiresPermission("android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT") public void forceSetMaxPolicyStorageLimit(int); method public void forceUpdateUserSetupComplete(int); method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages(); method @Deprecated public int getDeviceOwnerType(@NonNull android.content.ComponentName); method @Nullable public String getDevicePolicyManagementRoleHolderUpdaterPackage(); method @NonNull public java.util.Set<java.lang.String> getDisallowedSystemApps(@NonNull android.content.ComponentName, int, @NonNull String); - method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int getHeadlessDeviceOwnerMode(); + method @FlaggedApi("android.app.admin.flags.headless_device_owner_provisioning_fix_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int getHeadlessDeviceOwnerMode(); method public long getLastBugReportRequestTime(); method public long getLastNetworkLogRetrievalTime(); method public long getLastSecurityLogRetrievalTime(); method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public java.util.Set<java.lang.String> getPolicyExemptApps(); - method @RequiresPermission("android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT") public int getPolicySizeForAdmin(@NonNull android.app.admin.EnforcingAdmin); + method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_internal_bug_fix_enabled") @RequiresPermission("android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT") public int getPolicySizeForAdmin(@NonNull android.app.admin.EnforcingAdmin); method public boolean isCurrentInputMethodSetByOwner(); method public boolean isFactoryResetProtectionPolicySupported(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isNewUserDisclaimerAcknowledged(); @@ -680,7 +680,7 @@ package android.app.admin { } public final class EnforcingAdmin implements android.os.Parcelable { - ctor public EnforcingAdmin(@NonNull String, @NonNull android.app.admin.Authority, @NonNull android.os.UserHandle, @Nullable android.content.ComponentName); + ctor @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_internal_bug_fix_enabled") public EnforcingAdmin(@NonNull String, @NonNull android.app.admin.Authority, @NonNull android.os.UserHandle, @Nullable android.content.ComponentName); } public final class FlagUnion extends android.app.admin.ResolutionMechanism<java.lang.Integer> { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 21396a1a36e5..8fd332621599 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -7459,15 +7459,15 @@ public class AppOpsManager { } /** - * Similar to {@link #onOpChanged(String, String, int)} but includes the device for which - * the op mode has changed. + * Similar to {@link #onOpChanged(String, String)} but includes user and the device for + * which the op mode has changed. * * <p> Implement this method if callbacks are required on all devices. * If not implemented explicitly, the default implementation will notify for op changes - * on the default device {@link VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT} only. + * on the default device only. * - * <p> If implemented, {@link #onOpChanged(String, String, int)} - * will not be called automatically. + * <p> If implemented, {@link #onOpChanged(String, String)} will not be called + * automatically. * * @param op The Op that changed. * @param packageName Package of the app whose Op changed. diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java index 515c1c66b2a3..02e492bb06aa 100644 --- a/core/java/android/app/admin/AccountTypePolicyKey.java +++ b/core/java/android/app/admin/AccountTypePolicyKey.java @@ -24,6 +24,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.app.admin.flags.Flags; import android.os.Bundle; import android.os.Parcel; @@ -53,7 +54,9 @@ public final class AccountTypePolicyKey extends PolicyKey { @TestApi public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) { super(key); - PolicySizeVerifier.enforceMaxStringLength(accountType, "accountType"); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + PolicySizeVerifier.enforceMaxStringLength(accountType, "accountType"); + } mAccountType = Objects.requireNonNull((accountType)); } diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java index 00e67e64502a..c993671f4fc1 100644 --- a/core/java/android/app/admin/BundlePolicyValue.java +++ b/core/java/android/app/admin/BundlePolicyValue.java @@ -18,6 +18,7 @@ package android.app.admin; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.flags.Flags; import android.os.Bundle; import android.os.Parcel; @@ -30,7 +31,9 @@ public final class BundlePolicyValue extends PolicyValue<Bundle> { public BundlePolicyValue(Bundle value) { super(value); - PolicySizeVerifier.enforceMaxBundleFieldsLength(value); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + PolicySizeVerifier.enforceMaxBundleFieldsLength(value); + } } private BundlePolicyValue(Parcel source) { diff --git a/core/java/android/app/admin/ComponentNamePolicyValue.java b/core/java/android/app/admin/ComponentNamePolicyValue.java index f092b7bb5538..a7a2f7d27e0d 100644 --- a/core/java/android/app/admin/ComponentNamePolicyValue.java +++ b/core/java/android/app/admin/ComponentNamePolicyValue.java @@ -18,6 +18,7 @@ package android.app.admin; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.flags.Flags; import android.content.ComponentName; import android.os.Parcel; @@ -30,7 +31,9 @@ public final class ComponentNamePolicyValue extends PolicyValue<ComponentName> { public ComponentNamePolicyValue(@NonNull ComponentName value) { super(value); - PolicySizeVerifier.enforceMaxComponentNameLength(value); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + PolicySizeVerifier.enforceMaxComponentNameLength(value); + } } private ComponentNamePolicyValue(Parcel source) { diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java index c0e435c04d3c..156512a90295 100644 --- a/core/java/android/app/admin/DevicePolicyIdentifiers.java +++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java @@ -16,6 +16,8 @@ package android.app.admin; +import static android.app.admin.flags.Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED; + import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; @@ -183,11 +185,13 @@ public final class DevicePolicyIdentifiers { /** * String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}. */ + @FlaggedApi(FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED) public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; /** * String identifier for {@link DevicePolicyManager#setRequiredPasswordComplexity}. */ + @FlaggedApi(FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED) public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity"; /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 0f54cb7bc35e..d31d8f27844a 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -54,8 +54,10 @@ import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; 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_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.onboardingBugreportV2Enabled; import static android.app.admin.flags.Flags.onboardingConsentlessBugreports; import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED; @@ -10476,6 +10478,10 @@ public class DevicePolicyManager { @WorkerThread public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName, Bundle settings) { + if (!Flags.dmrhSetAppRestrictions()) { + throwIfParentInstance("setApplicationRestrictions"); + } + if (mService != null) { try { mService.setApplicationRestrictions(admin, mContext.getPackageName(), packageName, @@ -11880,6 +11886,9 @@ public class DevicePolicyManager { @WorkerThread public @NonNull Bundle getApplicationRestrictions( @Nullable ComponentName admin, String packageName) { + if (!Flags.dmrhSetAppRestrictions()) { + throwIfParentInstance("getApplicationRestrictions"); + } if (mService != null) { try { @@ -14224,11 +14233,21 @@ public class DevicePolicyManager { */ public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) { throwIfParentInstance("getParentProfileInstance"); - UserManager um = mContext.getSystemService(UserManager.class); - if (!um.isManagedProfile()) { - throw new SecurityException("The current user does not have a parent profile."); + try { + if (Flags.dmrhSetAppRestrictions()) { + UserManager um = mContext.getSystemService(UserManager.class); + if (!um.isManagedProfile()) { + throw new SecurityException("The current user does not have a parent profile."); + } + } else { + if (!mService.isManagedProfile(admin)) { + throw new SecurityException("The current user does not have a parent profile."); + } + } + return new DevicePolicyManager(mContext, mService, true); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } - return new DevicePolicyManager(mContext, mService, true); } /** @@ -17790,6 +17809,7 @@ public class DevicePolicyManager { */ @TestApi @RequiresPermission(permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT) + @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED) public void forceSetMaxPolicyStorageLimit(int storageLimit) { if (mService != null) { try { @@ -17807,6 +17827,7 @@ public class DevicePolicyManager { */ @TestApi @RequiresPermission(permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT) + @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED) public int getPolicySizeForAdmin(@NonNull EnforcingAdmin admin) { if (mService != null) { try { @@ -17825,9 +17846,13 @@ public class DevicePolicyManager { * @hide */ @TestApi + @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_PROVISIONING_FIX_ENABLED) @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) @DeviceAdminInfo.HeadlessDeviceOwnerMode public int getHeadlessDeviceOwnerMode() { + if (!Flags.headlessDeviceOwnerProvisioningFixEnabled()) { + return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; + } if (mService != null) { try { return mService.getHeadlessDeviceOwnerMode(mContext.getPackageName()); diff --git a/core/java/android/app/admin/EnforcingAdmin.java b/core/java/android/app/admin/EnforcingAdmin.java index 5f9bb9c22893..f70a53f61671 100644 --- a/core/java/android/app/admin/EnforcingAdmin.java +++ b/core/java/android/app/admin/EnforcingAdmin.java @@ -16,6 +16,9 @@ package android.app.admin; +import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED; + +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -61,6 +64,7 @@ public final class EnforcingAdmin implements Parcelable { * * @hide */ + @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED) @TestApi public EnforcingAdmin( @NonNull String packageName, @NonNull Authority authority, diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java index ab32d46a05ad..68b4ad84d81a 100644 --- a/core/java/android/app/admin/LockTaskPolicy.java +++ b/core/java/android/app/admin/LockTaskPolicy.java @@ -19,6 +19,7 @@ package android.app.admin; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.app.admin.flags.Flags; import android.os.Parcel; import android.os.Parcelable; @@ -134,8 +135,10 @@ public final class LockTaskPolicy extends PolicyValue<LockTaskPolicy> { } private void setPackagesInternal(Set<String> packages) { - for (String p : packages) { - PolicySizeVerifier.enforceMaxPackageNameLength(p); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + for (String p : packages) { + PolicySizeVerifier.enforceMaxPackageNameLength(p); + } } mPackages = new HashSet<>(packages); } diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java index 226c576d9bc3..1a04f6c908bc 100644 --- a/core/java/android/app/admin/PackagePermissionPolicyKey.java +++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java @@ -25,6 +25,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.app.admin.flags.Flags; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -58,8 +59,10 @@ public final class PackagePermissionPolicyKey extends PolicyKey { public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName, @NonNull String permissionName) { super(identifier); - PolicySizeVerifier.enforceMaxPackageNameLength(packageName); - PolicySizeVerifier.enforceMaxStringLength(permissionName, "permissionName"); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + PolicySizeVerifier.enforceMaxPackageNameLength(packageName); + PolicySizeVerifier.enforceMaxStringLength(permissionName, "permissionName"); + } mPackageName = Objects.requireNonNull((packageName)); mPermissionName = Objects.requireNonNull((permissionName)); } diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java index 8fa21dbb0a2e..9e31a23aec91 100644 --- a/core/java/android/app/admin/PackagePolicyKey.java +++ b/core/java/android/app/admin/PackagePolicyKey.java @@ -24,6 +24,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.app.admin.flags.Flags; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -54,7 +55,9 @@ public final class PackagePolicyKey extends PolicyKey { @TestApi public PackagePolicyKey(@NonNull String key, @NonNull String packageName) { super(key); - PolicySizeVerifier.enforceMaxPackageNameLength(packageName); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + PolicySizeVerifier.enforceMaxPackageNameLength(packageName); + } mPackageName = Objects.requireNonNull((packageName)); } diff --git a/core/java/android/app/admin/PackageSetPolicyValue.java b/core/java/android/app/admin/PackageSetPolicyValue.java index 24c50b0994d7..8b253a23a299 100644 --- a/core/java/android/app/admin/PackageSetPolicyValue.java +++ b/core/java/android/app/admin/PackageSetPolicyValue.java @@ -18,6 +18,7 @@ package android.app.admin; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.flags.Flags; import android.os.Parcel; import java.util.HashSet; @@ -31,8 +32,10 @@ public final class PackageSetPolicyValue extends PolicyValue<Set<String>> { public PackageSetPolicyValue(@NonNull Set<String> value) { super(value); - for (String packageName : value) { - PolicySizeVerifier.enforceMaxPackageNameLength(packageName); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + for (String packageName : value) { + PolicySizeVerifier.enforceMaxPackageNameLength(packageName); + } } } diff --git a/core/java/android/app/admin/StringPolicyValue.java b/core/java/android/app/admin/StringPolicyValue.java index bb07c23163ea..6efe9ad0dbed 100644 --- a/core/java/android/app/admin/StringPolicyValue.java +++ b/core/java/android/app/admin/StringPolicyValue.java @@ -18,6 +18,7 @@ package android.app.admin; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.flags.Flags; import android.os.Parcel; import java.util.Objects; @@ -29,7 +30,9 @@ public final class StringPolicyValue extends PolicyValue<String> { public StringPolicyValue(@NonNull String value) { super(value); - PolicySizeVerifier.enforceMaxStringLength(value, "policyValue"); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + PolicySizeVerifier.enforceMaxStringLength(value, "policyValue"); + } } private StringPolicyValue(Parcel source) { diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java index 16cfba4414d5..9054287cb7a0 100644 --- a/core/java/android/app/admin/UserRestrictionPolicyKey.java +++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java @@ -21,6 +21,7 @@ import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY; import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.app.admin.flags.Flags; import android.os.Bundle; import android.os.Parcel; @@ -44,7 +45,9 @@ public final class UserRestrictionPolicyKey extends PolicyKey { @TestApi public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) { super(identifier); - PolicySizeVerifier.enforceMaxStringLength(restriction, "restriction"); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + PolicySizeVerifier.enforceMaxStringLength(restriction, "restriction"); + } mRestriction = Objects.requireNonNull(restriction); } diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index e940a7bb96ad..edbbd5b22ddd 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -4,7 +4,6 @@ package: "android.app.admin.flags" container: "system" -# Fully rolled out and must not be used. flag { name: "policy_engine_migration_v2_enabled" is_exported: true @@ -29,6 +28,16 @@ flag { } flag { + name: "device_policy_size_tracking_internal_bug_fix_enabled" + namespace: "enterprise" + description: "Bug fix for tracking the total policy size and have a max threshold" + bug: "281543351" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "onboarding_bugreport_v2_enabled" is_exported: true namespace: "enterprise" @@ -68,6 +77,13 @@ flag { } flag { + name: "permission_migration_for_zero_trust_impl_enabled" + namespace: "enterprise" + description: "(Implementation) Migrate existing APIs to permission based, and enable DMRH to call them to collect Zero Trust signals." + bug: "289520697" +} + +flag { name: "device_theft_api_enabled" is_exported: true namespace: "enterprise" @@ -210,6 +226,33 @@ flag { } flag { + name: "headless_device_owner_provisioning_fix_enabled" + namespace: "enterprise" + description: "Fix provisioning for single-user headless DO" + bug: "289515470" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "dmrh_set_app_restrictions" + namespace: "enterprise" + description: "Allow DMRH to set application restrictions (both on the profile and the parent)" + bug: "328758346" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "allow_screen_brightness_control_on_cope" + namespace: "enterprise" + description: "Allow COPE admin to control screen brightness and timeout." + bug: "323894620" +} + +flag { name: "always_persist_do" namespace: "enterprise" description: "Always write device_owners2.xml so that migration flags aren't lost" @@ -227,6 +270,16 @@ flag { } flag { + name: "headless_device_owner_delegate_security_logging_bug_fix" + namespace: "enterprise" + description: "Fix delegate security logging for single user headless DO." + bug: "289515470" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "headless_single_user_bad_device_admin_state_fix" namespace: "enterprise" description: "Fix the bad state in DPMS caused by an earlier bug related to the headless single user change" @@ -247,6 +300,16 @@ flag { } flag { + name: "delete_private_space_under_restriction" + namespace: "enterprise" + description: "Delete private space if user restriction is set" + bug: "328758346" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "unmanaged_mode_migration" namespace: "enterprise" description: "Migrate APIs for unmanaged mode" @@ -257,6 +320,16 @@ flag { } flag { + name: "headless_single_user_fixes" + namespace: "enterprise" + description: "Various fixes for headless single user mode" + bug: "289515470" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "backup_connected_apps_settings" namespace: "enterprise" description: "backup and restore connected work and personal apps user settings across devices" diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 82c52a6e8931..b4f2c8b3afd9 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1339,7 +1339,7 @@ public final class Display { public HdrCapabilities getHdrCapabilities() { synchronized (mLock) { updateDisplayInfoLocked(); - if (mDisplayInfo.hdrCapabilities == null) { + if (mDisplayInfo.hdrCapabilities == null || mDisplayInfo.isForceSdr) { return null; } int[] supportedHdrTypes; @@ -1361,6 +1361,7 @@ public final class Display { supportedHdrTypes[index++] = enabledType; } } + return new HdrCapabilities(supportedHdrTypes, mDisplayInfo.hdrCapabilities.mMaxLuminance, mDisplayInfo.hdrCapabilities.mMaxAverageLuminance, diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 157cec8a4d0f..cac3e3c25098 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -230,6 +230,9 @@ public final class DisplayInfo implements Parcelable { /** The formats disabled by user **/ public int[] userDisabledHdrTypes = {}; + /** When true, all HDR capabilities are disabled **/ + public boolean isForceSdr; + /** * Indicates whether the display can be switched into a mode with minimal post * processing. @@ -440,6 +443,7 @@ public final class DisplayInfo implements Parcelable { && colorMode == other.colorMode && Arrays.equals(supportedColorModes, other.supportedColorModes) && Objects.equals(hdrCapabilities, other.hdrCapabilities) + && isForceSdr == other.isForceSdr && Arrays.equals(userDisabledHdrTypes, other.userDisabledHdrTypes) && minimalPostProcessingSupported == other.minimalPostProcessingSupported && logicalDensityDpi == other.logicalDensityDpi @@ -502,6 +506,7 @@ public final class DisplayInfo implements Parcelable { supportedColorModes = Arrays.copyOf( other.supportedColorModes, other.supportedColorModes.length); hdrCapabilities = other.hdrCapabilities; + isForceSdr = other.isForceSdr; userDisabledHdrTypes = other.userDisabledHdrTypes; minimalPostProcessingSupported = other.minimalPostProcessingSupported; logicalDensityDpi = other.logicalDensityDpi; @@ -567,6 +572,7 @@ public final class DisplayInfo implements Parcelable { supportedColorModes[i] = source.readInt(); } hdrCapabilities = source.readParcelable(null, android.view.Display.HdrCapabilities.class); + isForceSdr = source.readBoolean(); minimalPostProcessingSupported = source.readBoolean(); logicalDensityDpi = source.readInt(); physicalXDpi = source.readFloat(); @@ -636,6 +642,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(supportedColorModes[i]); } dest.writeParcelable(hdrCapabilities, flags); + dest.writeBoolean(isForceSdr); dest.writeBoolean(minimalPostProcessingSupported); dest.writeInt(logicalDensityDpi); dest.writeFloat(physicalXDpi); @@ -874,6 +881,8 @@ public final class DisplayInfo implements Parcelable { sb.append(Arrays.toString(appsSupportedModes)); sb.append(", hdrCapabilities "); sb.append(hdrCapabilities); + sb.append(", isForceSdr "); + sb.append(isForceSdr); sb.append(", userDisabledHdrTypes "); sb.append(Arrays.toString(userDisabledHdrTypes)); sb.append(", minimalPostProcessingSupported "); diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java index c7e93c19484f..b80146505a1b 100644 --- a/core/java/android/view/ImeBackAnimationController.java +++ b/core/java/android/view/ImeBackAnimationController.java @@ -149,15 +149,17 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { private void setPreCommitProgress(float progress) { if (isHideAnimationInProgress()) return; + setInterpolatedProgress(BACK_GESTURE.getInterpolation(progress) * PEEK_FRACTION); + } + + private void setInterpolatedProgress(float progress) { if (mWindowInsetsAnimationController != null) { float hiddenY = mWindowInsetsAnimationController.getHiddenStateInsets().bottom; float shownY = mWindowInsetsAnimationController.getShownStateInsets().bottom; float imeHeight = shownY - hiddenY; - float interpolatedProgress = BACK_GESTURE.getInterpolation(progress); - int newY = (int) (imeHeight - interpolatedProgress * (imeHeight * PEEK_FRACTION)); + int newY = (int) (imeHeight - progress * imeHeight); if (mStartRootScrollY != 0) { - mViewRoot.setScrollY( - (int) (mStartRootScrollY * (1 - interpolatedProgress * PEEK_FRACTION))); + mViewRoot.setScrollY((int) (mStartRootScrollY * (1 - progress))); } mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, newY), 1f, progress); @@ -171,21 +173,14 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { return; } mTriggerBack = triggerBack; - int currentBottomInset = mWindowInsetsAnimationController.getCurrentInsets().bottom; - int targetBottomInset; - if (triggerBack) { - targetBottomInset = mWindowInsetsAnimationController.getHiddenStateInsets().bottom; - } else { - targetBottomInset = mWindowInsetsAnimationController.getShownStateInsets().bottom; - } - mPostCommitAnimator = ValueAnimator.ofFloat(currentBottomInset, targetBottomInset); + float targetProgress = triggerBack ? 1f : 0f; + mPostCommitAnimator = ValueAnimator.ofFloat( + BACK_GESTURE.getInterpolation(mLastProgress) * PEEK_FRACTION, targetProgress); mPostCommitAnimator.setInterpolator( triggerBack ? STANDARD_ACCELERATE : EMPHASIZED_DECELERATE); mPostCommitAnimator.addUpdateListener(animation -> { - int bottomInset = (int) ((float) animation.getAnimatedValue()); if (mWindowInsetsAnimationController != null) { - mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, bottomInset), - 1f, animation.getAnimatedFraction()); + setInterpolatedProgress((float) animation.getAnimatedValue()); } else { reset(); } @@ -213,14 +208,8 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { notifyHideIme(); // requesting IME as invisible during post-commit mInsetsController.setRequestedVisibleTypes(0, ime()); - // Changes the animation state. This also notifies RootView of changed insets, which - // causes it to reset its scrollY to 0f (animated) if it was panned mInsetsController.onAnimationStateChanged(ime(), /*running*/ true); } - if (mStartRootScrollY != 0 && !triggerBack) { - // This causes RootView to update its scroll back to the panned position - mInsetsController.getHost().notifyInsetsChanged(); - } } private void notifyHideIme() { @@ -282,6 +271,10 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { return mPostCommitAnimator != null && mTriggerBack; } + boolean isAnimationInProgress() { + return mIsPreCommitAnimationInProgress || mWindowInsetsAnimationController != null; + } + /** * Dump information about this ImeBackAnimationController * diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 6343313b2e01..e90b1c0fc167 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -70,7 +70,14 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { "ImeInsetsSourceConsumer#onAnimationFinished", mController.getHost().getInputMethodManager(), null /* icProto */); } - boolean insetsChanged = super.onAnimationStateChanged(running); + boolean insetsChanged = false; + if (Flags.predictiveBackIme() && !running && isShowRequested() + && mAnimationState == ANIMATION_STATE_HIDE) { + // A user controlled hide animation may have ended in the shown state (e.g. + // cancelled predictive back animation) -> Insets need to be reset to shown. + insetsChanged |= applyLocalVisibilityOverride(); + } + insetsChanged |= super.onAnimationStateChanged(running); if (running && !isShowRequested() && mController.isPredictiveBackImeHideAnimInProgress()) { // IME predictive back animation switched from pre-commit to post-commit. diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 7392751cd3a1..8fdf91a2d87c 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1197,7 +1197,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation pendingRequest.listener, null /* frame */, true /* fromIme */, pendingRequest.mInsetsAnimationSpec, pendingRequest.animationType, pendingRequest.layoutInsetsDuringAnimation, - pendingRequest.useInsetsAnimationThread, statsToken); + pendingRequest.useInsetsAnimationThread, statsToken, + false /* fromPredictiveBack */); } @Override @@ -1333,7 +1334,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // TODO(b/342111149): Create statsToken here once ImeTracker#onStart becomes async. controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, spec, animationType, getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack), - false /* useInsetsAnimationThread */, null); + false /* useInsetsAnimationThread */, null, fromPredictiveBack); } private void controlAnimationUnchecked(@InsetsType int types, @@ -1341,7 +1342,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme, InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, - boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) { + boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken, + boolean fromPredictiveBack) { final boolean visible = layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN; // Basically, we accept the requested visibilities from the upstream callers... @@ -1351,7 +1353,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // rejecting showing IME. controlAnimationUncheckedInner(types, cancellationSignal, listener, frame, fromIme, insetsAnimationSpec, animationType, layoutInsetsDuringAnimation, - useInsetsAnimationThread, statsToken); + useInsetsAnimationThread, statsToken, fromPredictiveBack); // We are finishing setting the requested visible types. Report them to the server // and/or the app. @@ -1363,7 +1365,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme, InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, - boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) { + boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken, + boolean fromPredictiveBack) { if ((types & mTypesBeingCancelled) != 0) { final boolean monitoredAnimation = animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_HIDE; @@ -1449,7 +1452,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } else { Pair<Integer, Boolean> typesReadyPair = collectSourceControls( - fromIme, types, controls, animationType, statsToken); + fromIme, types, controls, animationType, statsToken, fromPredictiveBack); typesReady = typesReadyPair.first; boolean imeReady = typesReadyPair.second; if (DEBUG) { @@ -1585,7 +1588,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation */ private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types, SparseArray<InsetsSourceControl> controls, @AnimationType int animationType, - @Nullable ImeTracker.Token statsToken) { + @Nullable ImeTracker.Token statsToken, boolean fromPredictiveBack) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS); @@ -1597,7 +1600,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation continue; } boolean show = animationType == ANIMATION_TYPE_SHOW - || animationType == ANIMATION_TYPE_USER; + || (animationType == ANIMATION_TYPE_USER + && (!fromPredictiveBack || !mHost.hasAnimationCallbacks())); boolean canRun = true; if (show) { // Show request @@ -1620,7 +1624,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation break; } } else { - consumer.requestHide(fromIme, statsToken); + consumer.requestHide(fromIme + || (fromPredictiveBack && mHost.hasAnimationCallbacks()), statsToken); } if (!canRun) { if (WARN) Log.w(TAG, String.format( @@ -1675,9 +1680,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode( @InsetsType int types, boolean fromPredictiveBack) { - if (fromPredictiveBack) { - // When insets are animated by predictive back, we want insets to be shown to prevent a - // jump cut from shown to hidden at the start of the predictive back animation + if (fromPredictiveBack && !mHost.hasAnimationCallbacks()) { + // When insets are animated by predictive back and the app does not have an animation + // callback, we want insets to be shown to prevent a jump cut from shown to hidden at + // the start of the predictive back animation return LAYOUT_INSETS_DURING_ANIMATION_SHOWN; } // Generally, we want to layout the opposite of the current state. This is to make animation @@ -2024,7 +2030,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation listener /* insetsAnimationSpec */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN, - !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken); + !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken, + false /* fromPredictiveBack */); } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index e81f32e1e64b..523ff38550c1 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -141,7 +141,6 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; -import android.os.Vibrator; import android.service.credentials.CredentialProviderService; import android.sysprop.DisplayProperties; import android.text.InputType; @@ -34156,7 +34155,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * REQUESTED_FRAME_RATE_CATEGORY_NORMAL, REQUESTED_FRAME_RATE_CATEGORY_HIGH. * Keep in mind that the preferred frame rate affects the frame rate for the next frame, * so use this method carefully. It's important to note that the preference is valid as - * long as the View is invalidated. + * long as the View is invalidated. Please also be aware that the requested frame rate + * will not propagate to child views when this API is used on a ViewGroup. * * @param frameRate the preferred frame rate of the view. */ @@ -34175,6 +34175,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE, REQUESTED_FRAME_RATE_CATEGORY_LOW, * REQUESTED_FRAME_RATE_CATEGORY_NORMAL, and REQUESTED_FRAME_RATE_CATEGORY_HIGH. * Note that the frame rate value is valid as long as the View is invalidated. + * Please also be aware that the requested frame rate will not propagate to + * child views when this API is used on a ViewGroup. * * @return REQUESTED_FRAME_RATE_CATEGORY_DEFAULT by default, * or value passed to {@link #setRequestedFrameRate(float)}. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 0e1625aaedd8..f021bdfe478f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -6094,6 +6094,12 @@ public final class ViewRootImpl implements ViewParent, } boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) { + if (mImeBackAnimationController.isAnimationInProgress()) { + // IME predictive back animation is currently in progress which means that scrollY is + // currently controlled by ImeBackAnimationController. + return false; + } + final Rect ci = mAttachInfo.mContentInsets; final Rect vi = mAttachInfo.mVisibleInsets; int scrollY = 0; diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig index defe61e506af..b21a490cc506 100644 --- a/core/java/android/webkit/flags.aconfig +++ b/core/java/android/webkit/flags.aconfig @@ -9,3 +9,12 @@ flag { bug: "319292658" is_fixed_read_only: true } + +flag { + name: "mainline_apis" + is_exported: true + namespace: "webview" + description: "New APIs required by WebViewBootstrap mainline module" + bug: "310653407" + is_fixed_read_only: true +} diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig index 6ce9725f95b0..cd31850b281c 100644 --- a/core/java/android/window/flags/responsible_apis.aconfig +++ b/core/java/android/window/flags/responsible_apis.aconfig @@ -71,3 +71,11 @@ flag { bug: "339720406" } +flag { + name: "bal_reduce_grace_period" + namespace: "responsible_apis" + description: "Changes to reduce or ideally remove the grace period exemption." + bug: "362575865" +} + + diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index b9cc457fecc9..2acda8ad71c1 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -631,21 +631,20 @@ public class ZygoteInit { */ private static Runnable forkSystemServer(String abiList, String socketName, ZygoteServer zygoteServer) { - long capabilities = posixCapabilitiesAsBits( - OsConstants.CAP_IPC_LOCK, - OsConstants.CAP_KILL, - OsConstants.CAP_NET_ADMIN, - OsConstants.CAP_NET_BIND_SERVICE, - OsConstants.CAP_NET_BROADCAST, - OsConstants.CAP_NET_RAW, - OsConstants.CAP_SYS_MODULE, - OsConstants.CAP_SYS_NICE, - OsConstants.CAP_SYS_PTRACE, - OsConstants.CAP_SYS_TIME, - OsConstants.CAP_SYS_TTY_CONFIG, - OsConstants.CAP_WAKE_ALARM, - OsConstants.CAP_BLOCK_SUSPEND - ); + long capabilities = + (1L << OsConstants.CAP_IPC_LOCK) | + (1L << OsConstants.CAP_KILL) | + (1L << OsConstants.CAP_NET_ADMIN) | + (1L << OsConstants.CAP_NET_BIND_SERVICE) | + (1L << OsConstants.CAP_NET_BROADCAST) | + (1L << OsConstants.CAP_NET_RAW) | + (1L << OsConstants.CAP_SYS_MODULE) | + (1L << OsConstants.CAP_SYS_NICE) | + (1L << OsConstants.CAP_SYS_PTRACE) | + (1L << OsConstants.CAP_SYS_TIME) | + (1L << OsConstants.CAP_SYS_TTY_CONFIG) | + (1L << OsConstants.CAP_WAKE_ALARM) | + (1L << OsConstants.CAP_BLOCK_SUSPEND); /* Containers run without some capabilities, so drop any caps that are not available. */ StructCapUserHeader header = new StructCapUserHeader( OsConstants._LINUX_CAPABILITY_VERSION_3, 0); @@ -742,20 +741,6 @@ public class ZygoteInit { } /** - * Gets the bit array representation of the provided list of POSIX capabilities. - */ - private static long posixCapabilitiesAsBits(int... capabilities) { - long result = 0; - for (int capability : capabilities) { - if ((capability < 0) || (capability > OsConstants.CAP_LAST_CAP)) { - throw new IllegalArgumentException(String.valueOf(capability)); - } - result |= (1L << capability); - } - return result; - } - - /** * This is the entry point for a Zygote process. It creates the Zygote server, loads resources, * and handles other tasks related to preparing the process for forking into applications. * diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 2abdd57662eb..90cb10aa62b2 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -108,6 +108,7 @@ cc_library_shared_for_libandroid_runtime { "libtracing_perfetto", "libharfbuzz_ng", "liblog", + "libmediautils", "libminikin", "libz", "server_configurable_flags", diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 638591f130ab..46710b5d3edc 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -17,6 +17,7 @@ //#define LOG_NDEBUG 0 +#include <atomic> #define LOG_TAG "AudioSystem-JNI" #include <android/binder_ibinder_jni.h> #include <android/binder_libbinder.h> @@ -34,15 +35,16 @@ #include <media/AudioContainers.h> #include <media/AudioPolicy.h> #include <media/AudioSystem.h> +#include <mediautils/jthread.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedLocalRef.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/jni_macros.h> #include <system/audio.h> #include <system/audio_policy.h> +#include <sys/system_properties.h> #include <utils/Log.h> -#include <thread> #include <optional> #include <sstream> #include <memory> @@ -57,6 +59,7 @@ #include "android_media_AudioMixerAttributes.h" #include "android_media_AudioProfile.h" #include "android_media_MicrophoneInfo.h" +#include "android_media_JNIUtils.h" #include "android_util_Binder.h" #include "core_jni_helpers.h" @@ -3375,42 +3378,53 @@ static jboolean android_media_AudioSystem_isBluetoothVariableLatencyEnabled(JNIE class JavaSystemPropertyListener { public: JavaSystemPropertyListener(JNIEnv* env, jobject javaCallback, std::string sysPropName) : - mCallback(env->NewGlobalRef(javaCallback)), - mCachedProperty(android::base::CachedProperty{std::move(sysPropName)}) { - mListenerThread = std::thread([this]() mutable { - JNIEnv* threadEnv = GetOrAttachJNIEnvironment(gVm); - while (!mCleanupSignal.load()) { - using namespace std::chrono_literals; - // 1s timeout so this thread can read the cleanup signal to (slowly) be able to - // be destroyed. - std::string newVal = mCachedProperty.WaitForChange(1000ms) ?: ""; - if (newVal != "" && mLastVal != newVal) { - threadEnv->CallVoidMethod(mCallback, gRunnableClassInfo.run); - mLastVal = std::move(newVal); + mCallback {javaCallback, env}, + mPi {__system_property_find(sysPropName.c_str())}, + mListenerThread([this](mediautils::stop_token stok) mutable { + static const struct timespec close_delay = { .tv_sec = 1 }; + while (!stok.stop_requested()) { + uint32_t old_serial = mSerial.load(); + uint32_t new_serial; + if (__system_property_wait(mPi, old_serial, &new_serial, &close_delay)) { + while (new_serial > old_serial) { + if (mSerial.compare_exchange_weak(old_serial, new_serial)) { + fireUpdate(); + break; + } + } + } } + }) {} + + void triggerUpdateIfChanged() { + uint32_t old_serial = mSerial.load(); + uint32_t new_serial = __system_property_serial(mPi); + while (new_serial > old_serial) { + if (mSerial.compare_exchange_weak(old_serial, new_serial)) { + fireUpdate(); + break; } - }); + } } - ~JavaSystemPropertyListener() { - mCleanupSignal.store(true); - mListenerThread.join(); - JNIEnv* env = GetOrAttachJNIEnvironment(gVm); - env->DeleteGlobalRef(mCallback); + private: + void fireUpdate() { + const auto threadEnv = GetOrAttachJNIEnvironment(gVm); + threadEnv->CallVoidMethod(mCallback.get(), gRunnableClassInfo.run); } - private: - jobject mCallback; - android::base::CachedProperty mCachedProperty; - std::thread mListenerThread; - std::atomic<bool> mCleanupSignal{false}; - std::string mLastVal = ""; + // Should outlive thread object + const GlobalRef mCallback; + const prop_info * const mPi; + std::atomic<uint32_t> mSerial = 0; + const mediautils::jthread mListenerThread; }; +// A logical set keyed by address std::vector<std::unique_ptr<JavaSystemPropertyListener>> gSystemPropertyListeners; std::mutex gSysPropLock{}; -static void android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env, jobject thiz, +static jlong android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env, jobject thiz, jstring sysProp, jobject javaCallback) { ScopedUtfChars sysPropChars{env, sysProp}; @@ -3418,6 +3432,19 @@ static void android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env, std::string{sysPropChars.c_str()}); std::unique_lock _l{gSysPropLock}; gSystemPropertyListeners.push_back(std::move(listener)); + return reinterpret_cast<jlong>(gSystemPropertyListeners.back().get()); +} + +static void android_media_AudioSystem_triggerSystemPropertyUpdate(JNIEnv *env, jobject thiz, + jlong nativeHandle) { + std::unique_lock _l{gSysPropLock}; + const auto iter = std::find_if(gSystemPropertyListeners.begin(), gSystemPropertyListeners.end(), + [nativeHandle](const auto& x) { return reinterpret_cast<jlong>(x.get()) == nativeHandle; }); + if (iter != gSystemPropertyListeners.end()) { + (*iter)->triggerUpdateIfChanged(); + } else { + jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid handle"); + } } @@ -3595,8 +3622,11 @@ static const JNINativeMethod gMethods[] = MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled), MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled), MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange", - "(Ljava/lang/String;Ljava/lang/Runnable;)V", + "(Ljava/lang/String;Ljava/lang/Runnable;)J", android_media_AudioSystem_listenForSystemPropertyChange), + MAKE_JNI_NATIVE_METHOD("triggerSystemPropertyUpdate", + "(J)V", + android_media_AudioSystem_triggerSystemPropertyUpdate), }; diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index fba0d81d431f..7ad18b83f0d6 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -17,6 +17,7 @@ #define LOG_TAG "NativeLibraryHelper" //#define LOG_NDEBUG 0 +#include <android-base/properties.h> #include <androidfw/ApkParsing.h> #include <androidfw/ZipFileRO.h> #include <androidfw/ZipUtils.h> @@ -36,6 +37,7 @@ #include <zlib.h> #include <memory> +#include <string> #include "com_android_internal_content_FileSystemUtils.h" #include "core_jni_helpers.h" @@ -125,72 +127,10 @@ sumFiles(JNIEnv*, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char return INSTALL_SUCCEEDED; } -/* - * Copy the native library if needed. - * - * This function assumes the library and path names passed in are considered safe. - */ -static install_status_t -copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName) -{ - static const size_t kPageSize = getpagesize(); - void** args = reinterpret_cast<void**>(arg); - jstring* javaNativeLibPath = (jstring*) args[0]; - jboolean extractNativeLibs = *(jboolean*) args[1]; - jboolean debuggable = *(jboolean*) args[2]; - - ScopedUtfChars nativeLibPath(env, *javaNativeLibPath); - - uint32_t uncompLen; - uint32_t when; - uint32_t crc; - - uint16_t method; - off64_t offset; - uint16_t extraFieldLength; - if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc, - &extraFieldLength)) { - ALOGE("Couldn't read zip entry info\n"); - return INSTALL_FAILED_INVALID_APK; - } - - // Always extract wrap.sh for debuggable, even if extractNativeLibs=false. This makes it - // easier to use wrap.sh because it only works when it is extracted, see - // frameworks/base/services/core/java/com/android/server/am/ProcessList.java. - bool forceExtractCurrentFile = debuggable && strcmp(fileName, "wrap.sh") == 0; - - if (!extractNativeLibs && !forceExtractCurrentFile) { - // check if library is uncompressed and page-aligned - if (method != ZipFileRO::kCompressStored) { - ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n", - fileName); - return INSTALL_FAILED_INVALID_APK; - } - - if (offset % kPageSize != 0) { - ALOGE("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly " - "from apk.\n", fileName, kPageSize); - return INSTALL_FAILED_INVALID_APK; - } - -#ifdef ENABLE_PUNCH_HOLES - // if library is uncompressed, punch hole in it in place - if (!punchHolesInElf64(zipFile->getZipFileName(), offset)) { - ALOGW("Failed to punch uncompressed elf file :%s inside apk : %s at offset: " - "%" PRIu64 "", - fileName, zipFile->getZipFileName(), offset); - } - - // if extra field for this zip file is present with some length, possibility is that it is - // padding added for zip alignment. Punch holes there too. - if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) { - ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName()); - } -#endif // ENABLE_PUNCH_HOLES - - return INSTALL_SUCCEEDED; - } - +static install_status_t extractNativeLibFromApk(ZipFileRO* zipFile, ZipEntryRO zipEntry, + const char* fileName, + const std::string nativeLibPath, uint32_t when, + uint32_t uncompLen, uint32_t crc) { // Build local file path const size_t fileNameLen = strlen(fileName); char localFileName[nativeLibPath.size() + fileNameLen + 2]; @@ -313,6 +253,88 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr } /* + * Copy the native library if needed. + * + * This function assumes the library and path names passed in are considered safe. + */ +static install_status_t copyFileIfChanged(JNIEnv* env, void* arg, ZipFileRO* zipFile, + ZipEntryRO zipEntry, const char* fileName) { + static const size_t kPageSize = getpagesize(); + void** args = reinterpret_cast<void**>(arg); + jstring* javaNativeLibPath = (jstring*)args[0]; + jboolean extractNativeLibs = *(jboolean*)args[1]; + jboolean debuggable = *(jboolean*)args[2]; + jboolean app_compat_16kb = *(jboolean*)args[3]; + install_status_t ret = INSTALL_SUCCEEDED; + + ScopedUtfChars nativeLibPath(env, *javaNativeLibPath); + + uint32_t uncompLen; + uint32_t when; + uint32_t crc; + + uint16_t method; + off64_t offset; + uint16_t extraFieldLength; + if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc, + &extraFieldLength)) { + ALOGE("Couldn't read zip entry info\n"); + return INSTALL_FAILED_INVALID_APK; + } + + // Always extract wrap.sh for debuggable, even if extractNativeLibs=false. This makes it + // easier to use wrap.sh because it only works when it is extracted, see + // frameworks/base/services/core/java/com/android/server/am/ProcessList.java. + bool forceExtractCurrentFile = debuggable && strcmp(fileName, "wrap.sh") == 0; + + if (!extractNativeLibs && !forceExtractCurrentFile) { + // check if library is uncompressed and page-aligned + if (method != ZipFileRO::kCompressStored) { + ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n", + fileName); + return INSTALL_FAILED_INVALID_APK; + } + + if (offset % kPageSize != 0) { + // If the library is zip-aligned correctly for 4kb devices and app compat is + // enabled, on 16kb devices fallback to extraction + if (offset % 0x1000 == 0 && app_compat_16kb) { + ALOGI("16kB AppCompat: Library '%s' is not PAGE(%zu)-aligned - falling back to " + "extraction from apk\n", + fileName, kPageSize); + return extractNativeLibFromApk(zipFile, zipEntry, fileName, nativeLibPath.c_str(), + when, uncompLen, crc); + } + + ALOGE("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly " + "from apk.\n", + fileName, kPageSize); + return INSTALL_FAILED_INVALID_APK; + } + +#ifdef ENABLE_PUNCH_HOLES + // if library is uncompressed, punch hole in it in place + if (!punchHolesInElf64(zipFile->getZipFileName(), offset)) { + ALOGW("Failed to punch uncompressed elf file :%s inside apk : %s at offset: " + "%" PRIu64 "", + fileName, zipFile->getZipFileName(), offset); + } + + // if extra field for this zip file is present with some length, possibility is that it is + // padding added for zip alignment. Punch holes there too. + if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) { + ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName()); + } +#endif // ENABLE_PUNCH_HOLES + + return INSTALL_SUCCEEDED; + } + + return extractNativeLibFromApk(zipFile, zipEntry, fileName, nativeLibPath.c_str(), when, + uncompLen, crc); +} + +/* * An iterator over all shared libraries in a zip file. An entry is * considered to be a shared library if all of the conditions below are * satisfied : @@ -498,12 +520,24 @@ static int findSupportedAbi(JNIEnv* env, jlong apkHandle, jobjectArray supported return status; } +static inline bool app_compat_16kb_enabled() { + static const size_t kPageSize = getpagesize(); + + // App compat is only applicable on 16kb-page-size devices. + if (kPageSize != 0x4000) { + return false; + } + + return android::base::GetBoolProperty("bionic.linker.16kb.app_compat.enabled", false); +} + static jint com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz, jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi, jboolean extractNativeLibs, jboolean debuggable) { - void* args[] = { &javaNativeLibPath, &extractNativeLibs, &debuggable }; + jboolean app_compat_16kb = app_compat_16kb_enabled(); + void* args[] = { &javaNativeLibPath, &extractNativeLibs, &debuggable, &app_compat_16kb }; return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable, copyFileIfChanged, reinterpret_cast<void*>(args)); } diff --git a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java index 8685326a0173..ecd2f76a5160 100644 --- a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java +++ b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java @@ -16,11 +16,8 @@ package android.os.storage; -import android.content.res.ObbInfo; -import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.ProxyFileDescriptorCallback; -import android.os.ServiceManager; import android.system.ErrnoException; import androidx.test.filters.LargeTest; @@ -107,14 +104,7 @@ public class StorageManagerIntegrationTest extends StorageManagerBaseTest { public void testMountBadPackageNameObb() throws Exception { final File file = createObbFile(OBB_FILE_3_BAD_PACKAGENAME, R.raw.obb_file3_bad_packagename); String filePath = file.getAbsolutePath(); - try { - mountObb(filePath, OnObbStateChangeListener.ERROR_PERMISSION_DENIED); - fail("mountObb should have thrown a exception as package name is incorrect"); - } catch (Exception ex) { - assertEquals("Path " + filePath - + " does not contain package name " + mContext.getPackageName(), - ex.getMessage()); - } + mountObb(filePath, OnObbStateChangeListener.ERROR_PERMISSION_DENIED); } /** @@ -164,48 +154,6 @@ public class StorageManagerIntegrationTest extends StorageManagerBaseTest { } } - @LargeTest - public void testObbInfo_withValidObbInfo_success() throws Exception { - final File file = createObbFile(OBB_FILE_1, R.raw.obb_file1); - String filePath = file.getAbsolutePath(); - try { - mountObb(filePath); - unmountObb(filePath, DONT_FORCE); - } catch (Exception ex) { - fail("No exception expected, got " + ex.getMessage()); - } - } - - @LargeTest - public void testObbInfo_withInvalidObbInfo_exception() throws Exception { - final File file = createObbFile(OBB_FILE_1, R.raw.obb_file1); - String rawPath = file.getAbsolutePath(); - String canonicalPath = file.getCanonicalPath(); - - ObbInfo obbInfo = ObbInfo.CREATOR.createFromParcel(Parcel.obtain()); - obbInfo.packageName = "com.android.obbcrash"; - obbInfo.version = 1; - obbInfo.filename = canonicalPath; - - try { - IStorageManager.Stub.asInterface(ServiceManager.getServiceOrThrow("mount")).mountObb( - rawPath, canonicalPath, new ObbActionListener(), 0, obbInfo); - fail("mountObb should have thrown a exception as package name is incorrect"); - } catch (SecurityException ex) { - assertEquals("Path " + canonicalPath - + " does not contain package name " + mContext.getPackageName(), - ex.getMessage()); - } - } - - private static class ObbActionListener extends IObbActionListener.Stub { - @SuppressWarnings("hiding") - @Override - public void onObbResult(String filename, int nonce, int status) { - - } - } - private static class MyThreadFactory implements ThreadFactory { Thread thread = null; diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java index 4d9b591c0990..00ffda867d6a 100644 --- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java +++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java @@ -254,11 +254,8 @@ public class ImeBackAnimationControllerTest { float progress = 0.5f; mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, progress, EDGE_LEFT)); // verify correct ime insets manipulation - float interpolatedProgress = BACK_GESTURE.getInterpolation(progress); - int expectedInset = - (int) (IME_HEIGHT - interpolatedProgress * PEEK_FRACTION * IME_HEIGHT); verify(mWindowInsetsAnimationController, times(1)).setInsetsAndAlpha( - eq(Insets.of(0, 0, 0, expectedInset)), eq(1f), anyFloat()); + eq(Insets.of(0, 0, 0, getImeHeight(progress))), eq(1f), anyFloat()); } @Test @@ -268,12 +265,13 @@ public class ImeBackAnimationControllerTest { WindowInsetsAnimationControlListener animationControlListener = startBackGesture(); // progress back gesture - mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, 0.5f, EDGE_LEFT)); + float progress = 0.5f; + mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, progress, EDGE_LEFT)); // commit back gesture mBackAnimationController.onBackInvoked(); - // verify setInsetsAndAlpha never called due onReady delayed + // verify setInsetsAndAlpha never called due to onReady delayed verify(mWindowInsetsAnimationController, never()).setInsetsAndAlpha(any(), anyInt(), anyFloat()); verify(mInsetsController, never()).setPredictiveBackImeHideAnimInProgress(eq(true)); @@ -283,7 +281,7 @@ public class ImeBackAnimationControllerTest { // verify setInsetsAndAlpha immediately called verify(mWindowInsetsAnimationController, times(1)).setInsetsAndAlpha( - eq(Insets.of(0, 0, 0, IME_HEIGHT)), eq(1f), anyFloat()); + eq(Insets.of(0, 0, 0, getImeHeight(progress))), eq(1f), anyFloat()); // verify post-commit hide anim has started verify(mInsetsController, times(1)).setPredictiveBackImeHideAnimInProgress(eq(true)); }); @@ -319,4 +317,9 @@ public class ImeBackAnimationControllerTest { return animationControlListener.getValue(); } + + private int getImeHeight(float gestureProgress) { + float interpolatedProgress = BACK_GESTURE.getInterpolation(gestureProgress); + return (int) (IME_HEIGHT - interpolatedProgress * PEEK_FRACTION * IME_HEIGHT); + } } diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 22499aeb092c..bec8b1f76394 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -1022,7 +1022,7 @@ public class InsetsControllerTest { } @Test - public void testImeRequestedVisibleDuringPredictiveBackAnim() { + public void testImeRequestedVisibleDuringPredictiveBackAnimWithoutCallback() { prepareControls(); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // show ime as initial state @@ -1051,6 +1051,42 @@ public class InsetsControllerTest { } @Test + public void testImeRequestedInvisibleDuringPredictiveBackAnimWithCallback() { + prepareControls(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // set WindowInsetsAnimationCallback on ViewRoot + mViewRoot.getView().setWindowInsetsAnimationCallback( + new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { + @Override + public WindowInsets onProgress( + @NonNull WindowInsets insets, + @NonNull List<WindowInsetsAnimation> runningAnimations) { + return insets; + } + }); + + // show ime as initial state + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); + mController.cancelExistingAnimations(); // fast forward show animation + assertTrue(mController.getState().peekSource(ID_IME).isVisible()); + + // start control request (for predictive back animation) + WindowInsetsAnimationControlListener listener = + mock(WindowInsetsAnimationControlListener.class); + mController.controlWindowInsetsAnimation(ime(), /*cancellationSignal*/ null, + listener, /*fromIme*/ false, /*duration*/ -1, /*interpolator*/ null, + ANIMATION_TYPE_USER, /*fromPredictiveBack*/ true); + + // Verify that onReady is called (after next predraw) + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + verify(listener).onReady(notNull(), eq(ime())); + + // verify that insets are requested invisible during animation + assertFalse(isRequestedVisible(mController, ime())); + }); + } + + @Test public void testImeShowRequestCancelsPredictiveBackPostCommitAnim() { prepareControls(); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 3e758bbad29b..4622dcffb3cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -349,7 +349,8 @@ public class Bubble implements BubbleViewProvider { getPackageName(), getTitle(), getAppName(), - isImportantConversation()); + isImportantConversation(), + !isAppLaunchIntent()); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java index 829af08e612a..e873cbd6341d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java @@ -48,10 +48,11 @@ public class BubbleInfo implements Parcelable { @Nullable private String mAppName; private boolean mIsImportantConversation; + private boolean mShowAppBadge; public BubbleInfo(String key, int flags, @Nullable String shortcutId, @Nullable Icon icon, int userId, String packageName, @Nullable String title, @Nullable String appName, - boolean isImportantConversation) { + boolean isImportantConversation, boolean showAppBadge) { mKey = key; mFlags = flags; mShortcutId = shortcutId; @@ -61,6 +62,7 @@ public class BubbleInfo implements Parcelable { mTitle = title; mAppName = appName; mIsImportantConversation = isImportantConversation; + mShowAppBadge = showAppBadge; } private BubbleInfo(Parcel source) { @@ -73,6 +75,7 @@ public class BubbleInfo implements Parcelable { mTitle = source.readString(); mAppName = source.readString(); mIsImportantConversation = source.readBoolean(); + mShowAppBadge = source.readBoolean(); } public String getKey() { @@ -115,6 +118,10 @@ public class BubbleInfo implements Parcelable { return mIsImportantConversation; } + public boolean showAppBadge() { + return mShowAppBadge; + } + /** * Whether this bubble is currently being hidden from the stack. */ @@ -172,6 +179,7 @@ public class BubbleInfo implements Parcelable { parcel.writeString(mTitle); parcel.writeString(mAppName); parcel.writeBoolean(mIsImportantConversation); + parcel.writeBoolean(mShowAppBadge); } @NonNull 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 90f8276240a7..4b30ed0dfa7c 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 @@ -734,17 +734,33 @@ class DesktopTasksController( * Quick-resize to the right or left half of the stable bounds. * * @param taskInfo current task that is being snap-resized via dragging or maximize menu button + * @param taskSurface the leash of the task being dragged * @param currentDragBounds current position of the task leash being dragged (or current task * bounds if being snapped resize via maximize menu button) * @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to. */ fun snapToHalfScreen( taskInfo: RunningTaskInfo, + taskSurface: SurfaceControl, currentDragBounds: Rect, position: SnapPosition ) { val destinationBounds = getSnapBounds(taskInfo, position) - if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return + if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) { + // Handle the case where we attempt to snap resize when already snap resized: the task + // position won't need to change but we want to animate the surface going back to the + // snapped position from the "dragged-to-the-edge" position. + if (destinationBounds != currentDragBounds) { + returnToDragStartAnimator.start( + taskInfo.taskId, + taskSurface, + startBounds = currentDragBounds, + endBounds = destinationBounds, + isResizable = taskInfo.isResizeable + ) + } + return + } taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(true) val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds) @@ -774,13 +790,14 @@ class DesktopTasksController( taskInfo.taskId, taskSurface, startBounds = currentDragBounds, - endBounds = dragStartBounds + endBounds = dragStartBounds, + isResizable = taskInfo.isResizeable, ) } else { interactionJankMonitor.begin( taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable" ) - snapToHalfScreen(taskInfo, currentDragBounds, position) + snapToHalfScreen(taskInfo, taskSurface, currentDragBounds, position) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt index 4c5258f2bfcd..f4df42cde10f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt @@ -48,7 +48,13 @@ class ReturnToDragStartAnimator( } /** Builds new animator and starts animation of task leash reposition. */ - fun start(taskId: Int, taskSurface: SurfaceControl, startBounds: Rect, endBounds: Rect) { + fun start( + taskId: Int, + taskSurface: SurfaceControl, + startBounds: Rect, + endBounds: Rect, + isResizable: Boolean + ) { val tx = transactionSupplier.get() boundsAnimator?.cancel() @@ -81,11 +87,13 @@ class ReturnToDragStartAnimator( .apply() taskRepositionAnimationListener.onAnimationEnd(taskId) boundsAnimator = null - Toast.makeText( - context, - R.string.desktop_mode_non_resizable_snap_text, - Toast.LENGTH_SHORT - ).show() + if (!isResizable) { + Toast.makeText( + context, + R.string.desktop_mode_non_resizable_snap_text, + Toast.LENGTH_SHORT + ).show() + } interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE) } ) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java index 8a9302bcfc98..8ebdc96c21a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java @@ -22,6 +22,7 @@ import android.animation.ValueAnimator; import android.annotation.IntDef; import android.content.Context; import android.graphics.Rect; +import android.view.Surface; import android.view.SurfaceControl; import androidx.annotation.NonNull; @@ -51,8 +52,10 @@ public class PipEnterExitAnimator extends ValueAnimator @NonNull private final SurfaceControl mLeash; private final SurfaceControl.Transaction mStartTransaction; - private final int mEnterAnimationDuration; + private final SurfaceControl.Transaction mFinishTransaction; + private final int mEnterExitAnimationDuration; private final @BOUNDS int mDirection; + private final @Surface.Rotation int mRotation; // optional callbacks for tracking animation start and end @Nullable private Runnable mAnimationStartCallback; @@ -62,37 +65,59 @@ public class PipEnterExitAnimator extends ValueAnimator private final Rect mStartBounds = new Rect(); private final Rect mEndBounds = new Rect(); + @Nullable private final Rect mSourceRectHint; + private final Rect mSourceRectHintInsets = new Rect(); + private final Rect mZeroInsets = new Rect(0, 0, 0, 0); + // Bounds updated by the evaluator as animator is running. private final Rect mAnimatedRect = new Rect(); private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; private final RectEvaluator mRectEvaluator; + private final RectEvaluator mInsetEvaluator; private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper; public PipEnterExitAnimator(Context context, @NonNull SurfaceControl leash, SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction, @NonNull Rect baseBounds, @NonNull Rect startBounds, @NonNull Rect endBounds, - @BOUNDS int direction) { + @Nullable Rect sourceRectHint, + @BOUNDS int direction, + @Surface.Rotation int rotation) { mLeash = leash; mStartTransaction = startTransaction; + mFinishTransaction = finishTransaction; mBaseBounds.set(baseBounds); mStartBounds.set(startBounds); mAnimatedRect.set(startBounds); mEndBounds.set(endBounds); mRectEvaluator = new RectEvaluator(mAnimatedRect); + mInsetEvaluator = new RectEvaluator(new Rect()); mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(context); mDirection = direction; + mRotation = rotation; + + mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null; + if (mSourceRectHint != null) { + mSourceRectHintInsets.set( + mSourceRectHint.left - mBaseBounds.left, + mSourceRectHint.top - mBaseBounds.top, + mBaseBounds.right - mSourceRectHint.right, + mBaseBounds.bottom - mSourceRectHint.bottom + ); + } mSurfaceControlTransactionFactory = new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); - mEnterAnimationDuration = context.getResources() + mEnterExitAnimationDuration = context.getResources() .getInteger(R.integer.config_pipEnterAnimationDuration); - setDuration(mEnterAnimationDuration); + setObjectValues(startBounds, endBounds); + setDuration(mEnterExitAnimationDuration); setEvaluator(mRectEvaluator); addListener(this); addUpdateListener(this); @@ -118,6 +143,14 @@ public class PipEnterExitAnimator extends ValueAnimator @Override public void onAnimationEnd(@NonNull Animator animation) { + if (mFinishTransaction != null) { + // finishTransaction might override some state (eg. corner radii) so we want to + // manually set the state to the end of the animation + mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash, mSourceRectHint, + mBaseBounds, mAnimatedRect, getInsets(1f), isInPipDirection(), 1f) + .round(mFinishTransaction, mLeash, isInPipDirection()) + .shadow(mFinishTransaction, mLeash, isInPipDirection()); + } if (mAnimationEndCallback != null) { mAnimationEndCallback.run(); } @@ -127,19 +160,32 @@ public class PipEnterExitAnimator extends ValueAnimator public void onAnimationUpdate(@NonNull ValueAnimator animation) { final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); final float fraction = getAnimatedFraction(); + Rect insets = getInsets(fraction); + // TODO (b/350801661): implement fixed rotation - mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, null, - mBaseBounds, mAnimatedRect, null, isInPipDirection(), fraction) + mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, + mBaseBounds, mAnimatedRect, insets, isInPipDirection(), fraction) .round(tx, mLeash, isInPipDirection()) .shadow(tx, mLeash, isInPipDirection()); tx.apply(); } + private Rect getInsets(float fraction) { + Rect startInsets = isInPipDirection() ? mZeroInsets : mSourceRectHintInsets; + Rect endInsets = isInPipDirection() ? mSourceRectHintInsets : mZeroInsets; + + return mInsetEvaluator.evaluate(fraction, startInsets, endInsets); + } + private boolean isInPipDirection() { return mDirection == BOUNDS_ENTER; } + private boolean isOutPipDirection() { + return mDirection == BOUNDS_EXIT; + } + // no-ops @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index 7f168800fb29..262c14d2bfe3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java @@ -25,6 +25,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.view.SurfaceControl; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.util.Preconditions; @@ -88,6 +89,11 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, : new PictureInPictureParams.Builder().build()); } + @NonNull + public PictureInPictureParams getPictureInPictureParams() { + return mPictureInPictureParams; + } + @Override public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { PictureInPictureParams params = taskInfo.pictureInPictureParams; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 44baabdd5e2e..f93233ec7461 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -36,6 +36,7 @@ import android.content.Context; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; +import android.view.Surface; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -398,17 +399,22 @@ public class PipTransition extends PipTransitionController implements SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition."); + Rect sourceRectHint = null; + if (pipChange.getTaskInfo() != null + && pipChange.getTaskInfo().pictureInPictureParams != null) { + sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint(); + } + PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash, - startTransaction, startBounds, startBounds, endBounds, - PipEnterExitAnimator.BOUNDS_ENTER); + startTransaction, finishTransaction, startBounds, startBounds, endBounds, + sourceRectHint, PipEnterExitAnimator.BOUNDS_ENTER, Surface.ROTATION_0); tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(), this::onClientDrawAtTransitionEnd); finishWct.setBoundsChangeTransaction(pipTaskToken, tx); - animator.setAnimationEndCallback(() -> { - finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct); - }); + animator.setAnimationEndCallback(() -> + finishCallback.onTransitionFinished(finishWct)); animator.start(); return true; @@ -452,19 +458,53 @@ public class PipTransition extends PipTransitionController implements TransitionInfo.Change pipChange = getChangeByToken(info, pipToken); if (pipChange == null) { - return false; + // pipChange is null, check to see if we've reparented the PIP activity for + // the multi activity case. If so we should use the activity leash instead + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getTaskInfo() == null + && change.getLastParent() != null + && change.getLastParent().equals(pipToken)) { + pipChange = change; + break; + } + } + + // failsafe + if (pipChange == null) { + return false; + } + } + + // for multi activity, we need to manually set the leash layer + if (pipChange.getTaskInfo() == null) { + TransitionInfo.Change parent = getChangeByToken(info, pipChange.getParent()); + if (parent != null) { + startTransaction.setLayer(parent.getLeash(), Integer.MAX_VALUE - 1); + } } Rect startBounds = pipChange.getStartAbsBounds(); Rect endBounds = pipChange.getEndAbsBounds(); SurfaceControl pipLeash = pipChange.getLeash(); + Preconditions.checkNotNull(pipLeash, "Leash is null for exit transition."); + + Rect sourceRectHint = null; + if (pipChange.getTaskInfo() != null + && pipChange.getTaskInfo().pictureInPictureParams != null) { + // single activity + sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint(); + } else if (mPipTaskListener.getPictureInPictureParams().hasSourceBoundsHint()) { + // multi activity + sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint(); + } PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash, - startTransaction, startBounds, startBounds, endBounds, - PipEnterExitAnimator.BOUNDS_EXIT); + startTransaction, finishTransaction, endBounds, startBounds, endBounds, + sourceRectHint, PipEnterExitAnimator.BOUNDS_EXIT, Surface.ROTATION_0); + animator.setAnimationEndCallback(() -> { - finishCallback.onTransitionFinished(null); mPipTransitionState.setState(PipTransitionState.EXITED_PIP); + finishCallback.onTransitionFinished(null); }); animator.start(); 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 ac35459347c6..c88c1e28b011 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 @@ -491,7 +491,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else { mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable"); - mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo, + mDesktopTasksController.snapToHalfScreen( + decoration.mTaskInfo, + decoration.mTaskSurface, decoration.mTaskInfo.configuration.windowConfiguration.getBounds(), left ? SnapPosition.LEFT : SnapPosition.RIGHT); } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt index b85d7936efc2..a9ed13a099f3 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt @@ -16,10 +16,14 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.flicker.subject.exceptions.ExceptionMessageBuilder +import android.tools.flicker.subject.exceptions.IncorrectRegionException +import android.tools.flicker.subject.layers.LayerSubject import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.pip.common.EnterPipTransition @@ -29,6 +33,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized +import kotlin.math.abs /** * Test entering pip from an app via auto-enter property when navigating to home. @@ -67,9 +72,24 @@ open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTran override val defaultTeardown: FlickerBuilder.() -> Unit = { teardown { pipApp.exit(wmHelper) } } - @FlakyTest(bugId = 293133362) + private val widthNotSmallerThan: LayerSubject.(LayerSubject) -> Unit = { + val width = visibleRegion.region.bounds.width() + val otherWidth = it.visibleRegion.region.bounds.width() + if (width < otherWidth && abs(width - otherWidth) > EPSILON) { + val errorMsgBuilder = + ExceptionMessageBuilder() + .forSubject(this) + .forIncorrectRegion("width. $width smaller than $otherWidth") + .setExpected(width) + .setActual(otherWidth) + throw IncorrectRegionException(errorMsgBuilder) + } + } + + @Postsubmit @Test override fun pipLayerReduces() { + Assume.assumeFalse(flicker.scenario.isGesturalNavigation) flicker.assertLayers { val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } pipLayerList.zipWithNext { previous, current -> @@ -78,6 +98,32 @@ open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTran } } + /** Checks that [pipApp] window's width is first decreasing then increasing. */ + @Postsubmit + @Test + fun pipLayerWidthDecreasesThenIncreases() { + Assume.assumeTrue(flicker.scenario.isGesturalNavigation) + flicker.assertLayers { + val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } + var previousLayer = pipLayerList[0] + var currentLayer = previousLayer + var i = 0 + invoke("layer area is decreasing") { + if (i < pipLayerList.size - 1) { + previousLayer = currentLayer + currentLayer = pipLayerList[++i] + previousLayer.widthNotSmallerThan(currentLayer) + } + }.then().invoke("layer are is increasing", true /* isOptional */) { + if (i < pipLayerList.size - 1) { + previousLayer = currentLayer + currentLayer = pipLayerList[++i] + currentLayer.widthNotSmallerThan(previousLayer) + } + } + } + } + /** Checks that [pipApp] window is animated towards default position in right bottom corner */ @FlakyTest(bugId = 255578530) @Test @@ -108,4 +154,9 @@ open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTran override fun visibleLayersShownMoreThanOneConsecutiveEntry() { super.visibleLayersShownMoreThanOneConsecutiveEntry() } + + companion object { + // TODO(b/363080056): A margin of error allowed on certain layer size calculations. + const val EPSILON = 1 + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt index 5b22eddcb6ee..6695a1e56567 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt @@ -41,6 +41,7 @@ class BubbleInfoTest : ShellTestCase() { "com.some.package", "title", "Some app", + true, true ) val parcel = Parcel.obtain() 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 5474e539f286..10557dd9b439 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 @@ -123,12 +123,12 @@ import org.mockito.ArgumentMatchers.isA import org.mockito.ArgumentMatchers.isNull import org.mockito.Mock import org.mockito.Mockito -import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock import org.mockito.Mockito.spy import org.mockito.Mockito.verify +import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.eq @@ -2859,7 +2859,7 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun getSnapBounds_calculatesBoundsForResizable() { + fun snapToHalfScreen_getSnapBounds_calculatesBoundsForResizable() { val bounds = Rect(100, 100, 300, 300) val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply { topActivityInfo = ActivityInfo().apply { @@ -2874,13 +2874,45 @@ class DesktopTasksControllerTest : ShellTestCase() { STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom ) - controller.snapToHalfScreen(task, currentDragBounds, SnapPosition.LEFT) + controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT) // Assert bounds set to stable bounds val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds) assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds) } @Test + fun snapToHalfScreen_snapBoundsWhenAlreadySnapped_animatesSurfaceWithoutWCT() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + // Set up task to already be in snapped-left bounds + val bounds = Rect( + STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom + ) + val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply { + topActivityInfo = ActivityInfo().apply { + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE + configuration.windowConfiguration.appBounds = bounds + } + isResizeable = true + } + + // Attempt to snap left again + val currentDragBounds = Rect(bounds).apply { offset(-100, 0) } + controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT) + + // Assert that task is NOT updated via WCT + verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any()) + + // Assert that task leash is updated via Surface Animations + verify(mReturnToDragStartAnimator).start( + eq(task.taskId), + eq(mockSurface), + eq(currentDragBounds), + eq(bounds), + eq(true) + ) + } + + @Test @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) fun handleSnapResizingTask_nonResizable_snapsToHalfScreen() { val task = setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply { @@ -2911,7 +2943,8 @@ class DesktopTasksControllerTest : ShellTestCase() { eq(task.taskId), eq(mockSurface), eq(currentDragBounds), - eq(preDragBounds) + eq(preDragBounds), + eq(false) ) } 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 0b5c6784b73d..be0549b6655d 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 @@ -113,7 +113,7 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times -import org.mockito.Mockito.verify +import org.mockito.kotlin.verify import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argThat @@ -600,6 +600,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test fun testOnDecorSnappedLeft_snapResizes() { + val taskSurfaceCaptor = argumentCaptor<SurfaceControl>() val onLeftSnapClickListenerCaptor = forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>> val decor = createOpenTaskDecoration( @@ -610,8 +611,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds onLeftSnapClickListenerCaptor.value.invoke() - verify(mockDesktopTasksController) - .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT) + verify(mockDesktopTasksController).snapToHalfScreen( + eq(decor.mTaskInfo), + taskSurfaceCaptor.capture(), + eq(currentBounds), + eq(SnapPosition.LEFT) + ) + assertEquals(taskSurfaceCaptor.firstValue, decor.mTaskSurface) } @Test @@ -632,6 +638,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) fun testOnSnapResizeLeft_nonResizable_decorSnappedLeft() { + val taskSurfaceCaptor = argumentCaptor<SurfaceControl>() val onLeftSnapClickListenerCaptor = forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>> val decor = createOpenTaskDecoration( @@ -642,8 +649,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds onLeftSnapClickListenerCaptor.value.invoke() - verify(mockDesktopTasksController) - .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT) + verify(mockDesktopTasksController).snapToHalfScreen( + eq(decor.mTaskInfo), + taskSurfaceCaptor.capture(), + eq(currentBounds), + eq(SnapPosition.LEFT) + ) + assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue) } @Test @@ -660,12 +672,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onLeftSnapClickListenerCaptor.value.invoke() verify(mockDesktopTasksController, never()) - .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT) + .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT)) verify(mockToast).show() } @Test fun testOnDecorSnappedRight_snapResizes() { + val taskSurfaceCaptor = argumentCaptor<SurfaceControl>() val onRightSnapClickListenerCaptor = forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>> val decor = createOpenTaskDecoration( @@ -676,8 +689,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds onRightSnapClickListenerCaptor.value.invoke() - verify(mockDesktopTasksController) - .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT) + verify(mockDesktopTasksController).snapToHalfScreen( + eq(decor.mTaskInfo), + taskSurfaceCaptor.capture(), + eq(currentBounds), + eq(SnapPosition.RIGHT) + ) + assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue) } @Test @@ -698,6 +716,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) fun testOnSnapResizeRight_nonResizable_decorSnappedRight() { + val taskSurfaceCaptor = argumentCaptor<SurfaceControl>() val onRightSnapClickListenerCaptor = forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>> val decor = createOpenTaskDecoration( @@ -708,8 +727,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds onRightSnapClickListenerCaptor.value.invoke() - verify(mockDesktopTasksController) - .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT) + verify(mockDesktopTasksController).snapToHalfScreen( + eq(decor.mTaskInfo), + taskSurfaceCaptor.capture(), + eq(currentBounds), + eq(SnapPosition.RIGHT) + ) + assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue) } @Test @@ -726,7 +750,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onRightSnapClickListenerCaptor.value.invoke() verify(mockDesktopTasksController, never()) - .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT) + .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT)) verify(mockToast).show() } @@ -1033,6 +1057,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { private fun createOpenTaskDecoration( @WindowingMode windowingMode: Int, + taskSurface: SurfaceControl = SurfaceControl(), onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> = forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>, onLeftSnapClickListenerCaptor: ArgumentCaptor<Function0<Unit>> = @@ -1051,7 +1076,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { forClass(View.OnClickListener::class.java) as ArgumentCaptor<View.OnClickListener> ): DesktopModeWindowDecoration { val decor = setUpMockDecorationForTask(createTask(windowingMode = windowingMode)) - onTaskOpening(decor.mTaskInfo) + onTaskOpening(decor.mTaskInfo, taskSurface) verify(decor).setOnMaximizeOrRestoreClickListener(onMaxOrRestoreListenerCaptor.capture()) verify(decor).setOnLeftSnapClickListener(onLeftSnapClickListenerCaptor.capture()) verify(decor).setOnRightSnapClickListener(onRightSnapClickListenerCaptor.capture()) diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index a255f730b0f3..ebdfd3e41aa1 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -2655,7 +2655,16 @@ public class AudioSystem /** * Register a native listener for system property sysprop * @param callback the listener which fires when the property changes + * @return a native handle for use in subsequent methods * @hide */ - public static native void listenForSystemPropertyChange(String sysprop, Runnable callback); + public static native long listenForSystemPropertyChange(String sysprop, Runnable callback); + + /** + * Trigger a sysprop listener update, if the property has been updated: synchronously validating + * there are no pending sysprop changes. + * @param handle the handle returned by {@link listenForSystemPropertyChange} + * @hide + */ + public static native void triggerSystemPropertyUpdate(long handle); } diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index 717e01e18dbd..0f97b2c8d443 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -73,6 +73,7 @@ package android.nfc { method public void onApplyRouting(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public void onBootFinished(int); method public void onBootStarted(); + method public void onCardEmulationActivated(boolean); method public void onDisable(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public void onDisableFinished(int); method public void onDisableStarted(); @@ -81,6 +82,8 @@ package android.nfc { method public void onEnableStarted(); method public void onHceEventReceived(int); method public void onNdefRead(@NonNull java.util.function.Consumer<java.lang.Boolean>); + method public void onRfDiscoveryStarted(boolean); + method public void onRfFieldActivated(boolean); method public void onRoutingChanged(); method public void onStateUpdated(int); method public void onTagConnected(boolean, @NonNull android.nfc.Tag); diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl index c19a44ba0ff1..b65c83773618 100644 --- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl +++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl @@ -37,4 +37,7 @@ interface INfcOemExtensionCallback { void onTagDispatch(in ResultReceiver isSkipped); void onRoutingChanged(); void onHceEventReceived(int action); + void onCardEmulationActivated(boolean isActivated); + void onRfFieldActivated(boolean isActivated); + void onRfDiscoveryStarted(boolean isDiscoveryStarted); } diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java index 6c02edd0eafa..632f693c4fad 100644 --- a/nfc/java/android/nfc/NfcOemExtension.java +++ b/nfc/java/android/nfc/NfcOemExtension.java @@ -32,7 +32,9 @@ import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -40,6 +42,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -58,10 +61,13 @@ public final class NfcOemExtension { private static final int OEM_EXTENSION_RESPONSE_THRESHOLD_MS = 2000; private final NfcAdapter mAdapter; private final NfcOemExtensionCallback mOemNfcExtensionCallback; + private boolean mIsRegistered = false; + private final Map<Callback, Executor> mCallbackMap = new HashMap<>(); private final Context mContext; - private Executor mExecutor = null; - private Callback mCallback = null; private final Object mLock = new Object(); + private boolean mCardEmulationActivated = false; + private boolean mRfFieldActivated = false; + private boolean mRfDiscoveryStarted = false; /** * Event that Host Card Emulation is activated. @@ -215,6 +221,32 @@ public final class NfcOemExtension { * @param action Flag indicating actions to activate, start and stop cpu boost. */ void onHceEventReceived(@HostCardEmulationAction int action); + + /** + * Notifies NFC is activated in listen mode. + * NFC Forum NCI-2.3 ch.5.2.6 specification + * + * <p>NFCC is ready to communicate with a Card reader + * + * @param isActivated true, if card emulation activated, else de-activated. + */ + void onCardEmulationActivated(boolean isActivated); + + /** + * Notifies the Remote NFC Endpoint RF Field is activated. + * NFC Forum NCI-2.3 ch.5.3 specification + * + * @param isActivated true, if RF Field is ON, else RF Field is OFF. + */ + void onRfFieldActivated(boolean isActivated); + + /** + * Notifies the NFC RF discovery is started or in the IDLE state. + * NFC Forum NCI-2.3 ch.5.2 specification + * + * @param isDiscoveryStarted true, if RF discovery started, else RF state is Idle. + */ + void onRfDiscoveryStarted(boolean isDiscoveryStarted); } @@ -229,7 +261,12 @@ public final class NfcOemExtension { /** * Register an {@link Callback} to listen for NFC oem extension callbacks + * Multiple clients can register and callbacks will be invoked asynchronously. + * * <p>The provided callback will be invoked by the given {@link Executor}. + * As part of {@link #registerCallback(Executor, Callback)} the + * {@link Callback} will be invoked with current NFC state + * before the {@link #registerCallback(Executor, Callback)} function completes. * * @param executor an {@link Executor} to execute given callback * @param callback oem implementation of {@link Callback} @@ -239,15 +276,35 @@ public final class NfcOemExtension { public void registerCallback(@NonNull @CallbackExecutor Executor executor, @NonNull Callback callback) { synchronized (mLock) { - if (mCallback != null) { + if (executor == null || callback == null) { + Log.e(TAG, "Executor and Callback must not be null!"); + throw new IllegalArgumentException(); + } + + if (mCallbackMap.containsKey(callback)) { Log.e(TAG, "Callback already registered. Unregister existing callback before" + "registering"); throw new IllegalArgumentException(); } - NfcAdapter.callService(() -> { - NfcAdapter.sService.registerOemExtensionCallback(mOemNfcExtensionCallback); - mCallback = callback; - mExecutor = executor; + mCallbackMap.put(callback, executor); + if (!mIsRegistered) { + NfcAdapter.callService(() -> { + NfcAdapter.sService.registerOemExtensionCallback(mOemNfcExtensionCallback); + mIsRegistered = true; + }); + } else { + updateNfCState(callback, executor); + } + } + } + + private void updateNfCState(Callback callback, Executor executor) { + if (callback != null) { + Log.i(TAG, "updateNfCState"); + executor.execute(() -> { + callback.onCardEmulationActivated(mCardEmulationActivated); + callback.onRfFieldActivated(mRfFieldActivated); + callback.onRfDiscoveryStarted(mRfDiscoveryStarted); }); } } @@ -266,15 +323,19 @@ public final class NfcOemExtension { @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull Callback callback) { synchronized (mLock) { - if (mCallback == null || mCallback != callback) { + if (!mCallbackMap.containsKey(callback) || !mIsRegistered) { Log.e(TAG, "Callback not registered"); throw new IllegalArgumentException(); } - NfcAdapter.callService(() -> { - NfcAdapter.sService.unregisterOemExtensionCallback(mOemNfcExtensionCallback); - mCallback = null; - mExecutor = null; - }); + if (mCallbackMap.size() == 1) { + NfcAdapter.callService(() -> { + NfcAdapter.sService.unregisterOemExtensionCallback(mOemNfcExtensionCallback); + mIsRegistered = false; + mCallbackMap.remove(callback); + }); + } else { + mCallbackMap.remove(callback); + } } } @@ -322,90 +383,133 @@ public final class NfcOemExtension { } private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub { + @Override public void onTagConnected(boolean connected, Tag tag) throws RemoteException { - synchronized (mLock) { - if (mCallback == null || mExecutor == null) { - return; - } - final long identity = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onTagConnected(connected, tag)); - } finally { - Binder.restoreCallingIdentity(identity); - } - } + mCallbackMap.forEach((cb, ex) -> + handleVoid2ArgCallback(connected, tag, cb::onTagConnected, ex)); + } + + @Override + public void onCardEmulationActivated(boolean isActivated) throws RemoteException { + mCardEmulationActivated = isActivated; + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(isActivated, cb::onCardEmulationActivated, ex)); + } + + @Override + public void onRfFieldActivated(boolean isActivated) throws RemoteException { + mRfFieldActivated = isActivated; + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(isActivated, cb::onRfFieldActivated, ex)); + } + + @Override + public void onRfDiscoveryStarted(boolean isDiscoveryStarted) throws RemoteException { + mRfDiscoveryStarted = isDiscoveryStarted; + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(isDiscoveryStarted, cb::onRfDiscoveryStarted, ex)); } + @Override public void onStateUpdated(int state) throws RemoteException { - handleVoidCallback(state, mCallback::onStateUpdated); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(state, cb::onStateUpdated, ex)); } + @Override public void onApplyRouting(ResultReceiver isSkipped) throws RemoteException { - handleVoidCallback( - new ReceiverWrapper(isSkipped), mCallback::onApplyRouting); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback( + new ReceiverWrapper(isSkipped), cb::onApplyRouting, ex)); } @Override public void onNdefRead(ResultReceiver isSkipped) throws RemoteException { - handleVoidCallback( - new ReceiverWrapper(isSkipped), mCallback::onNdefRead); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback( + new ReceiverWrapper(isSkipped), cb::onNdefRead, ex)); } @Override public void onEnable(ResultReceiver isAllowed) throws RemoteException { - handleVoidCallback( - new ReceiverWrapper(isAllowed), mCallback::onEnable); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback( + new ReceiverWrapper(isAllowed), cb::onEnable, ex)); } @Override public void onDisable(ResultReceiver isAllowed) throws RemoteException { - handleVoidCallback( - new ReceiverWrapper(isAllowed), mCallback::onDisable); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback( + new ReceiverWrapper(isAllowed), cb::onDisable, ex)); } @Override public void onBootStarted() throws RemoteException { - handleVoidCallback(null, (Object input) -> mCallback.onBootStarted()); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(null, (Object input) -> cb.onBootStarted(), ex)); } @Override public void onEnableStarted() throws RemoteException { - handleVoidCallback(null, (Object input) -> mCallback.onEnableStarted()); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(null, (Object input) -> cb.onEnableStarted(), ex)); } @Override public void onDisableStarted() throws RemoteException { - handleVoidCallback(null, (Object input) -> mCallback.onDisableStarted()); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(null, (Object input) -> cb.onDisableStarted(), ex)); } @Override public void onBootFinished(int status) throws RemoteException { - handleVoidCallback(status, mCallback::onBootFinished); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(status, cb::onBootFinished, ex)); } @Override public void onEnableFinished(int status) throws RemoteException { - handleVoidCallback(status, mCallback::onEnableFinished); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(status, cb::onEnableFinished, ex)); } @Override public void onDisableFinished(int status) throws RemoteException { - handleVoidCallback(status, mCallback::onDisableFinished); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(status, cb::onDisableFinished, ex)); } @Override public void onTagDispatch(ResultReceiver isSkipped) throws RemoteException { - handleVoidCallback( - new ReceiverWrapper(isSkipped), mCallback::onTagDispatch); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback( + new ReceiverWrapper(isSkipped), cb::onTagDispatch, ex)); } @Override public void onRoutingChanged() throws RemoteException { - handleVoidCallback(null, (Object input) -> mCallback.onRoutingChanged()); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(null, (Object input) -> cb.onRoutingChanged(), ex)); } @Override public void onHceEventReceived(int action) throws RemoteException { - handleVoidCallback(action, mCallback::onHceEventReceived); + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(action, cb::onHceEventReceived, ex)); } - private <T> void handleVoidCallback(T input, Consumer<T> callbackMethod) { + private <T> void handleVoidCallback( + T input, Consumer<T> callbackMethod, Executor executor) { synchronized (mLock) { - if (mCallback == null || mExecutor == null) { - return; + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callbackMethod.accept(input)); + } catch (RuntimeException ex) { + throw ex; + } finally { + Binder.restoreCallingIdentity(identity); } + } + } + + private <T1, T2> void handleVoid2ArgCallback( + T1 input1, T2 input2, BiConsumer<T1, T2> callbackMethod, Executor executor) { + synchronized (mLock) { final long identity = Binder.clearCallingIdentity(); try { - mExecutor.execute(() -> callbackMethod.accept(input)); + executor.execute(() -> callbackMethod.accept(input1, input2)); + } catch (RuntimeException ex) { + throw ex; } finally { Binder.restoreCallingIdentity(identity); } @@ -415,17 +519,12 @@ public final class NfcOemExtension { private <S, T> S handleNonVoidCallbackWithInput( S defaultValue, T input, Function<T, S> callbackMethod) throws RemoteException { synchronized (mLock) { - if (mCallback == null) { - return defaultValue; - } final long identity = Binder.clearCallingIdentity(); S result = defaultValue; try { ExecutorService executor = Executors.newSingleThreadExecutor(); - FutureTask<S> futureTask = new FutureTask<>( - () -> callbackMethod.apply(input) - ); - executor.submit(futureTask); + FutureTask<S> futureTask = new FutureTask<>(() -> callbackMethod.apply(input)); + var unused = executor.submit(futureTask); try { result = futureTask.get( OEM_EXTENSION_RESPONSE_THRESHOLD_MS, TimeUnit.MILLISECONDS); @@ -447,17 +546,12 @@ public final class NfcOemExtension { private <T> T handleNonVoidCallbackWithoutInput(T defaultValue, Supplier<T> callbackMethod) throws RemoteException { synchronized (mLock) { - if (mCallback == null) { - return defaultValue; - } final long identity = Binder.clearCallingIdentity(); T result = defaultValue; try { ExecutorService executor = Executors.newSingleThreadExecutor(); - FutureTask<T> futureTask = new FutureTask<>( - callbackMethod::get - ); - executor.submit(futureTask); + FutureTask<T> futureTask = new FutureTask<>(callbackMethod::get); + var unused = executor.submit(futureTask); try { result = futureTask.get( OEM_EXTENSION_RESPONSE_THRESHOLD_MS, TimeUnit.MILLISECONDS); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index d3949769cd58..157af7d06fed 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -92,6 +92,7 @@ <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.MASTER_CLEAR" /> <uses-permission android:name="android.permission.VIBRATE" /> + <uses-permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS" /> <uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" /> <uses-permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" /> <uses-permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" /> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 97206de346c5..1ce171609e5b 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -606,16 +606,6 @@ flag { } flag { - name: "screenshot_private_profile_behavior_fix" - namespace: "systemui" - description: "Private profile support for screenshots" - bug: "327613051" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "screenshot_save_image_exporter" namespace: "systemui" description: "Save all screenshots using ImageExporter" diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index fe9105ed195e..5f6ea1c0eb43 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -131,6 +131,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { fakeFeatureFlags, mSelectedUserInteractor, keyguardKeyboardInteractor, + null, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index 0054d137bd2c..2af3b00ed95a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -117,7 +117,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener, mEmergencyButtonController, mFalsingCollector, featureFlags, - mSelectedUserInteractor, keyguardKeyboardInteractor) { + mSelectedUserInteractor, keyguardKeyboardInteractor, null) { @Override public void onResume(int reason) { super.onResume(reason); diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 281047ad89a0..fabc357c2a68 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -18,8 +18,10 @@ package com.android.keyguard import android.app.admin.DevicePolicyManager +import android.app.admin.flags.Flags as DevicePolicyFlags import android.content.res.Configuration import android.media.AudioManager +import android.platform.test.annotations.EnableFlags import android.telephony.TelephonyManager import android.testing.TestableLooper.RunWithLooper import android.testing.TestableResources @@ -237,6 +239,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { featureFlags, mSelectedUserInteractor, keyguardKeyboardInteractor, + null, ) kosmos = testKosmos() @@ -937,6 +940,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { } @Test + @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES) fun showAlmostAtWipeDialog_calledOnMainUser_setsCorrectUserType() { val mainUserId = 10 @@ -953,6 +957,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { } @Test + @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES) fun showAlmostAtWipeDialog_calledOnNonMainUser_setsCorrectUserType() { val secondaryUserId = 10 val mainUserId = 0 diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 080b48af2af1..0c5e726e17aa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -17,6 +17,8 @@ package com.android.systemui.authentication.domain.interactor import android.app.admin.DevicePolicyManager +import android.app.admin.flags.Flags as DevicePolicyFlags +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils @@ -412,6 +414,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES) fun upcomingWipe() = testScope.runTest { val upcomingWipe by collectLastValue(underTest.upcomingWipe) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt index c1615253804c..8c8faee99139 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt @@ -19,9 +19,11 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.pm.UserInfo import android.hardware.biometrics.BiometricFaceConstants import android.hardware.fingerprint.FingerprintManager +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository @@ -35,7 +37,6 @@ import com.android.systemui.biometrics.data.repository.fakeFacePropertyRepositor import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.bouncer.domain.interactor.bouncerInteractor -import com.android.systemui.bouncer.shared.flag.fakeComposeBouncerFlags import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus @@ -71,6 +72,7 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) +@EnableFlags(Flags.FLAG_COMPOSE_BOUNCER) class BouncerMessageViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope @@ -82,7 +84,6 @@ class BouncerMessageViewModelTest : SysuiTestCase() { @Before fun setUp() { kosmos.fakeUserRepository.setUserInfos(listOf(PRIMARY_USER)) - kosmos.fakeComposeBouncerFlags.composeBouncerEnabled = true overrideResource( R.array.config_face_acquire_device_entry_ignorelist, intArrayOf(ignoreHelpMessageId) 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 99fcbb854e3a..777ddab4e259 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 @@ -68,15 +68,16 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.fakeUserTracker +import com.android.systemui.statusbar.phone.fakeManagedProfileController import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever +import com.android.systemui.utils.leaks.FakeManagedProfileController import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -122,6 +123,7 @@ class CommunalInteractorTest : SysuiTestCase() { private lateinit var userTracker: FakeUserTracker private lateinit var activityStarter: ActivityStarter private lateinit var userManager: UserManager + private lateinit var managedProfileController: FakeManagedProfileController private lateinit var underTest: CommunalInteractor @@ -143,6 +145,7 @@ class CommunalInteractorTest : SysuiTestCase() { userTracker = kosmos.fakeUserTracker activityStarter = kosmos.activityStarter userManager = kosmos.userManager + managedProfileController = kosmos.fakeManagedProfileController whenever(mainUser.isMain).thenReturn(true) whenever(secondaryUser.isMain).thenReturn(false) @@ -1070,6 +1073,14 @@ class CommunalInteractorTest : SysuiTestCase() { } } + @Test + fun unpauseWorkProfileEnablesWorkMode() = + testScope.runTest { + underTest.unpauseWorkProfile() + + assertThat(managedProfileController.isWorkModeEnabled()).isTrue() + } + private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id))) .thenReturn(disabledFlags) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 3e1f4f6da5e4..3b2b12c4363d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -360,6 +360,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test + @DisableSceneContainer fun alpha_transitionBetweenHubAndDream_isZero() = testScope.runTest { val alpha by collectLastValue(underTest.alpha(viewState)) @@ -388,8 +389,8 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = Scenes.Communal, - emptyFlow(), - emptyFlow(), + flowOf(Scenes.Communal), + flowOf(0.5f), false, emptyFlow() ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt index 5fd3a242e195..a18f450cdc9e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt @@ -209,7 +209,7 @@ class ModesTileDataInteractorTest : SysuiTestCase() { private companion object { val TEST_USER = UserHandle.of(1)!! - val MODES_DRAWABLE_ID = com.android.systemui.res.R.drawable.qs_dnd_icon_off + val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes val MODES_DRAWABLE = TestStubDrawable("modes_icon") val BEDTIME_DRAWABLE = TestStubDrawable("bedtime") diff --git a/packages/SystemUI/src/com/android/keyguard/AuthInteractionProperties.kt b/packages/SystemUI/src/com/android/keyguard/AuthInteractionProperties.kt new file mode 100644 index 000000000000..efa13c632087 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/AuthInteractionProperties.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.keyguard + +import android.os.VibrationAttributes +import com.google.android.msdl.domain.InteractionProperties + +/** + * This class represents the set of [InteractionProperties] that only hold [VibrationAttributes] for + * the case of user authentication. + */ +data class AuthInteractionProperties( + override val vibrationAttributes: VibrationAttributes = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_COMMUNICATION_REQUEST) +) : InteractionProperties diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java index dad440083f70..64ccbe1a3f5a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -19,7 +19,9 @@ package com.android.keyguard; import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT; +import static com.android.systemui.Flags.msdlFeedback; +import android.annotation.Nullable; import android.content.res.ColorStateList; import android.os.AsyncTask; import android.os.CountDownTimer; @@ -40,6 +42,9 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.google.android.msdl.data.model.MSDLToken; +import com.google.android.msdl.domain.MSDLPlayer; + import java.util.HashMap; import java.util.Map; @@ -55,6 +60,8 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey protected AsyncTask<?, ?, ?> mPendingLockCheck; protected boolean mResumed; protected boolean mLockedOut; + @Nullable + protected MSDLPlayer mMSDLPlayer; private final KeyDownListener mKeyDownListener = (keyCode, keyEvent) -> { // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN. @@ -81,7 +88,8 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, - FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) { + FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, + @Nullable MSDLPlayer msdlPlayer) { super(view, securityMode, keyguardSecurityCallback, emergencyButtonController, messageAreaControllerFactory, featureFlags, selectedUserInteractor); mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -89,6 +97,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey mLatencyTracker = latencyTracker; mFalsingCollector = falsingCollector; mEmergencyButtonController = emergencyButtonController; + mMSDLPlayer = msdlPlayer; } abstract void resetState(); @@ -178,6 +187,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) { boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId; if (matched) { + playAuthenticationHaptics(/* unlock= */true); getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0); if (dismissKeyguard) { mDismissing = true; @@ -185,6 +195,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey getKeyguardSecurityCallback().dismiss(true, userId, getSecurityMode()); } } else { + playAuthenticationHaptics(/* unlock= */false); mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */); if (isValidPassword) { getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs); @@ -201,6 +212,18 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey } } + private void playAuthenticationHaptics(boolean unlock) { + if (!msdlFeedback() || mMSDLPlayer == null) return; + + MSDLToken token; + if (unlock) { + token = MSDLToken.UNLOCK; + } else { + token = MSDLToken.FAILURE; + } + mMSDLPlayer.playToken(token, mAuthInteractionProperties); + } + protected void startErrorAnimation() { /* no-op */ } protected void verifyPasswordAndUnlock() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index db14a0f67fca..45fdbc6bc888 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -45,6 +45,9 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; +import com.google.android.msdl.domain.InteractionProperties; +import com.google.android.msdl.domain.MSDLPlayer; + import javax.inject.Inject; /** Controller for a {@link KeyguardSecurityView}. */ @@ -63,6 +66,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {}; private final FeatureFlags mFeatureFlags; protected final SelectedUserInteractor mSelectedUserInteractor; + protected final InteractionProperties mAuthInteractionProperties = + new AuthInteractionProperties(); protected KeyguardInputViewController(T view, SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback, @@ -214,6 +219,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final SelectedUserInteractor mSelectedUserInteractor; private final UiEventLogger mUiEventLogger; private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor; + private final MSDLPlayer mMSDLPlayer; @Inject public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -228,7 +234,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> KeyguardViewController keyguardViewController, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger, - KeyguardKeyboardInteractor keyguardKeyboardInteractor) { + KeyguardKeyboardInteractor keyguardKeyboardInteractor, + MSDLPlayer msdlPlayer) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -246,6 +253,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mSelectedUserInteractor = selectedUserInteractor; mUiEventLogger = uiEventLogger; mKeyguardKeyboardInteractor = keyguardKeyboardInteractor; + mMSDLPlayer = msdlPlayer; } /** Create a new {@link KeyguardInputViewController}. */ @@ -268,14 +276,14 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mInputMethodManager, emergencyButtonController, mMainExecutor, mResources, mFalsingCollector, mKeyguardViewController, mDevicePostureController, mFeatureFlags, mSelectedUserInteractor, - mKeyguardKeyboardInteractor); + mKeyguardKeyboardInteractor, mMSDLPlayer); } else if (keyguardInputView instanceof KeyguardPINView) { return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, emergencyButtonController, mFalsingCollector, mDevicePostureController, mFeatureFlags, mSelectedUserInteractor, - mUiEventLogger, mKeyguardKeyboardInteractor + mUiEventLogger, mKeyguardKeyboardInteractor, mMSDLPlayer ); } else if (keyguardInputView instanceof KeyguardSimPinView) { return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, @@ -283,14 +291,14 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, mFalsingCollector, emergencyButtonController, mFeatureFlags, mSelectedUserInteractor, - mKeyguardKeyboardInteractor); + mKeyguardKeyboardInteractor, mMSDLPlayer); } else if (keyguardInputView instanceof KeyguardSimPukView) { return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, mFalsingCollector, emergencyButtonController, mFeatureFlags, mSelectedUserInteractor, - mKeyguardKeyboardInteractor + mKeyguardKeyboardInteractor, mMSDLPlayer ); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 3ad73bc17704..6983a06bb0e6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -19,6 +19,7 @@ package com.android.keyguard; import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; @@ -55,6 +56,8 @@ import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.concurrency.DelayableExecutor; +import com.google.android.msdl.domain.MSDLPlayer; + import java.util.List; public class KeyguardPasswordViewController @@ -134,10 +137,11 @@ public class KeyguardPasswordViewController DevicePostureController postureController, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, - KeyguardKeyboardInteractor keyguardKeyboardInteractor) { + KeyguardKeyboardInteractor keyguardKeyboardInteractor, + @Nullable MSDLPlayer msdlPlayer) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, falsingCollector, - emergencyButtonController, featureFlags, selectedUserInteractor); + emergencyButtonController, featureFlags, selectedUserInteractor, msdlPlayer); mKeyguardSecurityCallback = keyguardSecurityCallback; mInputMethodManager = inputMethodManager; mPostureController = postureController; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index 0f61233ac64f..dd7c3e4f8a3a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -16,9 +16,11 @@ package com.android.keyguard; +import static com.android.systemui.Flags.msdlFeedback; import static com.android.systemui.Flags.pinInputFieldStyledFocusState; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import android.annotation.Nullable; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.StateListDrawable; @@ -40,6 +42,9 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.google.android.msdl.data.model.MSDLToken; +import com.google.android.msdl.domain.MSDLPlayer; + public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView> extends KeyguardAbsKeyInputViewController<T> { @@ -77,10 +82,11 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB FalsingCollector falsingCollector, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, - KeyguardKeyboardInteractor keyguardKeyboardInteractor) { + KeyguardKeyboardInteractor keyguardKeyboardInteractor, + @Nullable MSDLPlayer msdlPlayer) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, falsingCollector, - emergencyButtonController, featureFlags, selectedUserInteractor); + emergencyButtonController, featureFlags, selectedUserInteractor, msdlPlayer); mLiftToActivateListener = liftToActivateListener; mFalsingCollector = falsingCollector; mKeyguardKeyboardInteractor = keyguardKeyboardInteractor; @@ -102,12 +108,22 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB return false; }); button.setAnimationEnabled(showAnimations); + button.setMSDLPlayer(mMSDLPlayer); } mPasswordEntry.setOnKeyListener(mOnKeyListener); mPasswordEntry.setUserActivityListener(this::onUserInput); View deleteButton = mView.findViewById(R.id.delete_button); - deleteButton.setOnTouchListener(mActionButtonTouchListener); + if (msdlFeedback()) { + deleteButton.setOnTouchListener((View view, MotionEvent event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mMSDLPlayer != null) { + mMSDLPlayer.playToken(MSDLToken.KEYPRESS_DELETE, null); + } + return false; + }); + } else { + deleteButton.setOnTouchListener(mActionButtonTouchListener); + } deleteButton.setOnClickListener(v -> { // check for time-based lockouts if (mPasswordEntry.isEnabled()) { @@ -119,13 +135,19 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB if (mPasswordEntry.isEnabled()) { mView.resetPasswordText(true /* animate */, true /* announce */); } - mView.doHapticKeyClick(); + if (msdlFeedback() && mMSDLPlayer != null) { + mMSDLPlayer.playToken(MSDLToken.LONG_PRESS, null); + } else { + mView.doHapticKeyClick(); + } return true; }); View okButton = mView.findViewById(R.id.key_enter); if (okButton != null) { - okButton.setOnTouchListener(mActionButtonTouchListener); + if (!msdlFeedback()) { + okButton.setOnTouchListener(mActionButtonTouchListener); + } okButton.setOnClickListener(v -> { if (mPasswordEntry.isEnabled()) { verifyPasswordAndUnlock(); @@ -177,6 +199,7 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB for (NumPadKey button : mView.getButtons()) { button.setOnTouchListener(null); + button.setMSDLPlayer(null); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index f4cda0204036..7fc038f98a85 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -18,6 +18,7 @@ package com.android.keyguard; import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; +import android.annotation.Nullable; import android.view.View; import com.android.internal.logging.UiEvent; @@ -33,6 +34,8 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.google.android.msdl.domain.MSDLPlayer; + public class KeyguardPinViewController extends KeyguardPinBasedInputViewController<KeyguardPINView> { private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -61,11 +64,12 @@ public class KeyguardPinViewController FalsingCollector falsingCollector, DevicePostureController postureController, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger, - KeyguardKeyboardInteractor keyguardKeyboardInteractor) { + KeyguardKeyboardInteractor keyguardKeyboardInteractor, + @Nullable MSDLPlayer msdlPlayer) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, - keyguardKeyboardInteractor); + keyguardKeyboardInteractor, msdlPlayer); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mPostureController = postureController; mLockPatternUtils = lockPatternUtils; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 2d28a189f84d..61f9800c351b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -35,6 +35,7 @@ import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; +import android.app.admin.flags.Flags; import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -1139,7 +1140,12 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard int remainingBeforeWipe, int failedAttempts) { int userType = USER_TYPE_PRIMARY; if (expiringUserId == userId) { - int primaryUser = mainUserId != null ? mainUserId : UserHandle.USER_SYSTEM; + int primaryUser = UserHandle.USER_SYSTEM; + if (Flags.headlessSingleUserFixes()) { + if (mainUserId != null) { + primaryUser = mainUserId; + } + } // TODO: http://b/23522538 if (expiringUserId != primaryUser) { userType = USER_TYPE_SECONDARY_USER; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 3ef3418bfed4..ce5b5d76332d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -21,6 +21,7 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.Dialog; @@ -48,6 +49,8 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.google.android.msdl.domain.MSDLPlayer; + public class KeyguardSimPinViewController extends KeyguardPinBasedInputViewController<KeyguardSimPinView> { public static final String TAG = "KeyguardSimPinView"; @@ -95,11 +98,12 @@ public class KeyguardSimPinViewController TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, - KeyguardKeyboardInteractor keyguardKeyboardInteractor) { + KeyguardKeyboardInteractor keyguardKeyboardInteractor, + @Nullable MSDLPlayer msdlPlayer) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, - keyguardKeyboardInteractor); + keyguardKeyboardInteractor, msdlPlayer); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 46225c7ea58a..86b29b2aa7d4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -17,6 +17,7 @@ package com.android.keyguard; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -43,6 +44,8 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.google.android.msdl.domain.MSDLPlayer; + public class KeyguardSimPukViewController extends KeyguardPinBasedInputViewController<KeyguardSimPukView> { private static final boolean DEBUG = KeyguardConstants.DEBUG; @@ -92,11 +95,12 @@ public class KeyguardSimPukViewController TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, - KeyguardKeyboardInteractor keyguardKeyboardInteractor) { + KeyguardKeyboardInteractor keyguardKeyboardInteractor, + @Nullable MSDLPlayer msdlPlayer) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, - keyguardKeyboardInteractor); + keyguardKeyboardInteractor, msdlPlayer); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index dcfa775dcabf..4fb80de2d4ec 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -15,6 +15,7 @@ */ package com.android.keyguard; +import static com.android.systemui.Flags.msdlFeedback; import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY; import android.content.Context; @@ -38,6 +39,9 @@ import androidx.annotation.Nullable; import com.android.settingslib.Utils; import com.android.systemui.res.R; +import com.google.android.msdl.data.model.MSDLToken; +import com.google.android.msdl.domain.MSDLPlayer; + /** * Viewgroup for the bouncer numpad button, specifically for digits. */ @@ -57,6 +61,8 @@ public class NumPadKey extends ViewGroup implements NumPadAnimationListener { @Nullable private NumPadAnimator mAnimator; private int mOrientation; + @Nullable + private MSDLPlayer mMSDLPlayer; private View.OnClickListener mListener = new View.OnClickListener() { @Override @@ -221,8 +227,12 @@ public class NumPadKey extends ViewGroup implements NumPadAnimationListener { // Cause a VIRTUAL_KEY vibration public void doHapticKeyClick() { - performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + if (msdlFeedback() && mMSDLPlayer != null) { + mMSDLPlayer.playToken(MSDLToken.KEYPRESS_STANDARD, null); + } else { + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } } @Override @@ -244,4 +254,8 @@ public class NumPadKey extends ViewGroup implements NumPadAnimationListener { super.onInitializeAccessibilityNodeInfo(info); info.setTextEntryKey(true); } + + public void setMSDLPlayer(@Nullable MSDLPlayer player) { + mMSDLPlayer = player; + } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java index 394f8dd629ae..04afd8693e04 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java @@ -408,6 +408,10 @@ public class FullscreenMagnificationController implements ComponentCallbacks { if (!isActivated()) { return; } + if (!(mFullscreenBorder.getBackground() instanceof GradientDrawable)) { + // Wear doesn't use the same magnification border background. So early return here. + return; + } float cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext); GradientDrawable backgroundDrawable = (GradientDrawable) mFullscreenBorder.getBackground(); diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index 468737d9372f..732a90d2c01d 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -32,11 +32,11 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim import com.android.systemui.authentication.shared.model.AuthenticationResultModel +import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.onSubscriberAdded @@ -254,7 +254,7 @@ constructor( override val hasLockoutOccurred: StateFlow<Boolean> = _hasLockoutOccurred.asStateFlow() init { - if (SceneContainerFlag.isEnabled) { + if (ComposeBouncerFlags.isComposeBouncerOrSceneContainerEnabled()) { // Hydrate failedAuthenticationAttempts initially and whenever the selected user // changes. applicationScope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 3080e1978b2a..fcba425f0956 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.authentication.domain.interactor +import android.app.admin.flags.Flags import android.os.UserHandle import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternView @@ -288,7 +289,12 @@ constructor( private suspend fun getWipeTarget(): WipeTarget { // Check which profile has the strictest policy for failed authentication attempts. val userToBeWiped = repository.getProfileWithMinFailedUnlockAttemptsForWipe() - val primaryUser = selectedUserInteractor.getMainUserId() ?: UserHandle.USER_SYSTEM + val primaryUser = + if (Flags.headlessSingleUserFixes()) { + selectedUserInteractor.getMainUserId() ?: UserHandle.USER_SYSTEM + } else { + UserHandle.USER_SYSTEM + } return when (userToBeWiped) { selectedUserInteractor.getSelectedUserId() -> if (userToBeWiped == primaryUser) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 25d43d972fe2..4c2fe07f92bb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -941,16 +941,16 @@ constructor( private fun vibrateOnSuccess() { _hapticsToPlay.value = HapticsToPlay( - HapticFeedbackConstants.CONFIRM, - HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, + HapticFeedbackConstants.BIOMETRIC_CONFIRM, + null, ) } private fun vibrateOnError() { _hapticsToPlay.value = HapticsToPlay( - HapticFeedbackConstants.REJECT, - HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, + HapticFeedbackConstants.BIOMETRIC_REJECT, + null, ) } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt index 62ef365345b7..a1111f68f1ee 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt @@ -17,18 +17,17 @@ package com.android.systemui.bouncer.shared.flag import com.android.systemui.Flags -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.flag.SceneContainerFlag -import dagger.Module -import dagger.Provides -interface ComposeBouncerFlags { +object ComposeBouncerFlags { /** * Returns `true` if the Compose bouncer is enabled or if the scene container framework is * enabled; `false` otherwise. */ - fun isComposeBouncerOrSceneContainerEnabled(): Boolean + fun isComposeBouncerOrSceneContainerEnabled(): Boolean { + return SceneContainerFlag.isEnabled || Flags.composeBouncer() + } /** * Returns `true` if only compose bouncer is enabled and scene container framework is not @@ -39,30 +38,7 @@ interface ComposeBouncerFlags { "that includes compose bouncer in legacy keyguard.", replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()") ) - fun isOnlyComposeBouncerEnabled(): Boolean -} - -class ComposeBouncerFlagsImpl() : ComposeBouncerFlags { - - override fun isComposeBouncerOrSceneContainerEnabled(): Boolean { - return SceneContainerFlag.isEnabled || Flags.composeBouncer() - } - - @Deprecated( - "Avoid using this, this is meant to be used only by the glue code " + - "that includes compose bouncer in legacy keyguard.", - replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()") - ) - override fun isOnlyComposeBouncerEnabled(): Boolean { + fun isOnlyComposeBouncerEnabled(): Boolean { return !SceneContainerFlag.isEnabled && Flags.composeBouncer() } } - -@Module -object ComposeBouncerFlagsModule { - @Provides - @SysUISingleton - fun impl(): ComposeBouncerFlags { - return ComposeBouncerFlagsImpl() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt index ad93a25f39a5..cc8dce7938aa 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt @@ -55,12 +55,11 @@ constructor( class BouncerViewBinder @Inject constructor( - private val composeBouncerFlags: ComposeBouncerFlags, private val legacyBouncerDependencies: Lazy<LegacyBouncerDependencies>, private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>, ) { fun bind(view: ViewGroup) { - if (composeBouncerFlags.isOnlyComposeBouncerEnabled()) { + if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) { val deps = composeBouncerDependencies.get() ComposeBouncerViewBinder.bind( view, 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 102ae7abb3e2..c4bbd9cf0d9f 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 @@ -8,14 +8,12 @@ import androidx.compose.ui.platform.ComposeView import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.compose.theme.PlatformTheme import com.android.keyguard.ViewMediatorCallback import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.ui.BouncerDialogFactory -import com.android.systemui.bouncer.ui.composable.BouncerContent +import com.android.systemui.bouncer.ui.composable.BouncerContainer import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel -import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.user.domain.interactor.SelectedUserInteractor import kotlinx.coroutines.flow.collectLatest @@ -49,16 +47,7 @@ object ComposeBouncerViewBinder { this@repeatWhenAttached.lifecycle } ) - setContent { - PlatformTheme { - BouncerContent( - rememberViewModel("ComposeBouncerViewBinder") { - viewModelFactory.create() - }, - dialogFactory, - ) - } - } + setContent { BouncerContainer(viewModelFactory, dialogFactory) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt new file mode 100644 index 000000000000..c05dcd5cea83 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt @@ -0,0 +1,54 @@ +/* + * 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.bouncer.ui.composable + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.compose.theme.PlatformTheme +import com.android.systemui.bouncer.ui.BouncerDialogFactory +import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel +import com.android.systemui.compose.modifiers.sysuiResTag +import com.android.systemui.lifecycle.rememberViewModel + +/** Container that includes the compose bouncer and is meant to be included in legacy keyguard. */ +@Composable +fun BouncerContainer( + viewModelFactory: BouncerSceneContentViewModel.Factory, + dialogFactory: BouncerDialogFactory, +) { + PlatformTheme { + val backgroundColor = MaterialTheme.colorScheme.surface + + val bouncerViewModel = rememberViewModel("BouncerContainer") { viewModelFactory.create() } + Box { + Canvas(Modifier.fillMaxSize()) { drawRect(color = backgroundColor) } + + // Separate the bouncer content into a reusable composable that + // doesn't have any SceneScope + // dependencies + BouncerContent( + bouncerViewModel, + dialogFactory, + Modifier.sysuiResTag(Bouncer.TestTags.Root).fillMaxSize() + ) + } + } +} 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 e54dc7dbdebb..c383b8d5f95c 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 @@ -78,7 +78,6 @@ constructor( private val faceAuthInteractor: DeviceEntryFaceAuthInteractor, private val deviceUnlockedInteractor: DeviceUnlockedInteractor, private val deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor, - private val flags: ComposeBouncerFlags, ) : ExclusiveActivatable() { /** * A message shown when the user has attempted the wrong credential too many times and now must @@ -96,7 +95,7 @@ constructor( val message: MutableStateFlow<MessageViewModel?> = MutableStateFlow(null) override suspend fun onActivated(): Nothing { - if (!flags.isComposeBouncerOrSceneContainerEnabled()) { + if (!ComposeBouncerFlags.isComposeBouncerOrSceneContainerEnabled()) { return awaitCancellation() } 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 adc4bc9a14f3..0aada06a7eb7 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 @@ -29,7 +29,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.authentication.shared.model.AuthenticationWipeModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor -import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text @@ -57,7 +56,6 @@ constructor( private val authenticationInteractor: AuthenticationInteractor, private val devicePolicyManager: DevicePolicyManager, private val bouncerMessageViewModelFactory: BouncerMessageViewModel.Factory, - private val flags: ComposeBouncerFlags, private val userSwitcher: UserSwitcherViewModel, private val actionButtonInteractor: BouncerActionButtonInteractor, private val pinViewModelFactory: PinBouncerViewModel.Factory, 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 9b96341bdd8e..b570e14c646a 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 @@ -61,6 +61,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.phone.ManagedProfileController import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.emitOnStart @@ -116,6 +117,7 @@ constructor( sceneInteractor: SceneInteractor, @CommunalLog logBuffer: LogBuffer, @CommunalTableLog tableLogBuffer: TableLogBuffer, + private val managedProfileController: ManagedProfileController ) { private val logger = Logger(logBuffer, "CommunalInteractor") @@ -401,12 +403,7 @@ constructor( /** Request to unpause work profile that is currently in quiet mode. */ fun unpauseWorkProfile() { - userTracker.userProfiles - .find { it.isManagedProfile } - ?.userHandle - ?.let { userHandle -> - userManager.requestQuietModeEnabled(/* enableQuietMode */ false, userHandle) - } + managedProfileController.setWorkModeEnabled(true) } /** Returns true if work profile is in quiet mode (disabled) for user handle. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 8c82900810be..d38c9520eb7a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -3568,12 +3568,16 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } return; } - try { - mStatusBarService.disableForUser(flags, mStatusBarDisableToken, - mContext.getPackageName(), - mSelectedUserInteractor.getSelectedUserId()); - } catch (RemoteException e) { - Log.d(TAG, "Failed to set disable flags: " + flags, e); + + // Handled in StatusBarDisableFlagsInteractor. + if (!KeyguardWmStateRefactor.isEnabled()) { + try { + mStatusBarService.disableForUser(flags, mStatusBarDisableToken, + mContext.getPackageName(), + mSelectedUserInteractor.getSelectedUserId()); + } catch (RemoteException e) { + Log.d(TAG, "Failed to set disable flags: " + flags, e); + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index a7a832148130..7899971484f1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -369,8 +369,7 @@ object KeyguardRootViewBinder { } else { vibratorHelper.performHapticFeedback( view, - HapticFeedbackConstants.CONFIRM, - HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, + HapticFeedbackConstants.BIOMETRIC_CONFIRM, ) } } @@ -390,8 +389,7 @@ object KeyguardRootViewBinder { } else { vibratorHelper.performHapticFeedback( view, - HapticFeedbackConstants.REJECT, - HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, + HapticFeedbackConstants.BIOMETRIC_REJECT, ) } } 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 ebdcaa0c91a6..eaa61a113ee6 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 @@ -34,20 +34,18 @@ 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.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.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.ui.viewmodel.NotificationShadeWindowModel import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScreenOffAnimationController -import com.android.systemui.util.kotlin.BooleanFlowOperators.any import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample @@ -86,6 +84,7 @@ constructor( private val communalInteractor: CommunalInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor, + notificationShadeWindowModel: NotificationShadeWindowModel, private val alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel, private val alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel, @@ -197,37 +196,18 @@ constructor( .distinctUntilChanged() /** - * Keyguard states which should fully hide the keyguard. - * - * Note: [GONE] is not included as it is handled separately. - */ - private val hiddenKeyguardStates = listOf(OCCLUDED, DREAMING, GLANCEABLE_HUB) - - /** * Keyguard should not show if fully transitioned into a hidden keyguard state or if * transitioning between hidden states. */ private val hideKeyguard: Flow<Boolean> = - (hiddenKeyguardStates.map { state -> - keyguardTransitionInteractor - .transitionValue(state) - .map { it == 1f } - .onStart { emit(false) } - } + - listOf( - communalInteractor.isIdleOnCommunal, - keyguardTransitionInteractor - .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE) - .map { it == 1f } - .onStart { emit(false) }, - keyguardTransitionInteractor - .isInTransitionWhere( - fromStatePredicate = { hiddenKeyguardStates.contains(it) }, - toStatePredicate = { hiddenKeyguardStates.contains(it) }, - ) - .onStart { emit(false) }, - )) - .any() + anyOf( + notificationShadeWindowModel.isKeyguardOccluded, + communalInteractor.isIdleOnCommunal, + keyguardTransitionInteractor + .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE) + .map { it == 1f } + .onStart { emit(false) }, + ) /** Last point that the root view was tapped */ val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt index 6d63d26d4300..313cb30d84ff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.os.Handler import android.os.Looper import android.service.quicksettings.Tile +import androidx.annotation.DrawableRes import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope import androidx.lifecycle.repeatOnLifecycle @@ -98,7 +99,7 @@ constructor( override fun newTileState(): QSTile.State { return QSTile.State().apply { label = mContext.getString(R.string.quick_settings_modes_label) - icon = ResourceIcon.get(R.drawable.qs_dnd_icon_off) + icon = ResourceIcon.get(ICON_RES_ID) state = Tile.STATE_INACTIVE } } @@ -116,7 +117,7 @@ constructor( state?.apply { this.state = tileState.activationState.legacyState val tileStateIcon = tileState.icon() - icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(R.drawable.qs_dnd_icon_off) + icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID) label = tileLabel secondaryLabel = tileState.secondaryLabel contentDescription = tileState.contentDescription @@ -127,5 +128,6 @@ constructor( companion object { const val TILE_SPEC = "dnd" + @DrawableRes val ICON_RES_ID = com.android.internal.R.drawable.ic_zen_priority_modes } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt index 664951d199a7..6173091b3b99 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt @@ -55,7 +55,7 @@ constructor( fun tileData() = zenModeInteractor.activeModes .map { activeModes -> - val modesIconResId = R.drawable.qs_dnd_icon_off + val modesIconResId = com.android.internal.R.drawable.ic_zen_priority_modes if (usesModeIcons()) { val mainModeDrawable = activeModes.mainMode?.icon?.drawable diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index fe5cbb18f046..000781acec58 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -894,11 +894,21 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis return; } mHandler.removeCallbacks(mConnectionRunnable); + + // Avoid creating TouchInteractionService because the System user in HSUM mode does not + // interact with UI elements + UserHandle currentUser = UserHandle.of(mUserTracker.getUserId()); + if (UserManager.isHeadlessSystemUserMode() && currentUser.isSystem()) { + Log.w(TAG_OPS, + "Skipping connection to TouchInteractionService for the System user in HSUM " + + "mode."); + return; + } try { mBound = mContext.bindServiceAsUser(mQuickStepIntent, mOverviewServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, - UserHandle.of(mUserTracker.getUserId())); + currentUser); } catch (SecurityException e) { Log.e(TAG_OPS, "Unable to bind because of security error", e); } diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 86b242782308..8fc896c961f7 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -17,7 +17,6 @@ package com.android.systemui.scene import com.android.systemui.CoreStartable -import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlagsModule import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule import com.android.systemui.scene.domain.SceneDomainModule import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor @@ -44,7 +43,6 @@ import dagger.multibindings.IntoMap [ BouncerSceneModule::class, CommunalSceneModule::class, - ComposeBouncerFlagsModule::class, EmptySceneModule::class, GoneSceneModule::class, LockscreenSceneModule::class, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt index 474afa8bcb9d..56afb79c40d4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt @@ -9,7 +9,6 @@ import android.view.ViewGroup.MarginLayoutParams import android.view.ViewTreeObserver import android.view.animation.AccelerateDecelerateInterpolator import androidx.constraintlayout.widget.Guideline -import com.android.systemui.Flags.screenshotPrivateProfileBehaviorFix import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.res.R import com.android.systemui.screenshot.message.ProfileMessageController @@ -49,44 +48,19 @@ constructor( } fun onScreenshotTaken(screenshot: ScreenshotData) { - if (screenshotPrivateProfileBehaviorFix()) { - mainScope.launch { - val profileData = profileMessageController.onScreenshotTaken(screenshot.userHandle) - var notifiedApps: List<CharSequence> = - screenshotDetectionController.maybeNotifyOfScreenshot(screenshot) - - // If profile first run needs to show, bias towards that, otherwise show screenshot - // detection notification if needed. - if (profileData != null) { - workProfileFirstRunView.visibility = View.VISIBLE - detectionNoticeView.visibility = View.GONE - profileMessageController.bindView(workProfileFirstRunView, profileData) { - animateOutMessageContainer() - } - animateInMessageContainer() - } else if (notifiedApps.isNotEmpty()) { - detectionNoticeView.visibility = View.VISIBLE - workProfileFirstRunView.visibility = View.GONE - screenshotDetectionController.populateView(detectionNoticeView, notifiedApps) - animateInMessageContainer() - } - } - } else { - val workProfileData = - workProfileMessageController.onScreenshotTaken(screenshot.userHandle) + mainScope.launch { + val profileData = profileMessageController.onScreenshotTaken(screenshot.userHandle) var notifiedApps: List<CharSequence> = screenshotDetectionController.maybeNotifyOfScreenshot(screenshot) - // If work profile first run needs to show, bias towards that, otherwise show screenshot + // If profile first run needs to show, bias towards that, otherwise show screenshot // detection notification if needed. - if (workProfileData != null) { + if (profileData != null) { workProfileFirstRunView.visibility = View.VISIBLE detectionNoticeView.visibility = View.GONE - workProfileMessageController.populateView( - workProfileFirstRunView, - workProfileData, - this::animateOutMessageContainer - ) + profileMessageController.bindView(workProfileFirstRunView, profileData) { + animateOutMessageContainer() + } animateInMessageContainer() } else if (notifiedApps.isNotEmpty()) { detectionNoticeView.visibility = View.VISIBLE diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt deleted file mode 100644 index 922997d08c25..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot - -import android.util.Log -import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE - -/** Implementation of [ScreenshotRequestProcessor] */ -class RequestProcessor( - private val capture: ImageCapture, - private val policy: ScreenshotPolicy, -) : ScreenshotRequestProcessor { - - override suspend fun process(screenshot: ScreenshotData): ScreenshotData { - var result = screenshot - - // Apply work profile screenshots policy: - // - // If the focused app belongs to a work profile, transforms a full screen - // (or partial) screenshot request to a task snapshot (provided image) screenshot. - - // Whenever displayContentInfo is fetched, the topComponent is also populated - // regardless of the managed profile status. - - if (screenshot.type != TAKE_SCREENSHOT_PROVIDED_IMAGE) { - val info = policy.findPrimaryContent(screenshot.displayId) - Log.d(TAG, "findPrimaryContent: $info") - result.taskId = info.taskId - result.topComponent = info.component - result.userHandle = info.user - - if (policy.isManagedProfile(info.user.identifier)) { - val image = - capture.captureTask(info.taskId) - ?: throw RequestProcessorException("Task snapshot returned a null Bitmap!") - - // Provide the task snapshot as the screenshot - result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE - result.bitmap = image - result.screenBounds = info.bounds - } - } - - return result - } -} - -private const val TAG = "RequestProcessor" diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt index 3ad4075a2b89..ee1008d26414 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt @@ -27,5 +27,5 @@ fun interface ScreenshotRequestProcessor { suspend fun process(original: ScreenshotData): ScreenshotData } -/** Exception thrown by [RequestProcessor] if something goes wrong. */ +/** Exception thrown by [ScreenshotRequestProcessor] if something goes wrong. */ class RequestProcessorException(message: String) : IllegalStateException(message) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt index 44f767aa321e..2cb9fe7f1a9d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt @@ -19,14 +19,11 @@ package com.android.systemui.screenshot.policy import android.content.ComponentName import android.content.Context import android.os.Process -import com.android.systemui.Flags.screenshotPrivateProfileBehaviorFix import com.android.systemui.SystemUIService import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.screenshot.ImageCapture -import com.android.systemui.screenshot.RequestProcessor -import com.android.systemui.screenshot.ScreenshotPolicy import com.android.systemui.screenshot.ScreenshotRequestProcessor import com.android.systemui.screenshot.data.repository.DisplayContentRepository import com.android.systemui.screenshot.data.repository.DisplayContentRepositoryImpl @@ -68,23 +65,18 @@ interface ScreenshotPolicyModule { @Application context: Context, @Background background: CoroutineDispatcher, imageCapture: ImageCapture, - policyProvider: Provider<ScreenshotPolicy>, - displayContentRepoProvider: Provider<DisplayContentRepository>, + displayContentRepo: DisplayContentRepository, policyListProvider: Provider<List<CapturePolicy>>, ): ScreenshotRequestProcessor { - return if (screenshotPrivateProfileBehaviorFix()) { - PolicyRequestProcessor( - background = background, - capture = imageCapture, - displayTasks = displayContentRepoProvider.get(), - policies = policyListProvider.get(), - defaultOwner = Process.myUserHandle(), - defaultComponent = - ComponentName(context.packageName, SystemUIService::class.java.toString()) - ) - } else { - RequestProcessor(imageCapture, policyProvider.get()) - } + return PolicyRequestProcessor( + background = background, + capture = imageCapture, + displayTasks = displayContentRepo, + policies = policyListProvider.get(), + defaultOwner = Process.myUserHandle(), + defaultComponent = + ComponentName(context.packageName, SystemUIService::class.java.toString()) + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java index 56ea00cf0954..7ef1e416aa19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java @@ -22,6 +22,7 @@ import android.os.UserManager; import androidx.annotation.NonNull; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; @@ -43,17 +44,20 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { private final UserManager mUserManager; private final UserTracker mUserTracker; private final LinkedList<UserInfo> mProfiles; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private boolean mListening; private int mCurrentUser; @Inject public ManagedProfileControllerImpl(Context context, @Main Executor mainExecutor, - UserTracker userTracker, UserManager userManager) { + UserTracker userTracker, UserManager userManager, + KeyguardUpdateMonitor keyguardUpdateMonitor) { mContext = context; mMainExecutor = mainExecutor; mUserManager = userManager; mUserTracker = userTracker; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; mProfiles = new LinkedList<>(); } @@ -80,6 +84,7 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { StatusBarManager statusBarManager = (StatusBarManager) mContext .getSystemService(android.app.Service.STATUS_BAR_SERVICE); statusBarManager.collapsePanels(); + mKeyguardUpdateMonitor.awakenFromDream(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt index 21ec14fc7f03..591d7af44db1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt @@ -403,7 +403,7 @@ interface PolicyModule { tileSpec = TileSpec.create(DND_TILE_SPEC), uiConfig = QSTileUIConfig.Resource( - iconRes = R.drawable.qs_dnd_icon_off, + iconRes = com.android.internal.R.drawable.ic_zen_priority_modes, labelRes = R.string.quick_settings_modes_label, ), instanceId = uiEventLogger.getNewInstanceId(), diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java index e724c60bfc54..487432eb5886 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -27,6 +29,7 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.os.SystemClock; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.testing.TestableLooper.RunWithLooper; import android.view.KeyEvent; @@ -43,9 +46,13 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.haptics.msdl.FakeMSDLPlayer; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.google.android.msdl.data.model.MSDLToken; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -86,6 +93,8 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { @Mock private SelectedUserInteractor mSelectedUserInteractor; private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController; + private KosmosJavaAdapter mKosmosJavaAdapter = new KosmosJavaAdapter(this); + private final FakeMSDLPlayer mMSDLPlayer = mKosmosJavaAdapter.getMsdlPlayer(); @Before public void setup() { @@ -108,7 +117,7 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { return new KeyguardAbsKeyInputViewController(mAbsKeyInputView, mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector, - mEmergencyButtonController, mFeatureFlags, mSelectedUserInteractor) { + mEmergencyButtonController, mFeatureFlags, mSelectedUserInteractor, mMSDLPlayer) { @Override void resetState() { } @@ -197,4 +206,32 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { verify(mAbsKeyInputView, never()).setPasswordEntryInputEnabled(true); verify(mAbsKeyInputView, never()).setPasswordEntryEnabled(true); } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + public void onPasswordChecked_withMSDLFeedback_withMatch_playsUnlockToken() { + mKeyguardAbsKeyInputViewController.onPasswordChecked(0, true, 100, true); + assertThat(mMSDLPlayer.getLatestTokenPlayed()).isEqualTo(MSDLToken.UNLOCK); + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) + public void onPasswordChecked_withoutMSDLFeedback_withMatch_doesNotPlayToken() { + mKeyguardAbsKeyInputViewController.onPasswordChecked(0, true, 100, true); + assertThat(mMSDLPlayer.getLatestTokenPlayed()).isNull(); + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + public void onPasswordChecked_withMSDLFeedback_withoutMatch_playsFailureToken() { + mKeyguardAbsKeyInputViewController.onPasswordChecked(0, false, 100, true); + assertThat(mMSDLPlayer.getLatestTokenPlayed()).isEqualTo(MSDLToken.FAILURE); + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) + public void onPasswordChecked_withoutMSDLFeedback_withoutMatch_doesNotPlayToken() { + mKeyguardAbsKeyInputViewController.onPasswordChecked(0, false, 100, true); + assertThat(mMSDLPlayer.getLatestTokenPlayed()).isNull(); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 36d4d122ab11..c43a1849d2a7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -149,7 +149,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { featureFlags, mSelectedUserInteractor, uiEventLogger, - keyguardKeyboardInteractor + keyguardKeyboardInteractor, + null, ) } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index 7151c429acf9..ea6c1bc5b31c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt @@ -101,7 +101,8 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { emergencyButtonController, fakeFeatureFlags, mSelectedUserInteractor, - keyguardKeyboardInteractor + keyguardKeyboardInteractor, + null, ) underTest.init() underTest.onViewAttached() diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt index acae913459b3..c26365d00376 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt @@ -96,7 +96,8 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() { emergencyButtonController, fakeFeatureFlags, mSelectedUserInteractor, - keyguardKeyboardInteractor + keyguardKeyboardInteractor, + null, ) underTest.init() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 6047e7d1bf79..4fc41669b2c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -717,13 +717,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(confirmHaptics?.hapticFeedbackConstant) .isEqualTo( if (expectConfirmation) HapticFeedbackConstants.NO_HAPTICS - else HapticFeedbackConstants.CONFIRM - ) - assertThat(confirmHaptics?.flag) - .isEqualTo( - if (expectConfirmation) null - else HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING + else HapticFeedbackConstants.BIOMETRIC_CONFIRM ) + assertThat(confirmHaptics?.flag).isNull() if (expectConfirmation) { kosmos.promptViewModel.confirmAuthenticated() @@ -731,9 +727,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val confirmedHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) assertThat(confirmedHaptics?.hapticFeedbackConstant) - .isEqualTo(HapticFeedbackConstants.CONFIRM) - assertThat(confirmedHaptics?.flag) - .isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) + .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) + assertThat(confirmedHaptics?.flag).isNull() } @Test @@ -747,9 +742,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) assertThat(currentHaptics?.hapticFeedbackConstant) - .isEqualTo(HapticFeedbackConstants.CONFIRM) - assertThat(currentHaptics?.flag) - .isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) + .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) + assertThat(currentHaptics?.flag).isNull() } @Test @@ -757,9 +751,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa kosmos.promptViewModel.showTemporaryError("test", "messageAfterError", false) val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(currentHaptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.REJECT) - assertThat(currentHaptics?.flag) - .isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) + assertThat(currentHaptics?.hapticFeedbackConstant) + .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) + assertThat(currentHaptics?.flag).isNull() } // biometricprompt_sfps_fingerprint_authenticating reused across rotations @@ -870,8 +864,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa ) val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.REJECT) - assertThat(haptics?.flag).isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) + assertThat(haptics?.hapticFeedbackConstant) + .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) + assertThat(haptics?.flag).isNull() } @Test @@ -901,10 +896,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) if (expectConfirmation) { - assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.REJECT) - assertThat(haptics?.flag).isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) + assertThat(haptics?.hapticFeedbackConstant) + .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) + assertThat(haptics?.flag).isNull() } else { - assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.CONFIRM) + assertThat(haptics?.hapticFeedbackConstant) + .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt index 8435b1cb71dc..d2dcf4d38d0c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.qs.tiles -import android.graphics.drawable.TestStubDrawable import android.os.Handler import android.platform.test.annotations.EnableFlags import android.service.quicksettings.Tile @@ -93,12 +92,7 @@ class ModesTileTest : SysuiTestCase() { ModesTileDataInteractor(context, kosmos.zenModeInteractor, testDispatcher) private val mapper = ModesTileMapper( - context.orCreateTestableResources - .apply { - addOverride(R.drawable.qs_dnd_icon_on, TestStubDrawable()) - addOverride(R.drawable.qs_dnd_icon_off, TestStubDrawable()) - } - .resources, + context.resources, context.theme, ) @@ -122,7 +116,7 @@ class ModesTileTest : SysuiTestCase() { QSTileConfigTestBuilder.build { uiConfig = QSTileUIConfig.Resource( - iconRes = R.drawable.qs_dnd_icon_off, + iconRes = ModesTile.ICON_RES_ID, labelRes = R.string.quick_settings_modes_label, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt index b31d21e7f8b2..15da77dfe33d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt @@ -2,8 +2,6 @@ package com.android.systemui.screenshot import android.graphics.drawable.Drawable import android.os.UserHandle -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import android.view.View import android.view.ViewGroup import android.widget.FrameLayout @@ -90,20 +88,6 @@ class MessageContainerControllerTest : SysuiTestCase() { } @Test - @DisableFlags(com.android.systemui.Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX) - fun testOnScreenshotTakenUserHandle_withWorkProfileFirstRun() { - whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle))) - .thenReturn(workProfileData) - messageContainer.onScreenshotTaken(screenshotData) - - verify(workProfileMessageController) - .populateView(eq(workProfileFirstRunView), eq(workProfileData), any()) - assertEquals(View.VISIBLE, workProfileFirstRunView.visibility) - assertEquals(View.GONE, detectionNoticeView.visibility) - } - - @Test - @EnableFlags(com.android.systemui.Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX) fun testOnScreenshotTakenUserHandle_withProfileProfileFirstRun() = runTest { val profileData = ProfileMessageController.ProfileFirstRunData( diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt deleted file mode 100644 index 0847f01f12d3..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot - -import android.content.ComponentName -import android.graphics.Bitmap -import android.graphics.ColorSpace -import android.graphics.Insets -import android.graphics.Rect -import android.hardware.HardwareBuffer -import android.os.UserHandle -import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER -import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER -import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN -import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE -import com.android.internal.util.ScreenshotRequest -import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.runBlocking -import org.junit.Assert -import org.junit.Test - -private const val USER_ID = 1 -private const val TASK_ID = 1 - -class RequestProcessorTest { - private val imageCapture = FakeImageCapture() - private val component = ComponentName("android.test", "android.test.Component") - private val bounds = Rect(25, 25, 75, 75) - - private val policy = FakeScreenshotPolicy() - - @Test - fun testFullScreenshot() = runBlocking { - // Indicate that the primary content belongs to a normal user - policy.setManagedProfile(USER_ID, false) - policy.setDisplayContentInfo( - policy.getDefaultDisplayId(), - DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID) - ) - - val request = - ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build() - val processor = RequestProcessor(imageCapture, policy) - - val processedData = processor.process(ScreenshotData.fromRequest(request)) - - // Request has topComponent added, but otherwise unchanged. - assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN) - assertThat(processedData.source).isEqualTo(SCREENSHOT_OTHER) - assertThat(processedData.topComponent).isEqualTo(component) - } - - @Test - fun testFullScreenshot_managedProfile() = runBlocking { - // Provide a fake task bitmap when asked - val bitmap = makeHardwareBitmap(100, 100) - imageCapture.image = bitmap - - // Indicate that the primary content belongs to a manged profile - policy.setManagedProfile(USER_ID, true) - policy.setDisplayContentInfo( - policy.getDefaultDisplayId(), - DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID) - ) - - val request = - ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build() - val processor = RequestProcessor(imageCapture, policy) - - val processedData = processor.process(ScreenshotData.fromRequest(request)) - - // Expect a task snapshot is taken, overriding the full screen mode - assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE) - assertThat(processedData.bitmap).isEqualTo(bitmap) - assertThat(processedData.screenBounds).isEqualTo(bounds) - assertThat(processedData.insets).isEqualTo(Insets.NONE) - assertThat(processedData.taskId).isEqualTo(TASK_ID) - assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID) - } - - @Test - fun testFullScreenshot_managedProfile_nullBitmap() { - // Provide a null task bitmap when asked - imageCapture.image = null - - // Indicate that the primary content belongs to a manged profile - policy.setManagedProfile(USER_ID, true) - policy.setDisplayContentInfo( - policy.getDefaultDisplayId(), - DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID) - ) - - val request = - ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build() - val processor = RequestProcessor(imageCapture, policy) - - Assert.assertThrows(IllegalStateException::class.java) { - runBlocking { processor.process(ScreenshotData.fromRequest(request)) } - } - } - - @Test - fun testProvidedImageScreenshot() = runBlocking { - val bounds = Rect(50, 50, 150, 150) - val processor = RequestProcessor(imageCapture, policy) - - policy.setManagedProfile(USER_ID, false) - - val bitmap = makeHardwareBitmap(100, 100) - - val request = - ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER) - .setTopComponent(component) - .setTaskId(TASK_ID) - .setUserId(USER_ID) - .setBitmap(bitmap) - .setBoundsOnScreen(bounds) - .setInsets(Insets.NONE) - .build() - - val screenshotData = ScreenshotData.fromRequest(request) - val processedData = processor.process(screenshotData) - - assertThat(processedData).isEqualTo(screenshotData) - } - - @Test - fun testProvidedImageScreenshot_managedProfile() = runBlocking { - val bounds = Rect(50, 50, 150, 150) - val processor = RequestProcessor(imageCapture, policy) - - // Indicate that the screenshot belongs to a manged profile - policy.setManagedProfile(USER_ID, true) - - val bitmap = makeHardwareBitmap(100, 100) - - val request = - ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER) - .setTopComponent(component) - .setTaskId(TASK_ID) - .setUserId(USER_ID) - .setBitmap(bitmap) - .setBoundsOnScreen(bounds) - .setInsets(Insets.NONE) - .build() - - val screenshotData = ScreenshotData.fromRequest(request) - val processedData = processor.process(screenshotData) - - // Work profile, but already a task snapshot, so no changes - assertThat(processedData).isEqualTo(screenshotData) - } - - private fun makeHardwareBitmap(width: Int, height: Int): Bitmap { - val buffer = - HardwareBuffer.create( - width, - height, - HardwareBuffer.RGBA_8888, - 1, - HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE - ) - return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!! - } - - private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean { - return bitmap.hardwareBuffer == this.hardwareBuffer && bitmap.colorSpace == this.colorSpace - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt index 2f810276d7b2..c7919df7e0d0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt @@ -20,6 +20,7 @@ import android.content.pm.UserInfo import android.os.UserManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.FakeExecutor @@ -35,6 +36,7 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import org.mockito.kotlin.never @SmallTest @RunWith(AndroidJUnit4::class) @@ -46,12 +48,20 @@ class ManagedProfileControllerImplTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var userManager: UserManager + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Before fun setup() { MockitoAnnotations.initMocks(this) - controller = ManagedProfileControllerImpl(context, mainExecutor, userTracker, userManager) + controller = + ManagedProfileControllerImpl( + context, + mainExecutor, + userTracker, + userManager, + keyguardUpdateMonitor + ) } @Test @@ -107,6 +117,24 @@ class ManagedProfileControllerImplTest : SysuiTestCase() { captor.value.onProfilesChanged(userManager.getEnabledProfiles(1)) } + @Test + fun hasWorkingProfile_setWorkModeEnabled_callsAwakenFromDream() { + `when`(userTracker.userId).thenReturn(1) + setupWorkingProfile(1) + `when`(userManager.requestQuietModeEnabled(any(), any())).thenReturn(false) + controller.hasActiveProfile() + + controller.isWorkModeEnabled = true + + verify(keyguardUpdateMonitor).awakenFromDream() + } + + @Test + fun noWorkingProfile_setWorkModeEnabled_NoAwakenFromDreamCall() { + controller.isWorkModeEnabled = true + verify(keyguardUpdateMonitor, never()).awakenFromDream() + } + private fun setupWorkingProfile(userId: Int) { `when`(userManager.getEnabledProfiles(userId)) .thenReturn( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt deleted file mode 100644 index 7482c0feb56a..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.bouncer.shared.flag - -import com.android.systemui.scene.shared.flag.SceneContainerFlag - -class FakeComposeBouncerFlags(var composeBouncerEnabled: Boolean = false) : ComposeBouncerFlags { - override fun isComposeBouncerOrSceneContainerEnabled(): Boolean { - return SceneContainerFlag.isEnabled || composeBouncerEnabled - } - - @Deprecated( - "Avoid using this, this is meant to be used only by the glue code " + - "that includes compose bouncer in legacy keyguard.", - replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()") - ) - override fun isOnlyComposeBouncerEnabled(): Boolean = composeBouncerEnabled -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt index e8612d084b14..5c5969d359c3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt @@ -22,7 +22,6 @@ import android.content.applicationContext import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor -import com.android.systemui.bouncer.shared.flag.composeBouncerFlags import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsAllowedInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor @@ -45,7 +44,6 @@ val Kosmos.bouncerMessageViewModel by Fixture { faceAuthInteractor = deviceEntryFaceAuthInteractor, deviceUnlockedInteractor = deviceUnlockedInteractor, deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor, - flags = composeBouncerFlags, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt index e405d17166b9..171be97bf964 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt @@ -25,7 +25,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor -import com.android.systemui.bouncer.shared.flag.composeBouncerFlags import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -55,7 +54,6 @@ val Kosmos.bouncerSceneContentViewModel by Fixture { authenticationInteractor = authenticationInteractor, devicePolicyManager = devicePolicyManager, bouncerMessageViewModelFactory = bouncerMessageViewModelFactory, - flags = composeBouncerFlags, userSwitcher = userSwitcherViewModel, actionButtonInteractor = bouncerActionButtonInteractor, pinViewModelFactory = pinBouncerViewModelFactory, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 4ad046cc095e..629fda6b610c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -36,6 +36,7 @@ import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.activityStarter import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.settings.userTracker +import com.android.systemui.statusbar.phone.fakeManagedProfileController import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.mock @@ -61,6 +62,7 @@ val Kosmos.communalInteractor by Fixture { sceneInteractor = sceneInteractor, logBuffer = logcatLogBuffer("CommunalInteractor"), tableLogBuffer = mock(), + managedProfileController = fakeManagedProfileController ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index b9443bcaf650..7cf4083e8407 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -25,6 +25,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor import com.android.systemui.statusbar.phone.dozeParameters import com.android.systemui.statusbar.phone.screenOffAnimationController @@ -39,6 +40,7 @@ val Kosmos.keyguardRootViewModel by Fixture { communalInteractor = communalInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, notificationsKeyguardInteractor = notificationsKeyguardInteractor, + notificationShadeWindowModel = notificationShadeWindowModel, alternateBouncerToAodTransitionViewModel = alternateBouncerToAodTransitionViewModel, alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, alternateBouncerToLockscreenTransitionViewModel = diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 953363d010fe..851a378f3165 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -36,6 +36,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.globalactions.domain.interactor.globalActionsInteractor +import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.haptics.qs.qsLongPressEffect import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -154,4 +155,5 @@ class KosmosJavaAdapter() { val scrimController by lazy { kosmos.scrimController } val scrimStartable by lazy { kosmos.scrimStartable } val sceneContainerOcclusionInteractor by lazy { kosmos.sceneContainerOcclusionInteractor } + val msdlPlayer by lazy { kosmos.msdlPlayer } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt index 60d97d1b1437..ef04b9d907f1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt @@ -14,9 +14,14 @@ * limitations under the License. */ -package com.android.systemui.bouncer.shared.flag +@file:OptIn(ExperimentalCoroutinesApi::class) +package com.android.systemui.statusbar.phone + +import android.testing.LeakCheck import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.utils.leaks.FakeManagedProfileController +import kotlinx.coroutines.ExperimentalCoroutinesApi -var Kosmos.fakeComposeBouncerFlags by Kosmos.Fixture { FakeComposeBouncerFlags() } -val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags } +val Kosmos.fakeManagedProfileController by Fixture { FakeManagedProfileController(LeakCheck()) } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java index 845249e2c82f..ab94e989f8b3 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java @@ -39,8 +39,14 @@ public final class MouseEventHandler { * @param displayId The display that is being magnified */ public void onEvent(MotionEvent event, int displayId) { - if (event.getAction() == ACTION_HOVER_MOVE - || (event.getAction() == ACTION_MOVE && event.getSource() == SOURCE_MOUSE)) { + // Ignore gesture events synthesized from the touchpad. + // TODO(b/354696546): Use synthesized pinch gestures to control scale. + boolean isSynthesizedFromTouchpad = + event.getClassification() != MotionEvent.CLASSIFICATION_NONE; + + // Consume only move events from the mouse or hovers from any tool. + if (!isSynthesizedFromTouchpad && (event.getAction() == ACTION_HOVER_MOVE + || (event.getAction() == ACTION_MOVE && event.getSource() == SOURCE_MOUSE))) { final float eventX = event.getX(); final float eventY = event.getY(); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index d86bae19f174..07e5f2e34ab8 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -224,9 +224,6 @@ class StorageManagerService extends IStorageManager.Stub /** Extended timeout for the system server watchdog for vold#partition operation. */ private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000; - private static final Pattern OBB_FILE_PATH = Pattern.compile( - "(?i)(^/storage/[^/]+/(?:([0-9]+)/)?Android/obb/)([^/]+)/([^/]+\\.obb)"); - @GuardedBy("mLock") private final Set<Integer> mFuseMountedUser = new ArraySet<>(); @@ -3147,9 +3144,7 @@ class StorageManagerService extends IStorageManager.Stub Objects.requireNonNull(rawPath, "rawPath cannot be null"); Objects.requireNonNull(canonicalPath, "canonicalPath cannot be null"); Objects.requireNonNull(token, "token cannot be null"); - Objects.requireNonNull(obbInfo, "obbInfo cannot be null"); - - validateObbInfo(obbInfo, rawPath); + Objects.requireNonNull(obbInfo, "obbIfno cannot be null"); final int callingUid = Binder.getCallingUid(); final ObbState obbState = new ObbState(rawPath, canonicalPath, @@ -3161,34 +3156,6 @@ class StorageManagerService extends IStorageManager.Stub Slog.i(TAG, "Send to OBB handler: " + action.toString()); } - private void validateObbInfo(ObbInfo obbInfo, String rawPath) { - String obbFilePath; - try { - obbFilePath = new File(rawPath).getCanonicalPath(); - } catch (IOException ex) { - throw new RuntimeException("Failed to resolve path" + rawPath + " : " + ex); - } - - Matcher matcher = OBB_FILE_PATH.matcher(obbFilePath); - - if (matcher.matches()) { - int userId = UserHandle.getUserId(Binder.getCallingUid()); - String pathUserId = matcher.group(2); - String pathPackageName = matcher.group(3); - if ((pathUserId != null && Integer.parseInt(pathUserId) != userId) - || (pathUserId == null && userId != mCurrentUserId)) { - throw new SecurityException( - "Path " + obbFilePath + "does not correspond to calling userId " + userId); - } - if (obbInfo != null && !obbInfo.packageName.equals(pathPackageName)) { - throw new SecurityException("Path " + obbFilePath - + " does not contain package name " + pathPackageName); - } - } else { - throw new SecurityException("Invalid path to Obb file : " + obbFilePath); - } - } - @Override public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) { Objects.requireNonNull(rawPath, "rawPath cannot be null"); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 168ec052e67d..4e24cf38fe73 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -790,6 +790,7 @@ public class AudioService extends IAudioService.Stub private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver(); private final Executor mAudioServerLifecycleExecutor; + private long mSysPropListenerNativeHandle; private final List<Future> mScheduledPermissionTasks = new ArrayList(); private IMediaProjectionManager mProjectionService; // to validate projection token @@ -10640,7 +10641,7 @@ public class AudioService extends IAudioService.Stub }, UPDATE_DELAY_MS, TimeUnit.MILLISECONDS)); } }; - mAudioSystem.listenForSystemPropertyChange( + mSysPropListenerNativeHandle = mAudioSystem.listenForSystemPropertyChange( PermissionManager.CACHE_KEY_PACKAGE_INFO, task); } else { @@ -14713,6 +14714,7 @@ public class AudioService extends IAudioService.Stub @Override /** @see AudioManager#permissionUpdateBarrier() */ public void permissionUpdateBarrier() { + mAudioSystem.triggerSystemPropertyUpdate(mSysPropListenerNativeHandle); List<Future> snapshot; synchronized (mScheduledPermissionTasks) { snapshot = List.copyOf(mScheduledPermissionTasks); diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index d083c68c4c2c..5cabddea9c17 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -748,8 +748,12 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, return AudioSystem.setMasterMute(mute); } - public void listenForSystemPropertyChange(String systemPropertyName, Runnable callback) { - AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback); + public long listenForSystemPropertyChange(String systemPropertyName, Runnable callback) { + return AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback); + } + + public void triggerSystemPropertyUpdate(long handle) { + AudioSystem.triggerSystemPropertyUpdate(handle); } /** diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java index 38eb416ffdd8..ddea285d3564 100644 --- a/services/core/java/com/android/server/display/DisplayControl.java +++ b/services/core/java/com/android/server/display/DisplayControl.java @@ -109,7 +109,7 @@ public class DisplayControl { /** * Sets the HDR conversion mode for the device. * - * Returns the system preferred Hdr output type nn case when HDR conversion mode is + * Returns the system preferred HDR output type in case when HDR conversion mode is * {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_SYSTEM}. * Returns Hdr::INVALID in other cases. * @hide diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 93bd92614403..acf4db30ba93 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -318,13 +318,16 @@ final class DisplayDeviceInfo { */ public Display.HdrCapabilities hdrCapabilities; + /** When true, all HDR capabilities are hidden from public APIs */ + public boolean isForceSdr; + /** * Indicates whether this display supports Auto Low Latency Mode. */ public boolean allmSupported; /** - * Indicates whether this display suppors Game content type. + * Indicates whether this display supports Game content type. */ public boolean gameContentTypeSupported; @@ -516,6 +519,7 @@ final class DisplayDeviceInfo { || !Arrays.equals(supportedModes, other.supportedModes) || !Arrays.equals(supportedColorModes, other.supportedColorModes) || !Objects.equals(hdrCapabilities, other.hdrCapabilities) + || isForceSdr != other.isForceSdr || allmSupported != other.allmSupported || gameContentTypeSupported != other.gameContentTypeSupported || densityDpi != other.densityDpi @@ -560,6 +564,7 @@ final class DisplayDeviceInfo { colorMode = other.colorMode; supportedColorModes = other.supportedColorModes; hdrCapabilities = other.hdrCapabilities; + isForceSdr = other.isForceSdr; allmSupported = other.allmSupported; gameContentTypeSupported = other.gameContentTypeSupported; densityDpi = other.densityDpi; @@ -603,6 +608,7 @@ final class DisplayDeviceInfo { sb.append(", colorMode ").append(colorMode); sb.append(", supportedColorModes ").append(Arrays.toString(supportedColorModes)); sb.append(", hdrCapabilities ").append(hdrCapabilities); + sb.append(", isForceSdr ").append(isForceSdr); sb.append(", allmSupported ").append(allmSupported); sb.append(", gameContentTypeSupported ").append(gameContentTypeSupported); sb.append(", density ").append(densityDpi); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 187caba4db03..1813c2eb3242 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -48,6 +48,7 @@ import static android.os.Process.ROOT_UID; import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL; import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH; import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN; +import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; import static com.android.server.display.layout.Layout.Display.POSITION_REAR; @@ -282,7 +283,7 @@ public final class DisplayManagerService extends SystemService { @GuardedBy("mSyncRoot") private int[] mUserDisabledHdrTypes = {}; @Display.HdrCapabilities.HdrType - private int[] mSupportedHdrOutputType; + private int[] mSupportedHdrOutputTypes; @GuardedBy("mSyncRoot") private boolean mAreUserDisabledHdrTypesAllowed = true; @@ -297,10 +298,10 @@ public final class DisplayManagerService extends SystemService { // HDR conversion mode chosen by user @GuardedBy("mSyncRoot") private HdrConversionMode mHdrConversionMode = null; - // Actual HDR conversion mode, which takes app overrides into account. - private HdrConversionMode mOverrideHdrConversionMode = null; + // Whether app has disabled HDR conversion + private boolean mShouldDisableHdrConversion = false; @GuardedBy("mSyncRoot") - private int mSystemPreferredHdrOutputType = Display.HdrCapabilities.HDR_TYPE_INVALID; + private int mSystemPreferredHdrOutputType = HDR_TYPE_INVALID; // The synchronization root for the display manager. @@ -1407,7 +1408,8 @@ public final class DisplayManagerService extends SystemService { } } - private void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) { + @VisibleForTesting + void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) { synchronized (mSyncRoot) { if (userDisabledHdrTypes == null) { Slog.e(TAG, "Null is not an expected argument to " @@ -1425,6 +1427,7 @@ public final class DisplayManagerService extends SystemService { if (Arrays.equals(mUserDisabledHdrTypes, userDisabledHdrTypes)) { return; } + String userDisabledFormatsString = ""; if (userDisabledHdrTypes.length != 0) { userDisabledFormatsString = TextUtils.join(",", @@ -1440,6 +1443,15 @@ public final class DisplayManagerService extends SystemService { handleLogicalDisplayChangedLocked(display); }); } + /** Note: it may be expected to reset the Conversion Mode when an HDR type is enabled + and the Conversion Mode is set to System Preferred. This is handled in the Settings + code because in the special case where HDR is indirectly disabled by Force SDR + Conversion, manually enabling HDR is not recognized as an action that reduces the + disabled HDR count. Thus, this case needs to be checked in the Settings code when we + know we're enabling an HDR mode. If we split checking for SystemConversion and + isForceSdr in two places, we may have duplicate calls to resetting to System Conversion + and get two black screens. + */ } } @@ -1452,19 +1464,20 @@ public final class DisplayManagerService extends SystemService { return true; } - private void setAreUserDisabledHdrTypesAllowedInternal( + @VisibleForTesting + void setAreUserDisabledHdrTypesAllowedInternal( boolean areUserDisabledHdrTypesAllowed) { synchronized (mSyncRoot) { if (mAreUserDisabledHdrTypesAllowed == areUserDisabledHdrTypesAllowed) { return; } mAreUserDisabledHdrTypesAllowed = areUserDisabledHdrTypesAllowed; - if (mUserDisabledHdrTypes.length == 0) { - return; - } Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED, areUserDisabledHdrTypesAllowed ? 1 : 0); + if (mUserDisabledHdrTypes.length == 0) { + return; + } int userDisabledHdrTypes[] = {}; if (!mAreUserDisabledHdrTypesAllowed) { userDisabledHdrTypes = mUserDisabledHdrTypes; @@ -1475,6 +1488,14 @@ public final class DisplayManagerService extends SystemService { display.setUserDisabledHdrTypes(finalUserDisabledHdrTypes); handleLogicalDisplayChangedLocked(display); }); + // When HDR conversion mode is set to SYSTEM, modification to + // areUserDisabledHdrTypesAllowed requires refreshing the HDR conversion mode to tell + // the system which HDR types it is not allowed to use. + if (getHdrConversionModeInternal().getConversionMode() + == HdrConversionMode.HDR_CONVERSION_SYSTEM) { + setHdrConversionModeInternal( + new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM)); + } } } @@ -2348,7 +2369,7 @@ public final class DisplayManagerService extends SystemService { final int preferredHdrOutputType = hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_FORCE ? hdrConversionMode.getPreferredHdrOutputType() - : Display.HdrCapabilities.HDR_TYPE_INVALID; + : HDR_TYPE_INVALID; Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.HDR_FORCE_CONVERSION_TYPE, preferredHdrOutputType); } @@ -2361,7 +2382,7 @@ public final class DisplayManagerService extends SystemService { ? Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.HDR_FORCE_CONVERSION_TYPE, Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION) - : Display.HdrCapabilities.HDR_TYPE_INVALID; + : HDR_TYPE_INVALID; mHdrConversionMode = new HdrConversionMode(conversionMode, preferredHdrOutputType); setHdrConversionModeInternal(mHdrConversionMode); } @@ -2498,22 +2519,38 @@ public final class DisplayManagerService extends SystemService { }); } + /** + * Returns the HDR output types that are supported by the device's HDR conversion capabilities, + * stripping out any user-disabled HDR types if mAreUserDisabledHdrTypesAllowed is false. + */ @GuardedBy("mSyncRoot") - private int[] getEnabledAutoHdrTypesLocked() { - IntArray autoHdrOutputTypesArray = new IntArray(); + @VisibleForTesting + int[] getEnabledHdrOutputTypesLocked() { + if (mAreUserDisabledHdrTypesAllowed) { + return getSupportedHdrOutputTypesInternal(); + } + // Strip out all HDR formats that are currently user-disabled + IntArray enabledHdrOutputTypesArray = new IntArray(); for (int type : getSupportedHdrOutputTypesInternal()) { - boolean isDisabled = false; + boolean isEnabled = true; for (int disabledType : mUserDisabledHdrTypes) { if (type == disabledType) { - isDisabled = true; + isEnabled = false; break; } } - if (!isDisabled) { - autoHdrOutputTypesArray.add(type); + if (isEnabled) { + enabledHdrOutputTypesArray.add(type); } } - return autoHdrOutputTypesArray.toArray(); + return enabledHdrOutputTypesArray.toArray(); + } + + @VisibleForTesting + int[] getEnabledHdrOutputTypes() { + synchronized (mSyncRoot) { + return getEnabledHdrOutputTypesLocked(); + } } @GuardedBy("mSyncRoot") @@ -2522,7 +2559,7 @@ public final class DisplayManagerService extends SystemService { final int preferredHdrOutputType = mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM ? mSystemPreferredHdrOutputType : mode.getPreferredHdrOutputType(); - if (preferredHdrOutputType != Display.HdrCapabilities.HDR_TYPE_INVALID) { + if (preferredHdrOutputType != HDR_TYPE_INVALID) { int[] hdrTypesWithLatency = mInjector.getHdrOutputTypesWithLatency(); return ArrayUtils.contains(hdrTypesWithLatency, preferredHdrOutputType); } @@ -2556,41 +2593,57 @@ public final class DisplayManagerService extends SystemService { if (!mInjector.getHdrOutputConversionSupport()) { return; } - int[] autoHdrOutputTypes = null; + synchronized (mSyncRoot) { if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM && hdrConversionMode.getPreferredHdrOutputType() - != Display.HdrCapabilities.HDR_TYPE_INVALID) { + != HDR_TYPE_INVALID) { throw new IllegalArgumentException("preferredHdrOutputType must not be set if" + " the conversion mode is HDR_CONVERSION_SYSTEM"); } mHdrConversionMode = hdrConversionMode; storeHdrConversionModeLocked(mHdrConversionMode); - // For auto mode, all supported HDR types are allowed except the ones specifically - // disabled by the user. + // If the HDR conversion is HDR_CONVERSION_SYSTEM, all supported HDR types are allowed + // except the ones specifically disabled by the user. + int[] enabledHdrOutputTypes = null; if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) { - autoHdrOutputTypes = getEnabledAutoHdrTypesLocked(); + enabledHdrOutputTypes = getEnabledHdrOutputTypesLocked(); } int conversionMode = hdrConversionMode.getConversionMode(); int preferredHdrType = hdrConversionMode.getPreferredHdrOutputType(); + // If the HDR conversion is disabled by an app through WindowManager.LayoutParams, then // set HDR conversion mode to HDR_CONVERSION_PASSTHROUGH. - if (mOverrideHdrConversionMode == null) { + if (mShouldDisableHdrConversion) { + conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH; + preferredHdrType = -1; + enabledHdrOutputTypes = null; + } else { // HDR_CONVERSION_FORCE with HDR_TYPE_INVALID is used to represent forcing SDR type. - // But, internally SDR is selected by using passthrough mode. + // But, internally SDR is forced by using passthrough mode and not reporting any + // HDR capabilities to apps. if (conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE - && preferredHdrType == Display.HdrCapabilities.HDR_TYPE_INVALID) { + && preferredHdrType == HDR_TYPE_INVALID) { conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH; + mLogicalDisplayMapper.forEachLocked( + logicalDisplay -> { + if (logicalDisplay.setIsForceSdr(true)) { + handleLogicalDisplayChangedLocked(logicalDisplay); + } + }); + } else { + mLogicalDisplayMapper.forEachLocked( + logicalDisplay -> { + if (logicalDisplay.setIsForceSdr(false)) { + handleLogicalDisplayChangedLocked(logicalDisplay); + } + }); } - } else { - conversionMode = mOverrideHdrConversionMode.getConversionMode(); - preferredHdrType = mOverrideHdrConversionMode.getPreferredHdrOutputType(); - autoHdrOutputTypes = null; } mSystemPreferredHdrOutputType = mInjector.setHdrConversionMode( - conversionMode, preferredHdrType, autoHdrOutputTypes); + conversionMode, preferredHdrType, enabledHdrOutputTypes); } } @@ -2612,8 +2665,8 @@ public final class DisplayManagerService extends SystemService { } HdrConversionMode mode; synchronized (mSyncRoot) { - mode = mOverrideHdrConversionMode != null - ? mOverrideHdrConversionMode + mode = mShouldDisableHdrConversion + ? new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH) : mHdrConversionMode; // Handle default: PASSTHROUGH. Don't include the system-preferred type. if (mode == null @@ -2621,8 +2674,6 @@ public final class DisplayManagerService extends SystemService { return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH); } // Handle default or current mode: SYSTEM. Include the system preferred type. - // mOverrideHdrConversionMode and mHdrConversionMode do not include the system - // preferred type, it is kept separately in mSystemPreferredHdrOutputType. if (mode == null || mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) { return new HdrConversionMode( @@ -2633,10 +2684,10 @@ public final class DisplayManagerService extends SystemService { } private @Display.HdrCapabilities.HdrType int[] getSupportedHdrOutputTypesInternal() { - if (mSupportedHdrOutputType == null) { - mSupportedHdrOutputType = mInjector.getSupportedHdrOutputTypes(); + if (mSupportedHdrOutputTypes == null) { + mSupportedHdrOutputTypes = mInjector.getSupportedHdrOutputTypes(); } - return mSupportedHdrOutputType; + return mSupportedHdrOutputTypes; } void setShouldAlwaysRespectAppRequestedModeInternal(boolean enabled) { @@ -2822,15 +2873,9 @@ public final class DisplayManagerService extends SystemService { // HDR conversion is disabled in two cases: // - HDR conversion introduces latency and minimal post-processing is requested // - app requests to disable HDR conversion - if (mOverrideHdrConversionMode == null && (disableHdrConversion - || disableHdrConversionForLatency)) { - mOverrideHdrConversionMode = - new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH); - setHdrConversionModeInternal(mHdrConversionMode); - handleLogicalDisplayChangedLocked(display); - } else if (mOverrideHdrConversionMode != null && !disableHdrConversion - && !disableHdrConversionForLatency) { - mOverrideHdrConversionMode = null; + boolean previousShouldDisableHdrConversion = mShouldDisableHdrConversion; + mShouldDisableHdrConversion = disableHdrConversion || disableHdrConversionForLatency; + if (previousShouldDisableHdrConversion != mShouldDisableHdrConversion) { setHdrConversionModeInternal(mHdrConversionMode); handleLogicalDisplayChangedLocked(display); } @@ -3509,9 +3554,9 @@ public final class DisplayManagerService extends SystemService { } int setHdrConversionMode(int conversionMode, int preferredHdrOutputType, - int[] autoHdrTypes) { + int[] allowedHdrOutputTypes) { return DisplayControl.setHdrConversionMode(conversionMode, preferredHdrOutputType, - autoHdrTypes); + allowedHdrOutputTypes); } @Display.HdrCapabilities.HdrType diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 5d55d1904f1b..5e42444ecf12 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -518,6 +518,7 @@ final class LogicalDisplay { deviceInfo.supportedColorModes, deviceInfo.supportedColorModes.length); mBaseDisplayInfo.hdrCapabilities = deviceInfo.hdrCapabilities; + mBaseDisplayInfo.isForceSdr = deviceInfo.isForceSdr; mBaseDisplayInfo.userDisabledHdrTypes = mUserDisabledHdrTypes; mBaseDisplayInfo.minimalPostProcessingSupported = deviceInfo.allmSupported || deviceInfo.gameContentTypeSupported; @@ -899,6 +900,29 @@ final class LogicalDisplay { } /** + * Checks whether display is of the type where HDR settings are relevant, and then sets + * whether Force SDR conversion mode is active. isForceSdr is checked by the Display when + * returning HDR capabilities. + * + * @param isForceSdr Whether Force SDR conversion mode is active + * @return Whether Display Manager should call handleLogicalDisplayChangedLocked() + */ + public boolean setIsForceSdr(boolean isForceSdr) { + int displayType = getDisplayInfoLocked().type; + boolean isTargetDisplayType = displayType == Display.TYPE_INTERNAL + || displayType == Display.TYPE_EXTERNAL + || displayType == Display.TYPE_OVERLAY; + + boolean handleLogicalDisplayChangedLocked = false; + if (isTargetDisplayType && mBaseDisplayInfo.isForceSdr != isForceSdr) { + mBaseDisplayInfo.isForceSdr = isForceSdr; + mInfo.set(null); + handleLogicalDisplayChangedLocked = true; + } + return handleLogicalDisplayChangedLocked; + } + + /** * Swap the underlying {@link DisplayDevice} with the specified LogicalDisplay. * * @param targetDisplay The display with which to swap display-devices. diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 6b3b5bde851b..67900f843063 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -251,7 +251,7 @@ public final class TvInputManagerService extends SystemService { } private void registerBroadcastReceivers() { - PackageMonitor monitor = new PackageMonitor() { + PackageMonitor monitor = new PackageMonitor(/* supportsPackageRestartQuery */ true) { private void buildTvInputList(String[] packages) { int userId = getChangingUserId(); synchronized (mLock) { diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java index edd2fa9a4e44..6a7fc6dcf7cd 100644 --- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java @@ -519,7 +519,7 @@ public class TvInteractiveAppManagerService extends SystemService { } private void registerBroadcastReceivers() { - PackageMonitor monitor = new PackageMonitor() { + PackageMonitor monitor = new PackageMonitor(/* supportsPackageRestartQuery */ true) { private void buildTvInteractiveAppServiceList(String[] packages) { int userId = getChangingUserId(); synchronized (mLock) { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 20c5f02aaee2..a5cea34bf79d 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -1685,6 +1685,21 @@ public class BackgroundActivityStartController { (state.mOriginatingPendingIntent != null)); } + if (finalVerdict.getRawCode() == BAL_ALLOW_GRACE_PERIOD) { + if (state.realCallerExplicitOptInOrAutoOptIn() + && state.mResultForRealCaller.allows() + && state.mResultForRealCaller.getRawCode() != BAL_ALLOW_GRACE_PERIOD) { + // real caller could allow with a different exemption + } else if (state.callerExplicitOptInOrAutoOptIn() && state.mResultForCaller.allows() + && state.mResultForCaller.getRawCode() != BAL_ALLOW_GRACE_PERIOD) { + // caller could allow with a different exemption + } else { + // log to determine grace period length distribution + Slog.wtf(TAG, "Activity start ONLY allowed by BAL_ALLOW_GRACE_PERIOD " + + finalVerdict.mMessage + ": " + state); + } + } + if (balImprovedMetrics()) { if (shouldLogStats(finalVerdict, state)) { String activityName; diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java index 4a870a3a5b6e..5f5365dca1e9 100644 --- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java +++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static com.android.internal.util.Preconditions.checkArgument; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS; @@ -48,7 +47,6 @@ import android.os.SystemClock; import android.os.UserHandle; import android.util.ArrayMap; import android.util.IntArray; -import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.wm.BackgroundActivityStartController.BalVerdict; @@ -138,22 +136,16 @@ class BackgroundLaunchProcessController { if (appSwitchState == APP_SWITCH_ALLOW) { // Allow if any activity in the caller has either started or finished very recently, and // it must be started or finished after last stop app switches time. - final long now = SystemClock.uptimeMillis(); - if (now - lastActivityLaunchTime < ACTIVITY_BG_START_GRACE_PERIOD_MS - || now - lastActivityFinishTime < ACTIVITY_BG_START_GRACE_PERIOD_MS) { - // If activity is started and finished before stop app switch time, we should not - // let app to be able to start background activity even it's in grace period. - if (lastActivityLaunchTime > lastStopAppSwitchesTime - || lastActivityFinishTime > lastStopAppSwitchesTime) { + if (lastActivityLaunchTime > lastStopAppSwitchesTime + || lastActivityFinishTime > lastStopAppSwitchesTime) { + final long now = SystemClock.uptimeMillis(); + long timeSinceLastStartOrFinish = now - Math.max(lastActivityLaunchTime, + lastActivityFinishTime); + if (timeSinceLastStartOrFinish < ACTIVITY_BG_START_GRACE_PERIOD_MS) { return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ true, - "within " + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period"); + "within " + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period (" + + timeSinceLastStartOrFinish + "ms)"); } - if (DEBUG_ACTIVITY_STARTS) { - Slog.d(TAG, "[Process(" + pid + ")] Activity start within " - + ACTIVITY_BG_START_GRACE_PERIOD_MS - + "ms grace period but also within stop app switch window"); - } - } } return BalVerdict.BLOCK; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java index b982098fefa4..5eec0124a9e3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java @@ -1325,6 +1325,11 @@ class ActiveAdmin { pw.print("encryptionRequested="); pw.println(encryptionRequested); + if (!Flags.policyEngineMigrationV2Enabled()) { + pw.print("mUsbDataSignaling="); + pw.println(mUsbDataSignalingEnabled); + } + pw.print("disableCallerId="); pw.println(disableCallerId); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index 4beb6a8a3480..a08af72586ee 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -230,9 +230,11 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); - if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value, - policyDefinition, userId)) { - return; + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value, + policyDefinition, userId)) { + return; + } } if (policyDefinition.isNonCoexistablePolicy()) { @@ -352,7 +354,9 @@ final class DevicePolicyEngine { } PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); - decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin); + } if (policyDefinition.isNonCoexistablePolicy()) { setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState, @@ -496,9 +500,11 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); - if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value, - policyDefinition, UserHandle.USER_ALL)) { - return; + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value, + policyDefinition, UserHandle.USER_ALL)) { + return; + } } // TODO(b/270999567): Move error handling for DISALLOW_CELLULAR_2G into the code // that honors the restriction once there's an API available @@ -565,7 +571,9 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition); - decreasePolicySizeForAdmin(policyState, enforcingAdmin); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + decreasePolicySizeForAdmin(policyState, enforcingAdmin); + } boolean policyChanged = policyState.removePolicy(enforcingAdmin); @@ -1731,23 +1739,25 @@ final class DevicePolicyEngine { pw.println(); } pw.decreaseIndent(); - pw.println(); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + pw.println(); - pw.println("Default admin policy size limit: " + DEFAULT_POLICY_SIZE_LIMIT); - pw.println("Current admin policy size limit: " + mPolicySizeLimit); - pw.println("Admin Policies size: "); - for (int i = 0; i < mAdminPolicySize.size(); i++) { - int userId = mAdminPolicySize.keyAt(i); - pw.printf("User %d:\n", userId); - pw.increaseIndent(); - for (EnforcingAdmin admin : mAdminPolicySize.get(userId).keySet()) { - pw.printf("Admin : " + admin + " : " + mAdminPolicySize.get(userId).get( - admin)); - pw.println(); + pw.println("Default admin policy size limit: " + DEFAULT_POLICY_SIZE_LIMIT); + pw.println("Current admin policy size limit: " + mPolicySizeLimit); + pw.println("Admin Policies size: "); + for (int i = 0; i < mAdminPolicySize.size(); i++) { + int userId = mAdminPolicySize.keyAt(i); + pw.printf("User %d:\n", userId); + pw.increaseIndent(); + for (EnforcingAdmin admin : mAdminPolicySize.get(userId).keySet()) { + pw.printf("Admin : " + admin + " : " + mAdminPolicySize.get(userId).get( + admin)); + pw.println(); + } + pw.decreaseIndent(); } pw.decreaseIndent(); } - pw.decreaseIndent(); } } @@ -2008,21 +2018,23 @@ final class DevicePolicyEngine { private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer) throws IOException { - if (mAdminPolicySize != null) { - for (int i = 0; i < mAdminPolicySize.size(); i++) { - int userId = mAdminPolicySize.keyAt(i); - for (EnforcingAdmin admin : mAdminPolicySize.get( - userId).keySet()) { - serializer.startTag(/* namespace= */ null, - TAG_ENFORCING_ADMIN_AND_SIZE); - serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN); - admin.saveToXml(serializer); - serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN); - serializer.startTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE); - serializer.attributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE, - mAdminPolicySize.get(userId).get(admin)); - serializer.endTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE); - serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_AND_SIZE); + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + if (mAdminPolicySize != null) { + for (int i = 0; i < mAdminPolicySize.size(); i++) { + int userId = mAdminPolicySize.keyAt(i); + for (EnforcingAdmin admin : mAdminPolicySize.get( + userId).keySet()) { + serializer.startTag(/* namespace= */ null, + TAG_ENFORCING_ADMIN_AND_SIZE); + serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN); + admin.saveToXml(serializer); + serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN); + serializer.startTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE); + serializer.attributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE, + mAdminPolicySize.get(userId).get(admin)); + serializer.endTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE); + serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_AND_SIZE); + } } } } @@ -2030,6 +2042,9 @@ final class DevicePolicyEngine { private void writeMaxPolicySizeInner(TypedXmlSerializer serializer) throws IOException { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + return; + } serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT); serializer.attributeInt( /* namespace= */ null, ATTR_POLICY_SUM_SIZE, mPolicySizeLimit); @@ -2177,6 +2192,9 @@ final class DevicePolicyEngine { private void readMaxPolicySizeInner(TypedXmlPullParser parser) throws XmlPullParserException, IOException { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + return; + } mPolicySizeLimit = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 470025a67dee..886ae7ad7e50 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1328,7 +1328,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Bundle prevRestrictions) { resetCrossProfileIntentFiltersIfNeeded(userId, newRestrictions, prevRestrictions); resetUserVpnIfNeeded(userId, newRestrictions, prevRestrictions); - removePrivateSpaceIfRestrictionIsSet(userId, newRestrictions, prevRestrictions); + if (Flags.deletePrivateSpaceUnderRestriction()) { + removePrivateSpaceIfRestrictionIsSet(userId, newRestrictions, prevRestrictions); + } } private void resetUserVpnIfNeeded( @@ -3693,6 +3695,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } revertTransferOwnershipIfNecessaryLocked(); + if (!Flags.policyEngineMigrationV2Enabled()) { + updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked()); + } } // Check whether work apps were paused via suspension and unsuspend if necessary. @@ -7151,7 +7156,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // If there is a profile owner, redirect to that; otherwise query the device owner. ComponentName aliasChooser = getProfileOwnerAsUser(caller.getUserId()); - boolean isDoUser = caller.getUserId() == getDeviceOwnerUserId(); + boolean isDoUser = Flags.headlessSingleUserFixes() + ? caller.getUserId() == getDeviceOwnerUserId() + : caller.getUserHandle().isSystem(); if (aliasChooser == null && isDoUser) { synchronized (getLockObject()) { final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked(); @@ -8161,7 +8168,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // First check whether the admin is allowed to wipe the device/user/profile. final String restriction; boolean shouldFactoryReset = userId == UserHandle.USER_SYSTEM; - if (getHeadlessDeviceOwnerModeForDeviceOwner() + if (Flags.headlessSingleUserFixes() && getHeadlessDeviceOwnerModeForDeviceOwner() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER) { shouldFactoryReset = userId == getMainUserId(); } @@ -8185,7 +8192,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { adminPackage, userId)) { // Legacy mode - wipeDevice = getHeadlessDeviceOwnerModeForDeviceOwner() + wipeDevice = Flags.headlessSingleUserFixes() + && getHeadlessDeviceOwnerModeForDeviceOwner() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser; } else { // Explicit behaviour @@ -9369,7 +9377,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { void sendDeviceOwnerOrProfileOwnerCommand(String action, Bundle extras, int userId) { if (userId == UserHandle.USER_ALL) { - if (getHeadlessDeviceOwnerModeForDeviceOwner() + if (Flags.headlessDeviceOwnerDelegateSecurityLoggingBugFix() + && getHeadlessDeviceOwnerModeForDeviceOwner() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER) { userId = mOwners.getDeviceOwnerUserId(); } else { @@ -11855,7 +11864,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } setBackwardsCompatibleAppRestrictions( caller, packageName, restrictions, caller.getUserHandle()); - } else { + } else if (Flags.dmrhSetAppRestrictions()) { final boolean isRoleHolder; if (who != null) { // DO or PO @@ -11902,6 +11911,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { caller.getUserHandle()); }); } + } else { + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, + DELEGATION_APP_RESTRICTIONS))); + mInjector.binderWithCleanCallingIdentity(() -> { + mUserManager.setApplicationRestrictions(packageName, restrictions, + caller.getUserHandle()); + }); } DevicePolicyEventLogger @@ -12434,6 +12452,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (packageList != null) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + for (String pkg : packageList) { + PolicySizeVerifier.enforceMaxPackageNameLength(pkg); + } + } + List<InputMethodInfo> enabledImes = mInjector.binderWithCleanCallingIdentity(() -> InputMethodManagerInternal.get().getEnabledInputMethodListAsUser(userId)); if (enabledImes != null) { @@ -13232,7 +13256,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return Bundle.EMPTY; } return policies.get(enforcingAdmin).getValue(); - } else { + } else if (Flags.dmrhSetAppRestrictions()) { final boolean isRoleHolder; if (who != null) { // Caller is DO or PO. They cannot call this on parent @@ -13275,6 +13299,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return bundle != null ? bundle : Bundle.EMPTY; }); } + + } else { + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, + DELEGATION_APP_RESTRICTIONS))); + return mInjector.binderWithCleanCallingIdentity(() -> { + Bundle bundle = mUserManager.getApplicationRestrictions(packageName, + caller.getUserHandle()); + // if no restrictions were saved, mUserManager.getApplicationRestrictions + // returns null, but DPM method should return an empty Bundle as per JavaDoc + return bundle != null ? bundle : Bundle.EMPTY; + }); } } @@ -14283,6 +14320,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + PolicySizeVerifier.enforceMaxStringLength(accountType, "account type"); + } + CallerIdentity caller = getCallerIdentity(who, callerPackageName); synchronized (getLockObject()) { int affectedUser = getAffectedUser(parent); @@ -14893,6 +14934,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void setLockTaskPackages(ComponentName who, String callerPackageName, String[] packages) throws SecurityException { Objects.requireNonNull(packages, "packages is null"); + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + for (String pkg : packages) { + PolicySizeVerifier.enforceMaxPackageNameLength(pkg); + } + } CallerIdentity caller = getCallerIdentity(who, callerPackageName); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES); @@ -15173,7 +15219,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization( isProfileOwner(caller) || isDefaultDeviceOwner(caller)); - if (parent) { + if (Flags.allowScreenBrightnessControlOnCope() && parent) { Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)); } checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING); @@ -15184,7 +15230,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "Permission denial: device owners cannot update %1$s", setting)); } int affectedUser; - if (parent) { + if (Flags.allowScreenBrightnessControlOnCope() && parent) { affectedUser = getProfileParentId(caller.getUserId()); } else { affectedUser = caller.getUserId(); @@ -16776,11 +16822,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); } - final UserHandle user = UserHandle.of(userId); - final String roleHolderPackage = getRoleHolderPackageNameOnUser( - RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, userId); - if (roleHolderPackage != null) { - broadcastExplicitIntentToPackage(intent, roleHolderPackage, user); + if (Flags.permissionMigrationForZeroTrustImplEnabled()) { + final UserHandle user = UserHandle.of(userId); + final String roleHolderPackage = getRoleHolderPackageNameOnUser( + RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, userId); + if (roleHolderPackage != null) { + broadcastExplicitIntentToPackage(intent, roleHolderPackage, user); + } } } }); @@ -16788,10 +16836,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin, String callerPackage) { - CallerIdentity caller = getCallerIdentity(admin, callerPackage); - enforcePermissions(new String[] {NOTIFY_PENDING_SYSTEM_UPDATE, - MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES}, caller.getPackageName(), - caller.getUserId()); + if (Flags.permissionMigrationForZeroTrustImplEnabled()) { + CallerIdentity caller = getCallerIdentity(admin, callerPackage); + enforcePermissions(new String[] {NOTIFY_PENDING_SYSTEM_UPDATE, + MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES}, caller.getPackageName(), + caller.getUserId()); + } else { + Objects.requireNonNull(admin, "ComponentName is null"); + + final CallerIdentity caller = getCallerIdentity(admin); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + } return mOwners.getSystemUpdateInfo(); } @@ -17335,10 +17391,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Nullable ComponentName componentName, @UserIdInt int callingUserId) { synchronized (getLockObject()) { int deviceOwnerUserId = -1; - deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode() - && getHeadlessDeviceOwnerModeForDeviceAdmin(componentName, callingUserId) - == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED - ? UserHandle.USER_SYSTEM : callingUserId; + if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) { + deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode() + && getHeadlessDeviceOwnerModeForDeviceAdmin(componentName, callingUserId) + == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED + ? UserHandle.USER_SYSTEM : callingUserId; + } else { + deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode() + && getHeadlessDeviceOwnerModeForDeviceOwner() + == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED + ? UserHandle.USER_SYSTEM : callingUserId; + } Slogf.i(LOG_TAG, "Calling user %d, device owner will be set on user %d", callingUserId, deviceOwnerUserId); // hasIncompatibleAccountsOrNonAdb doesn't matter since the caller is not adb. @@ -18637,7 +18700,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Backup service has to be enabled on the main user in order for it to be enabled on // secondary users. - if (isDeviceOwner(caller) && getHeadlessDeviceOwnerModeForDeviceOwner() + if (Flags.headlessSingleUserFixes() && isDeviceOwner(caller) + && getHeadlessDeviceOwnerModeForDeviceOwner() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER) { toggleBackupServiceActive(UserHandle.USER_SYSTEM, enabled); } @@ -21378,7 +21442,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(callerPackage); - enforcePermission(MANAGE_DEVICE_POLICY_CERTIFICATES, caller.getPackageName()); + if (Flags.permissionMigrationForZeroTrustImplEnabled()) { + enforcePermission(MANAGE_DEVICE_POLICY_CERTIFICATES, caller.getPackageName()); + } else { + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller) + || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); + } synchronized (getLockObject()) { final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked( caller.getUserId()); @@ -21977,9 +22047,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final long identity = Binder.clearCallingIdentity(); try { boolean isSingleUserMode; - int headlessDeviceOwnerMode = getHeadlessDeviceOwnerModeForDeviceAdmin( - deviceAdmin, caller.getUserId()); - isSingleUserMode = headlessDeviceOwnerMode == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER; + if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) { + int headlessDeviceOwnerMode = getHeadlessDeviceOwnerModeForDeviceAdmin( + deviceAdmin, caller.getUserId()); + isSingleUserMode = + headlessDeviceOwnerMode == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER; + } else { + isSingleUserMode = + getHeadlessDeviceOwnerModeForDeviceOwner() + == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER; + } if (Flags.headlessSingleMinTargetSdk() && mInjector.userManagerIsHeadlessSystemUserMode() @@ -22378,17 +22455,35 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Objects.requireNonNull(packageName, "Admin package name must be provided"); final CallerIdentity caller = getCallerIdentity(packageName); - synchronized (getLockObject()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - /* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, - caller.getPackageName(), - caller.getUserId()); + if (!Flags.policyEngineMigrationV2Enabled()) { + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "USB data signaling can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); Preconditions.checkState(canUsbDataSignalingBeDisabled(), "USB data signaling cannot be disabled."); - mDevicePolicyEngine.setGlobalPolicy( - PolicyDefinition.USB_DATA_SIGNALING, - enforcingAdmin, - new BooleanPolicyValue(enabled)); + } + + synchronized (getLockObject()) { + if (Flags.policyEngineMigrationV2Enabled()) { + EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( + /* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, + caller.getPackageName(), + caller.getUserId()); + Preconditions.checkState(canUsbDataSignalingBeDisabled(), + "USB data signaling cannot be disabled."); + mDevicePolicyEngine.setGlobalPolicy( + PolicyDefinition.USB_DATA_SIGNALING, + enforcingAdmin, + new BooleanPolicyValue(enabled)); + } else { + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); + if (admin.mUsbDataSignalingEnabled != enabled) { + admin.mUsbDataSignalingEnabled = enabled; + saveSettingsLocked(caller.getUserId()); + updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked()); + } + } } DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_USB_DATA_SIGNALING) @@ -22410,10 +22505,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean isUsbDataSignalingEnabled(String packageName) { final CallerIdentity caller = getCallerIdentity(packageName); - Boolean enabled = mDevicePolicyEngine.getResolvedPolicy( - PolicyDefinition.USB_DATA_SIGNALING, - caller.getUserId()); - return enabled == null || enabled; + if (Flags.policyEngineMigrationV2Enabled()) { + Boolean enabled = mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.USB_DATA_SIGNALING, + caller.getUserId()); + return enabled == null || enabled; + } else { + synchronized (getLockObject()) { + // If the caller is an admin, return the policy set by itself. Otherwise + // return the device-wide policy. + if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice( + caller)) { + return getProfileOwnerOrDeviceOwnerLocked( + caller.getUserId()).mUsbDataSignalingEnabled; + } else { + return isUsbDataSignalingEnabledInternalLocked(); + } + } + } } private boolean isUsbDataSignalingEnabledInternalLocked() { @@ -24766,6 +24875,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + return; + } CallerIdentity caller = getCallerIdentity(callerPackageName); enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(), caller.getUserId()); @@ -24779,6 +24891,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public int getMaxPolicyStorageLimit(String callerPackageName) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + return -1; + } CallerIdentity caller = getCallerIdentity(callerPackageName); enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(), caller.getUserId()); @@ -24788,6 +24903,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void forceSetMaxPolicyStorageLimit(String callerPackageName, int storageLimit) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + return; + } CallerIdentity caller = getCallerIdentity(callerPackageName); enforcePermission(MANAGE_DEVICE_POLICY_STORAGE_LIMIT, caller.getPackageName(), caller.getUserId()); @@ -24798,6 +24916,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public int getPolicySizeForAdmin( String callerPackageName, android.app.admin.EnforcingAdmin admin) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + return -1; + } CallerIdentity caller = getCallerIdentity(callerPackageName); enforcePermission(MANAGE_DEVICE_POLICY_STORAGE_LIMIT, caller.getPackageName(), caller.getUserId()); diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java index a4ef629492e7..7ffd0eca9b96 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionService.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java @@ -20,7 +20,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.supervision.ISupervisionManager; import android.content.Context; - +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; import com.android.internal.util.DumpUtils; import com.android.server.SystemService; @@ -28,7 +30,9 @@ import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; -/** Service for handling system supervision. */ +/** + * Service for handling system supervision. + */ public class SupervisionService extends ISupervisionManager.Stub { private static final String LOG_TAG = "SupervisionService"; @@ -44,8 +48,20 @@ public class SupervisionService extends ISupervisionManager.Stub { } @Override - protected void dump(@NonNull FileDescriptor fd, - @NonNull PrintWriter fout, @Nullable String[] args) { + public void onShellCommand( + @Nullable FileDescriptor in, + @Nullable FileDescriptor out, + @Nullable FileDescriptor err, + @NonNull String[] args, + @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + new SupervisionServiceShellCommand(this) + .exec(this, in, out, err, args, callback, resultReceiver); + } + + @Override + protected void dump( + @NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, fout)) return; fout.println("Supervision enabled: " + isSupervisionEnabled()); diff --git a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java new file mode 100644 index 000000000000..3aba24a3d4a5 --- /dev/null +++ b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java @@ -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.server.supervision; + +import android.os.ShellCommand; + +import java.io.PrintWriter; + +public class SupervisionServiceShellCommand extends ShellCommand { + private final SupervisionService mService; + + public SupervisionServiceShellCommand(SupervisionService mService) { + this.mService = mService; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(null); + } + final PrintWriter pw = getOutPrintWriter(); + switch (cmd) { + case "help": return help(pw); + case "is-enabled": return isEnabled(pw); + default: return handleDefaultCommands(cmd); + } + } + + private int help(PrintWriter pw) { + pw.println("Supervision service commands:"); + pw.println(" help"); + pw.println(" Prints this help text"); + pw.println(" is-enabled"); + pw.println(" Is supervision enabled"); + return 0; + } + + private int isEnabled(PrintWriter pw) { + pw.println(mService.isSupervisionEnabled()); + return 0; + } + + @Override + public void onHelp() { + help(getOutPrintWriter()); + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 026fcc486a92..8f9382729119 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -27,6 +27,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_D import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY; import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; +import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.server.display.ExternalDisplayPolicy.ENABLE_ON_CONNECT; @@ -190,8 +191,8 @@ public class DisplayManagerServiceTest { private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display"; private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests"; private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS = STANDARD_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED; @@ -233,6 +234,8 @@ public class DisplayManagerServiceTest { private UserManager mUserManager; + private int[] mAllowedHdrOutputTypes; + private final DisplayManagerService.Injector mShortMockedInjector = new DisplayManagerService.Injector() { @Override @@ -251,11 +254,12 @@ public class DisplayManagerServiceTest { displayAdapterListener, flags, mMockedDisplayNotificationManager, new LocalDisplayAdapter.Injector() { - @Override - public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { - return mSurfaceControlProxy; - } - }); + @Override + public LocalDisplayAdapter.SurfaceControlProxy + getSurfaceControlProxy() { + return mSurfaceControlProxy; + } + }); } @Override @@ -315,7 +319,7 @@ public class DisplayManagerServiceTest { @Override int setHdrConversionMode(int conversionMode, int preferredHdrOutputType, - int[] autoHdrTypes) { + int[] allowedHdrOutputTypes) { mHdrConversionMode = conversionMode; mPreferredHdrOutputType = preferredHdrOutputType; return Display.HdrCapabilities.HDR_TYPE_INVALID; @@ -1287,11 +1291,11 @@ public class DisplayManagerServiceTest { .setUniqueId("uniqueId --- mirror display"); assertThrows(SecurityException.class, () -> { localService.createVirtualDisplay( - builder.build(), - mMockAppToken /* callback */, - null /* virtualDeviceToken */, - mock(DisplayWindowPolicyController.class), - PACKAGE_NAME); + builder.build(), + mMockAppToken /* callback */, + null /* virtualDeviceToken */, + mock(DisplayWindowPolicyController.class), + PACKAGE_NAME); }); } @@ -1425,7 +1429,7 @@ public class DisplayManagerServiceTest { // The virtual display should not have FLAG_ALWAYS_UNLOCKED set. assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags - & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED)); + & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED)); } /** @@ -1458,7 +1462,7 @@ public class DisplayManagerServiceTest { // The virtual display should not have FLAG_PRESENTATION set. assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags - & DisplayDeviceInfo.FLAG_PRESENTATION)); + & DisplayDeviceInfo.FLAG_PRESENTATION)); } @Test @@ -2350,6 +2354,7 @@ public class DisplayManagerServiceTest { HdrConversionMode.HDR_CONVERSION_FORCE, Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION); displayManager.setHdrConversionModeInternal(mode); + assertEquals(mode, displayManager.getHdrConversionModeSettingInternal()); assertEquals(mode.getConversionMode(), mHdrConversionMode); assertEquals(mode.getPreferredHdrOutputType(), mPreferredHdrOutputType); @@ -2394,6 +2399,86 @@ public class DisplayManagerServiceTest { } @Test + public void testSetAreUserDisabledHdrTypesAllowed_withFalse_whenHdrDisabled_stripsHdrType() { + DisplayManagerService displayManager = new DisplayManagerService( + mContext, new BasicInjector() { + @Override + int setHdrConversionMode(int conversionMode, int preferredHdrOutputType, + int[] allowedTypes) { + mAllowedHdrOutputTypes = allowedTypes; + return Display.HdrCapabilities.HDR_TYPE_INVALID; + } + + // Overriding this method to capture the allowed HDR type + @Override + int[] getSupportedHdrOutputTypes() { + return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION}; + } + }); + + // Setup: no HDR types disabled, userDisabledTypes allowed, system conversion + displayManager.setUserDisabledHdrTypesInternal(new int [0]); + displayManager.setAreUserDisabledHdrTypesAllowedInternal(true); + displayManager.setHdrConversionModeInternal( + new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM)); + + assertEquals(1, mAllowedHdrOutputTypes.length); + assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == mAllowedHdrOutputTypes[0]); + + // Action: disable Dolby Vision, set userDisabledTypes not allowed + displayManager.setUserDisabledHdrTypesInternal( + new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION}); + displayManager.setAreUserDisabledHdrTypesAllowedInternal(false); + + assertEquals(0, mAllowedHdrOutputTypes.length); + } + + @Test + public void testGetEnabledHdrTypesLocked_whenTypesDisabled_stripsDisabledTypes() { + DisplayManagerService displayManager = new DisplayManagerService( + mContext, new BasicInjector() { + @Override + int[] getSupportedHdrOutputTypes() { + return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION}; + } + }); + + displayManager.setUserDisabledHdrTypesInternal(new int [0]); + displayManager.setAreUserDisabledHdrTypesAllowedInternal(true); + int [] enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes(); + assertEquals(1, enabledHdrOutputTypes.length); + assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]); + + displayManager.setAreUserDisabledHdrTypesAllowedInternal(false); + enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes(); + assertEquals(1, enabledHdrOutputTypes.length); + assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]); + + displayManager.setUserDisabledHdrTypesInternal( + new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION}); + enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes(); + assertEquals(0, enabledHdrOutputTypes.length); + } + + @Test + public void testSetHdrConversionModeInternal_isForceSdrIsUpdated() { + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper(); + FakeDisplayDevice displayDevice = + createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL); + LogicalDisplay logicalDisplay = + logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true); + + displayManager.setHdrConversionModeInternal( + new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, HDR_TYPE_INVALID)); + assertTrue(logicalDisplay.getDisplayInfoLocked().isForceSdr); + + displayManager.setHdrConversionModeInternal( + new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM)); + assertFalse(logicalDisplay.getDisplayInfoLocked().isForceSdr); + } + + @Test public void testReturnsRefreshRateForDisplayAndSensor_proximitySensorSet() { DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); DisplayManagerInternal localService = displayManager.new LocalService(); @@ -3351,7 +3436,7 @@ public class DisplayManagerServiceTest { } private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager, - Display.Mode[] modes) { + Display.Mode[] modes) { FakeDisplayDevice displayDevice = new FakeDisplayDevice(); DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo(); displayDeviceInfo.supportedModes = modes; @@ -3607,9 +3692,9 @@ public class DisplayManagerServiceTest { public void setUserPreferredDisplayModeLocked(Display.Mode preferredMode) { for (Display.Mode mode : mDisplayDeviceInfo.supportedModes) { if (mode.matchesIfValid( - preferredMode.getPhysicalWidth(), - preferredMode.getPhysicalHeight(), - preferredMode.getRefreshRate())) { + preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight(), + preferredMode.getRefreshRate())) { mPreferredMode = mode; break; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 957ee06b6e27..598d3a3a9f8a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -23,6 +23,8 @@ import static android.view.MotionEvent.ACTION_POINTER_DOWN; import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT; import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; +import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE; +import static android.view.MotionEvent.TOOL_TYPE_FINGER; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; @@ -1414,6 +1416,49 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + public void testSynthesizedGestureEventsDoNotMoveMagnifierViewport() { + final EventCaptor eventCaptor = new EventCaptor(); + mMgh.setNext(eventCaptor); + + float centerX = + (INITIAL_MAGNIFICATION_BOUNDS.left + INITIAL_MAGNIFICATION_BOUNDS.width()) / 2.0f; + float centerY = + (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f; + float scale = 5.6f; // value is unimportant but unique among tests to increase coverage. + mFullScreenMagnificationController.setScaleAndCenter( + DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1); + centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0); + centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0); + + // Second finger down on trackpad starts a synthesized two-finger swipe with source + // mouse. + MotionEvent downEvent = motionEvent(centerX, centerY, ACTION_DOWN, + TOOL_TYPE_FINGER, CLASSIFICATION_TWO_FINGER_SWIPE); + send(downEvent, InputDevice.SOURCE_MOUSE); + fastForward(20); + + // Two-finger swipe creates a synthesized move event, and shouldn't impact magnifier + // viewport. + MotionEvent moveEvent = motionEvent(centerX - 42, centerY - 42, ACTION_MOVE, + TOOL_TYPE_FINGER, CLASSIFICATION_TWO_FINGER_SWIPE); + send(moveEvent, InputDevice.SOURCE_MOUSE); + fastForward(20); + + assertThat(mFullScreenMagnificationController.getCenterX(DISPLAY_0)).isEqualTo(centerX); + assertThat(mFullScreenMagnificationController.getCenterY(DISPLAY_0)).isEqualTo(centerY); + + // The events were not consumed by magnifier. + assertThat(eventCaptor.mEvents.size()).isEqualTo(2); + assertThat(eventCaptor.mEvents.get(0).getSource()).isEqualTo(InputDevice.SOURCE_MOUSE); + assertThat(eventCaptor.mEvents.get(1).getSource()).isEqualTo(InputDevice.SOURCE_MOUSE); + + final List<Integer> expectedActions = new ArrayList(); + expectedActions.add(Integer.valueOf(ACTION_DOWN)); + expectedActions.add(Integer.valueOf(ACTION_MOVE)); + assertActionsInOrder(eventCaptor.mEvents, expectedActions); + } + + @Test @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) public void testMouseHoverMoveEventsDoNotMoveMagnifierViewport() { runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE); @@ -2130,6 +2175,30 @@ public class FullScreenMagnificationGestureHandlerTest { return MotionEvent.obtain(mLastDownTime, mClock.now(), action, x, y, 0); } + private MotionEvent motionEvent(float x, float y, int action, int toolType, + int classification) { + // Create a generic motion event to populate the parameters. + MotionEvent event = motionEvent(x, y, action); + int pointerCount = event.getPointerCount(); + MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount]; + MotionEvent.PointerProperties[] properties = + new MotionEvent.PointerProperties[pointerCount]; + for (int i = 0; i < pointerCount; i++) { + properties[i] = new MotionEvent.PointerProperties(); + event.getPointerProperties(i, properties[i]); + properties[i].toolType = toolType; + coords[i] = new MotionEvent.PointerCoords(); + event.getPointerCoords(i, coords[i]); + } + // Apply the custom classification. + return MotionEvent.obtain(event.getDownTime(), event.getEventTime(), action, + /*pointerCount=*/1, properties, coords, + event.getMetaState(), event.getButtonState(), + event.getXPrecision(), event.getYPrecision(), event.getDeviceId(), + event.getEdgeFlags(), event.getSource(), event.getDisplayId(), event.getFlags(), + classification); + } + private MotionEvent mouseEvent(float x, float y, int action) { return fromMouse(motionEvent(x, y, action)); } |