diff options
143 files changed, 2687 insertions, 1202 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index b4127c5660f7..3c5686bd6d13 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -26,6 +26,7 @@ aconfig_declarations_group { "android.app.flags-aconfig-java", "android.app.ondeviceintelligence-aconfig-java", "android.app.smartspace.flags-aconfig-java", + "android.app.supervision.flags-aconfig-java", "android.app.usage.flags-aconfig-java", "android.app.wearable.flags-aconfig-java", "android.appwidget.flags-aconfig-java", @@ -1212,6 +1213,21 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Supervision +aconfig_declarations { + name: "android.app.supervision.flags-aconfig", + exportable: true, + package: "android.app.supervision.flags", + container: "system", + srcs: ["core/java/android/app/supervision/flags.aconfig"], +} + +java_aconfig_library { + name: "android.app.supervision.flags-aconfig-java", + aconfig_declarations: "android.app.supervision.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // SurfaceFlinger java_aconfig_library { name: "surfaceflinger_flags_java_lib", diff --git a/core/api/current.txt b/core/api/current.txt index d8eac414cfeb..861be4079acc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -7968,7 +7968,7 @@ package android.app.admin { 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 @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String SECURITY_LOGGING_POLICY = "securityLogging"; + field public static final String SECURITY_LOGGING_POLICY = "securityLogging"; field public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled"; 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/system-current.txt b/core/api/system-current.txt index 2d3d89123653..5413c6606bcb 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -201,7 +201,7 @@ package android { field public static final String MANAGE_DEFAULT_APPLICATIONS = "android.permission.MANAGE_DEFAULT_APPLICATIONS"; field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS"; - field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING"; + field public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING"; field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"; field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS"; field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION"; @@ -1296,7 +1296,7 @@ package android.app.admin { } public final class DevicePolicyIdentifiers { - field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String AUDIT_LOGGING_POLICY = "auditLogging"; + field public static final String AUDIT_LOGGING_POLICY = "auditLogging"; } public class DevicePolicyKeyguardService extends android.app.Service { @@ -1308,7 +1308,7 @@ package android.app.admin { public class DevicePolicyManager { method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String); - method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback(); + method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account); @@ -1328,7 +1328,7 @@ package android.app.admin { method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException; method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException; method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState(); - method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled(); + method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled(); method public boolean isDeviceManaged(); method @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") @RequiresPermission(android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE) public boolean isDevicePotentiallyStolen(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned(); @@ -1344,8 +1344,8 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException; - method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean); - method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>); + method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean); + method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean); method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int); @@ -3694,9 +3694,11 @@ package android.companion.virtual.sensor { method public int getMinDelay(); method @NonNull public String getName(); method public float getPower(); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public int getReportingMode(); method public float getResolution(); method public int getType(); method @Nullable public String getVendor(); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public boolean isWakeUpSensor(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR; } @@ -3710,8 +3712,10 @@ package android.companion.virtual.sensor { method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setMaximumRange(float); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setMinDelay(int); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setPower(float); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setReportingMode(int); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setResolution(float); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String); + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setWakeUpSensor(boolean); } public interface VirtualSensorDirectChannelCallback { @@ -8083,6 +8087,7 @@ package android.media.tv.tuner { public class Tuner implements java.lang.AutoCloseable { ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @Nullable String, int); method public int applyFrontend(@NonNull android.media.tv.tuner.frontend.FrontendInfo); + method @FlaggedApi("android.media.tv.flags.tuner_w_apis") @RequiresPermission(allOf={"android.permission.TUNER_RESOURCE_ACCESS", "android.permission.ACCESS_TV_TUNER"}) public int applyFrontendByType(int); method public int cancelScanning(); method public int cancelTuning(); method public void clearOnTuneEventListener(); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index cb38cf297cf6..8b3ee24db025 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -48,6 +48,8 @@ import android.app.sdksandbox.SdkSandboxManagerFrameworkInitializer; import android.app.search.SearchUiManager; import android.app.slice.SliceManager; import android.app.smartspace.SmartspaceManager; +import android.app.supervision.ISupervisionManager; +import android.app.supervision.SupervisionManager; import android.app.time.TimeManager; import android.app.timedetector.TimeDetector; import android.app.timedetector.TimeDetectorImpl; @@ -1703,6 +1705,21 @@ public final class SystemServiceRegistry { return new E2eeContactKeysManager(ctx); }}); + registerService(Context.SUPERVISION_SERVICE, SupervisionManager.class, + new CachedServiceFetcher<>() { + @Override + public SupervisionManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + if (!android.app.supervision.flags.Flags.supervisionApi()) { + throw new ServiceNotFoundException( + "SupervisionManager is not supported"); + } + IBinder iBinder = ServiceManager.getServiceOrThrow( + Context.SUPERVISION_SERVICE); + ISupervisionManager service = ISupervisionManager.Stub.asInterface(iBinder); + return new SupervisionManager(ctx, service); + } + }); // DO NOT do a flag check like this unless the flag is read-only. // (because this code is executed during preload in zygote.) // If the flag is mutable, the check should be inside CachedServiceFetcher. diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java index eeaf0b3706fc..156512a90295 100644 --- a/core/java/android/app/admin/DevicePolicyIdentifiers.java +++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java @@ -17,7 +17,6 @@ package android.app.admin; import static android.app.admin.flags.Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED; -import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED; import android.annotation.FlaggedApi; import android.annotation.NonNull; @@ -50,7 +49,6 @@ public final class DevicePolicyIdentifiers { /** * String identifier for {@link DevicePolicyManager#setSecurityLoggingEnabled}. */ - @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) public static final String SECURITY_LOGGING_POLICY = "securityLogging"; /** @@ -58,7 +56,6 @@ public final class DevicePolicyIdentifiers { * * @hide */ - @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) @SystemApi public static final String AUDIT_LOGGING_POLICY = "auditLogging"; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ba1dc5677b21..5088ea6b603c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -60,7 +60,6 @@ import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED; import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED; import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_PROVISIONING_FIX_ENABLED; import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED; -import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_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; @@ -14335,7 +14334,6 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean enabled) { throwIfParentInstance("setAuditLogEnabled"); @@ -14352,7 +14350,6 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled() { throwIfParentInstance("isAuditLogEnabled"); @@ -14374,7 +14371,6 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback( @NonNull @CallbackExecutor Executor executor, @@ -14401,7 +14397,6 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback() { throwIfParentInstance("clearAuditLogEventCallback"); diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 9148e3c3a072..56f47922b078 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -105,6 +105,7 @@ flag { bug: "289520697" } +# Fully rolled out and must not be used. flag { name: "security_log_v2_enabled" is_exported: true diff --git a/core/java/android/app/supervision/ISupervisionManager.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl new file mode 100644 index 000000000000..8d25cad2fc67 --- /dev/null +++ b/core/java/android/app/supervision/ISupervisionManager.aidl @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2024, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.supervision; + +/** + * Internal IPC interface to the supervision service. + * {@hide} + */ +interface ISupervisionManager { + boolean isSupervisionEnabled(); +} diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java new file mode 100644 index 000000000000..8611a92074c0 --- /dev/null +++ b/core/java/android/app/supervision/SupervisionManager.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.supervision; + +import android.annotation.SystemService; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.os.RemoteException; + +/** + * Service for handling parental supervision. + * + * @hide + */ +@SystemService(Context.SUPERVISION_SERVICE) +public class SupervisionManager { + private final Context mContext; + private final ISupervisionManager mService; + + /** + * @hide + */ + @UnsupportedAppUsage + public SupervisionManager(Context context, ISupervisionManager service) { + mContext = context; + mService = service; + } + + /** + * Returns whether the device is supervised. + * + * @hide + */ + public boolean isSupervisionEnabled() { + try { + return mService.isSupervisionEnabled(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + +} diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig new file mode 100644 index 000000000000..bcb5b3636c95 --- /dev/null +++ b/core/java/android/app/supervision/flags.aconfig @@ -0,0 +1,10 @@ +package: "android.app.supervision.flags" +container: "system" + +flag { + name: "supervision_api" + is_exported: true + namespace: "supervision" + description: "Flag to enable the SupervisionService" + bug: "340351729" +}
\ No newline at end of file diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index c3c3f0ef32e1..b4c36e1bc513 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -103,3 +103,10 @@ flag { description: "Expose multiple surface for the virtual camera owner for different stream resolution" bug: "341083465" } + +flag { + namespace: "virtual_devices" + name: "device_aware_display_power" + description: "Device awareness in power and display APIs" + bug: "285020111" +} diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java index 21ad914bbc29..82f183fd1d62 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java @@ -17,12 +17,14 @@ package android.companion.virtual.sensor; +import android.annotation.FlaggedApi; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.companion.virtualdevice.flags.Flags; import android.hardware.Sensor; import android.hardware.SensorDirectChannel; import android.os.Parcel; @@ -42,6 +44,13 @@ import java.util.Objects; public final class VirtualSensorConfig implements Parcelable { private static final String TAG = "VirtualSensorConfig"; + // Defined in sensors.h + private static final int FLAG_WAKE_UP_SENSOR = 1; + + // Mask for the reporting mode, bit 2, 3, 4. + private static final int REPORTING_MODE_MASK = 0xE; + private static final int REPORTING_MODE_SHIFT = 1; + // Mask for direct mode highest rate level, bit 7, 8, 9. private static final int DIRECT_REPORT_MASK = 0x380; private static final int DIRECT_REPORT_SHIFT = 7; @@ -193,8 +202,7 @@ public final class VirtualSensorConfig implements Parcelable { @SensorDirectChannel.RateLevel public int getHighestDirectReportRateLevel() { int rateLevel = ((mFlags & DIRECT_REPORT_MASK) >> DIRECT_REPORT_SHIFT); - return rateLevel <= SensorDirectChannel.RATE_VERY_FAST - ? rateLevel : SensorDirectChannel.RATE_VERY_FAST; + return Math.min(rateLevel, SensorDirectChannel.RATE_VERY_FAST); } /** @@ -215,6 +223,28 @@ public final class VirtualSensorConfig implements Parcelable { } /** + * Returns whether the sensor is a wake-up sensor. + * + * @see Builder#setWakeUpSensor(boolean) + * @see Sensor#isWakeUpSensor() + */ + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + public boolean isWakeUpSensor() { + return (mFlags & FLAG_WAKE_UP_SENSOR) > 0; + } + + /** + * Returns the reporting mode of this sensor. + * + * @see Builder#setReportingMode(int) + * @see Sensor#getReportingMode() + */ + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + public int getReportingMode() { + return ((mFlags & REPORTING_MODE_MASK) >> REPORTING_MODE_SHIFT); + } + + /** * Returns the sensor flags. * * @hide @@ -383,6 +413,45 @@ public final class VirtualSensorConfig implements Parcelable { } return this; } + + /** + * Sets whether this sensor is a wake up sensor. + * + * @see Sensor#isWakeUpSensor() + */ + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @NonNull + public VirtualSensorConfig.Builder setWakeUpSensor(boolean wakeUpSensor) { + if (wakeUpSensor) { + mFlags |= FLAG_WAKE_UP_SENSOR; + } else { + mFlags &= ~FLAG_WAKE_UP_SENSOR; + } + return this; + } + + /** + * Sets the reporting mode of this sensor. + * + * @throws IllegalArgumentException if the reporting mode is not one of + * {@link Sensor#REPORTING_MODE_CONTINUOUS}, {@link Sensor#REPORTING_MODE_ON_CHANGE}, + * {@link Sensor#REPORTING_MODE_ONE_SHOT}, or + * {@link Sensor#REPORTING_MODE_SPECIAL_TRIGGER}. + * + * @see Sensor#getReportingMode() + */ + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @NonNull + public VirtualSensorConfig.Builder setReportingMode(int reportingMode) { + if (reportingMode != Sensor.REPORTING_MODE_CONTINUOUS + && reportingMode != Sensor.REPORTING_MODE_ON_CHANGE + && reportingMode != Sensor.REPORTING_MODE_ONE_SHOT + && reportingMode != Sensor.REPORTING_MODE_SPECIAL_TRIGGER) { + throw new IllegalArgumentException("Invalid reporting mode: " + reportingMode); + } + mFlags |= reportingMode << REPORTING_MODE_SHIFT; + return this; + } } @NonNull diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 9c711bcb1521..3bf0f0324716 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -6711,6 +6711,16 @@ public abstract class Context { public static final String PROTOLOG_CONFIGURATION_SERVICE = "protolog_configuration"; /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.app.supervision.SupervisionManager}. + * + * @see #getSystemService(String) + * @see android.app.supervision.SupervisionManager + * @hide + */ + public static final String SUPERVISION_SERVICE = "supervision"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index b7556dfb51af..4bc3dbedeb94 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -708,9 +708,16 @@ public class Binder implements IBinder { * * @hide */ + @android.ravenwood.annotation.RavenwoodReplace @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) public final native void markVintfStability(); + /** @hide */ + private void markVintfStability$ravenwood() { + // This is not useful for Ravenwood which uses local binder. + // TODO(b/361785059): Use real native libbinder. + } + /** * Use a VINTF-stability binder w/o VINTF requirements. Should be called * on a binder before it is sent out of process. diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java index 013ec5f35761..244257cb61c8 100644 --- a/core/java/android/service/dreams/DreamOverlayService.java +++ b/core/java/android/service/dreams/DreamOverlayService.java @@ -51,6 +51,8 @@ public abstract class DreamOverlayService extends Service { */ private Executor mExecutor; + private boolean mCurrentRedirectToWake; + // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding // requests to the {@link DreamOverlayService} private static class OverlayClient extends IDreamOverlayClient.Stub { @@ -132,6 +134,10 @@ public abstract class DreamOverlayService extends Service { mExecutor.execute(() -> { endDreamInternal(mCurrentClient); mCurrentClient = client; + if (Flags.dreamWakeRedirect()) { + mCurrentClient.redirectWake(mCurrentRedirectToWake); + } + onStartDream(params); }); } @@ -282,8 +288,10 @@ public abstract class DreamOverlayService extends Service { return; } + mCurrentRedirectToWake = redirect; + if (mCurrentClient == null) { - throw new IllegalStateException("redirected wake with no dream present"); + return; } mCurrentClient.redirectWake(redirect); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 6f8838619808..3b5286a04b3d 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3093,74 +3093,74 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { - final boolean handled; - - // Canceling motions is a special case. We don't need to perform any transformations - // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); - if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { - event.setAction(MotionEvent.ACTION_CANCEL); - if (child == null) { - handled = super.dispatchTouchEvent(event); - } else { - handled = child.dispatchTouchEvent(event); + try { + final boolean handled; + if (cancel) { + event.setAction(MotionEvent.ACTION_CANCEL); } - event.setAction(oldAction); - return handled; - } - // Calculate the number of pointers to deliver. - final int oldPointerIdBits = event.getPointerIdBits(); - final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; - - // If for some reason we ended up in an inconsistent state where it looks like we - // might produce a motion event with no pointers in it, then drop the event. - if (newPointerIdBits == 0) { - return false; - } + // Calculate the number of pointers to deliver. + final int oldPointerIdBits = event.getPointerIdBits(); + int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; - // If the number of pointers is the same and we don't need to perform any fancy - // irreversible transformations, then we can reuse the motion event for this - // dispatch as long as we are careful to revert any changes we make. - // Otherwise we need to make a copy. - final MotionEvent transformedEvent; - if (newPointerIdBits == oldPointerIdBits) { - if (child == null || child.hasIdentityMatrix()) { - if (child == null) { - handled = super.dispatchTouchEvent(event); + // If for some reason we ended up in an inconsistent state where it looks like we + // might produce a non-cancel motion event with no pointers in it, then drop the event. + // Make sure that we don't drop any cancel events. + if (newPointerIdBits == 0) { + if (event.getAction() != MotionEvent.ACTION_CANCEL) { + return false; } else { - final float offsetX = mScrollX - child.mLeft; - final float offsetY = mScrollY - child.mTop; - event.offsetLocation(offsetX, offsetY); + newPointerIdBits = oldPointerIdBits; + } + } + + // If the number of pointers is the same and we don't need to perform any fancy + // irreversible transformations, then we can reuse the motion event for this + // dispatch as long as we are careful to revert any changes we make. + // Otherwise we need to make a copy. + final MotionEvent transformedEvent; + if (newPointerIdBits == oldPointerIdBits) { + if (child == null || child.hasIdentityMatrix()) { + if (child == null) { + handled = super.dispatchTouchEvent(event); + } else { + final float offsetX = mScrollX - child.mLeft; + final float offsetY = mScrollY - child.mTop; + event.offsetLocation(offsetX, offsetY); - handled = child.dispatchTouchEvent(event); + handled = child.dispatchTouchEvent(event); - event.offsetLocation(-offsetX, -offsetY); + event.offsetLocation(-offsetX, -offsetY); + } + return handled; } - return handled; + transformedEvent = MotionEvent.obtain(event); + } else { + transformedEvent = event.split(newPointerIdBits); } - transformedEvent = MotionEvent.obtain(event); - } else { - transformedEvent = event.split(newPointerIdBits); - } - // Perform any necessary transformations and dispatch. - if (child == null) { - handled = super.dispatchTouchEvent(transformedEvent); - } else { - final float offsetX = mScrollX - child.mLeft; - final float offsetY = mScrollY - child.mTop; - transformedEvent.offsetLocation(offsetX, offsetY); - if (! child.hasIdentityMatrix()) { - transformedEvent.transform(child.getInverseMatrix()); + // Perform any necessary transformations and dispatch. + if (child == null) { + handled = super.dispatchTouchEvent(transformedEvent); + } else { + final float offsetX = mScrollX - child.mLeft; + final float offsetY = mScrollY - child.mTop; + transformedEvent.offsetLocation(offsetX, offsetY); + if (!child.hasIdentityMatrix()) { + transformedEvent.transform(child.getInverseMatrix()); + } + + handled = child.dispatchTouchEvent(transformedEvent); } - handled = child.dispatchTouchEvent(transformedEvent); - } + // Done. + transformedEvent.recycle(); + return handled; - // Done. - transformedEvent.recycle(); - return handled; + } finally { + event.setAction(oldAction); + } } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 0e0262715d2f..0e1625aaedd8 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4388,14 +4388,7 @@ public final class ViewRootImpl implements ViewParent, mReportNextDraw = false; mLastReportNextDrawReason = null; mActiveSurfaceSyncGroup = null; - if (mHasPendingTransactions) { - // TODO: We shouldn't ever actually hit this, it means mPendingTransaction wasn't - // merged with a sync group or BLASTBufferQueue before making it to this point - // But better a one or two frame flicker than steady-state broken from dropping - // whatever is in this transaction - mPendingTransaction.apply(); - mHasPendingTransactions = false; - } + mHasPendingTransactions = false; mSyncBuffer = false; if (isInWMSRequestedSync()) { mWmsRequestSyncGroup.markSyncReady(); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 03a26722da8f..0acc6bde5bfd 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -20,6 +20,7 @@ import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP; import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID; import static com.android.graphics.hwui.flags.Flags.highContrastTextSmallTextRect; +import static com.android.text.flags.Flags.contextMenuHideUnavailableItems; import android.R; import android.animation.ValueAnimator; @@ -3250,62 +3251,135 @@ public class Editor { final int menuItemOrderShare = 9; final int menuItemOrderAutofill = 10; - menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo, - com.android.internal.R.string.undo) - .setAlphabeticShortcut('z') - .setOnMenuItemClickListener(mOnContextMenuItemClickListener) - .setIcon(a.getDrawable(0)) - .setEnabled(mTextView.canUndo()); - menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, menuItemOrderRedo, - com.android.internal.R.string.redo) - .setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON) - .setOnMenuItemClickListener(mOnContextMenuItemClickListener) - .setIcon(a.getDrawable(1)) - .setEnabled(mTextView.canRedo()); - - menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, menuItemOrderCut, - com.android.internal.R.string.cut) - .setAlphabeticShortcut('x') - .setOnMenuItemClickListener(mOnContextMenuItemClickListener) - .setIcon(a.getDrawable(2)) - .setEnabled(mTextView.canCut()); - menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, menuItemOrderCopy, - com.android.internal.R.string.copy) - .setAlphabeticShortcut('c') - .setOnMenuItemClickListener(mOnContextMenuItemClickListener) - .setIcon(a.getDrawable(3)) - .setEnabled(mTextView.canCopy()); - menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, menuItemOrderPaste, - com.android.internal.R.string.paste) - .setAlphabeticShortcut('v') - .setEnabled(mTextView.canPaste()) - .setIcon(a.getDrawable(4)) - .setOnMenuItemClickListener(mOnContextMenuItemClickListener); - menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT, - menuItemOrderPasteAsPlainText, - com.android.internal.R.string.paste_as_plain_text) - .setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON) - .setEnabled(mTextView.canPasteAsPlainText()) - .setIcon(a.getDrawable(4)) - .setOnMenuItemClickListener(mOnContextMenuItemClickListener); - menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL, - menuItemOrderSelectAll, com.android.internal.R.string.selectAll) - .setAlphabeticShortcut('a') - .setEnabled(mTextView.canSelectAllText()) - .setIcon(a.getDrawable(5)) - .setOnMenuItemClickListener(mOnContextMenuItemClickListener); - - menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, menuItemOrderShare, - com.android.internal.R.string.share) - .setEnabled(mTextView.canShare()) - .setIcon(a.getDrawable(6)) - .setOnMenuItemClickListener(mOnContextMenuItemClickListener); - final String selected = mTextView.getSelectedText(); - menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill, - android.R.string.autofill) - .setEnabled(mTextView.canRequestAutofill() - && (selected == null || selected.isEmpty())) - .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + if (contextMenuHideUnavailableItems()) { + if (mTextView.canUndo()) { + menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo, + com.android.internal.R.string.undo) + .setAlphabeticShortcut('z') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(0)); + } + + if (mTextView.canRedo()) { + menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, menuItemOrderRedo, + com.android.internal.R.string.redo) + .setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(1)); + } + + if (mTextView.canCut()) { + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, menuItemOrderCut, + com.android.internal.R.string.cut) + .setAlphabeticShortcut('x') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(2)); + } + + if (mTextView.canCopy()) { + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, menuItemOrderCopy, + com.android.internal.R.string.copy) + .setAlphabeticShortcut('c') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(3)); + } + + if (mTextView.canPaste()) { + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, menuItemOrderPaste, + com.android.internal.R.string.paste) + .setAlphabeticShortcut('v') + .setIcon(a.getDrawable(4)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + } + + if (mTextView.canPasteAsPlainText()) { + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT, + menuItemOrderPasteAsPlainText, + com.android.internal.R.string.paste_as_plain_text) + .setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON) + .setIcon(a.getDrawable(4)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + } + + if (mTextView.canSelectAllText()) { + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL, + menuItemOrderSelectAll, com.android.internal.R.string.selectAll) + .setAlphabeticShortcut('a') + .setIcon(a.getDrawable(5)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + } + + if (mTextView.canShare()) { + menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, menuItemOrderShare, + com.android.internal.R.string.share) + .setIcon(a.getDrawable(6)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + } + + final String selected = mTextView.getSelectedText(); + if (mTextView.canRequestAutofill() && (selected == null || selected.isEmpty())) { + menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill, + android.R.string.autofill) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + } + } else { + menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo, + com.android.internal.R.string.undo) + .setAlphabeticShortcut('z') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(0)) + .setEnabled(mTextView.canUndo()); + menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, menuItemOrderRedo, + com.android.internal.R.string.redo) + .setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(1)) + .setEnabled(mTextView.canRedo()); + + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, menuItemOrderCut, + com.android.internal.R.string.cut) + .setAlphabeticShortcut('x') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(2)) + .setEnabled(mTextView.canCut()); + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, menuItemOrderCopy, + com.android.internal.R.string.copy) + .setAlphabeticShortcut('c') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setIcon(a.getDrawable(3)) + .setEnabled(mTextView.canCopy()); + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, menuItemOrderPaste, + com.android.internal.R.string.paste) + .setAlphabeticShortcut('v') + .setEnabled(mTextView.canPaste()) + .setIcon(a.getDrawable(4)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT, + menuItemOrderPasteAsPlainText, + com.android.internal.R.string.paste_as_plain_text) + .setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON) + .setEnabled(mTextView.canPasteAsPlainText()) + .setIcon(a.getDrawable(4)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL, + menuItemOrderSelectAll, com.android.internal.R.string.selectAll) + .setAlphabeticShortcut('a') + .setEnabled(mTextView.canSelectAllText()) + .setIcon(a.getDrawable(5)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + + menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, menuItemOrderShare, + com.android.internal.R.string.share) + .setEnabled(mTextView.canShare()) + .setIcon(a.getDrawable(6)) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + final String selected = mTextView.getSelectedText(); + menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill, + android.R.string.autofill) + .setEnabled(mTextView.canRequestAutofill() + && (selected == null || selected.isEmpty())) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + } a.recycle(); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1ea20fa85bd4..a346a679ea00 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -15552,15 +15552,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - boolean canUndo() { + /** @hide */ + @VisibleForTesting + public boolean canUndo() { return mEditor != null && mEditor.canUndo(); } - boolean canRedo() { + /** @hide */ + @VisibleForTesting + public boolean canRedo() { return mEditor != null && mEditor.canRedo(); } - boolean canCut() { + /** @hide */ + @VisibleForTesting + public boolean canCut() { if (hasPasswordTransformationMethod()) { return false; } @@ -15573,7 +15579,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - boolean canCopy() { + /** @hide */ + @VisibleForTesting + public boolean canCopy() { if (hasPasswordTransformationMethod()) { return false; } @@ -15594,7 +15602,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions(); } - boolean canShare() { + /** @hide */ + @VisibleForTesting + public boolean canShare() { if (!getContext().canStartActivityForResult() || !isDeviceProvisioned() || !getContext().getResources().getBoolean( com.android.internal.R.bool.config_textShareSupported)) { @@ -15613,8 +15623,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; } + /** @hide */ + @VisibleForTesting @UnsupportedAppUsage - boolean canPaste() { + public boolean canPaste() { return (mText instanceof Editable && mEditor != null && mEditor.mKeyListener != null && getSelectionStart() >= 0 @@ -15622,7 +15634,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener && getClipboardManagerForUser().hasPrimaryClip()); } - boolean canPasteAsPlainText() { + /** @hide */ + @VisibleForTesting + public boolean canPasteAsPlainText() { if (!canPaste()) { return false; } @@ -15644,7 +15658,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return canShare(); } - boolean canSelectAllText() { + /** @hide */ + @VisibleForTesting + public boolean canSelectAllText() { return canSelectText() && !hasPasswordTransformationMethod() && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); } diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 4f848175cd99..217bca77af0c 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -225,3 +225,13 @@ flag { description: "Adds a minimize button the the caption bar" bug: "356843241" } + +flag { + name: "skip_compat_ui_education_in_desktop_mode" + namespace: "lse_desktop_experience" + description: "Ignore Compat UI educations when in Desktop Mode." + bug: "357062954" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java index 1c26687c97e9..2e0ff3db6c50 100644 --- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java +++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java @@ -34,6 +34,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.UserSh import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; @@ -176,24 +177,19 @@ public final class ShortcutUtils { * @param type The shortcut type. * @return Mapping key in Settings. */ + @SuppressLint("SwitchIntDef") public static String convertToKey(@UserShortcutType int type) { - switch (type) { - case SOFTWARE: - return Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS; - case GESTURE: - return Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS; - case HARDWARE: - return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE; - case TRIPLETAP: - return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED; - case TWOFINGER_DOUBLETAP: - return Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED; - case QUICK_SETTINGS: - return Settings.Secure.ACCESSIBILITY_QS_TARGETS; - default: - throw new IllegalArgumentException( - "Unsupported user shortcut type: " + type); - } + return switch (type) { + case SOFTWARE -> Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS; + case GESTURE -> Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS; + case HARDWARE -> Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE; + case TRIPLETAP -> Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED; + case TWOFINGER_DOUBLETAP -> + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED; + case QUICK_SETTINGS -> Settings.Secure.ACCESSIBILITY_QS_TARGETS; + default -> throw new IllegalArgumentException( + "Unsupported user shortcut type: " + type); + }; } /** diff --git a/core/java/com/android/internal/protolog/TEST_MAPPING b/core/java/com/android/internal/protolog/TEST_MAPPING new file mode 100644 index 000000000000..37d57eed8cf4 --- /dev/null +++ b/core/java/com/android/internal/protolog/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "ProtologPerfTests" + } + ] +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2dd560c69a8c..91c33702d3e3 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3836,7 +3836,6 @@ <!-- Allows an application to use audit logging API. @hide @SystemApi - @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") --> <permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING" android:protectionLevel="internal|role" /> diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java index bcf1053e8ddd..3e76977c99fa 100644 --- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java +++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java @@ -27,6 +27,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -40,6 +41,9 @@ import android.content.Intent; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.Icon; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.view.ContextMenu; import android.view.MenuItem; import android.view.textclassifier.TextClassification; @@ -47,9 +51,12 @@ import android.view.textclassifier.TextClassification; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.verification.VerificationMode; /** * TextViewTest tests {@link TextView}. @@ -86,6 +93,10 @@ public class TextViewContextMenuTest { private SelectionActionModeHelper mMockHelper; + @ClassRule public static final SetFlagsRule.ClassRule SET_FLAGS_CLASS_RULE = + new SetFlagsRule.ClassRule(); + @Rule public final SetFlagsRule mSetFlagsRule = SET_FLAGS_CLASS_RULE.createSetFlagsRule(); + @Before public void setUp() { mMockHelper = mock(SelectionActionModeHelper.class); @@ -234,6 +245,7 @@ public class TextViewContextMenuTest { } @Test + @DisableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) public void testAutofillMenuItemEnabledWhenNoTextSelected() { ContextMenu menu = mock(ContextMenu.class); MenuItem mockMenuItem = newMockMenuItem(); @@ -254,6 +266,7 @@ public class TextViewContextMenuTest { } @Test + @DisableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) public void testAutofillMenuItemNotEnabledWhenTextSelected() { ContextMenu menu = mock(ContextMenu.class); MenuItem mockMenuItem = newMockMenuItem(); @@ -271,4 +284,147 @@ public class TextViewContextMenuTest { verify(menu).add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt()); verify(mockAutofillMenuItem).setEnabled(false); } + + private interface EditTextSetup { + void run(EditText et); + } + + private void verifyMenuItemNotAdded(EditTextSetup setup, int id, VerificationMode times) { + ContextMenu menu = mock(ContextMenu.class); + MenuItem mockMenuItem = newMockMenuItem(); + when(menu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mockMenuItem); + EditText et = spy(new EditText(getInstrumentation().getContext())); + setup.run(et); + Editor editor = new Editor(et); + editor.setTextContextMenuItems(menu); + verify(menu, times).add(anyInt(), eq(id), anyInt(), anyInt()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuUndoNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canUndo(), + TextView.ID_UNDO, never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuUndoAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canUndo(), TextView.ID_UNDO, + times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuRedoNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canRedo(), TextView.ID_REDO, + never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuRedoAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canRedo(), TextView.ID_REDO, + times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuCutNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canCut(), TextView.ID_CUT, + never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuCutAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canCut(), TextView.ID_CUT, + times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuCopyNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canCopy(), TextView.ID_COPY, + never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuCopyAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canCopy(), TextView.ID_COPY, + times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuPasteNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canPaste(), TextView.ID_PASTE, + never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuPasteAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canPaste(), TextView.ID_PASTE, + times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuPasteAsPlaintextNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canPasteAsPlainText(), + TextView.ID_PASTE_AS_PLAIN_TEXT, never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuPasteAsPlaintextAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canPasteAsPlainText(), + TextView.ID_PASTE_AS_PLAIN_TEXT, times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuSelectAllNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canSelectAllText(), + TextView.ID_SELECT_ALL, never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuSelectAllAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canSelectAllText(), + TextView.ID_SELECT_ALL, times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuShareNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canShare(), TextView.ID_SHARE, + never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuShareAddedWhenAvailable() { + verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canShare(), TextView.ID_SHARE, + times(1)); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuAutofillNotAddedWhenUnavailable() { + verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canRequestAutofill(), + TextView.ID_AUTOFILL, never()); + } + + @Test + @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS) + public void testContextMenuAutofillNotAddedWhenUnavailableBecauseTextSelected() { + verifyMenuItemNotAdded((spy) -> { + doReturn(true).when(spy).canRequestAutofill(); + doReturn("test").when(spy).getSelectedText(); + }, TextView.ID_AUTOFILL, never()); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index c2ee223b916a..972b78f6ca9a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -39,6 +39,7 @@ import android.view.InsetsState; import android.view.accessibility.AccessibilityManager; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener; @@ -67,6 +68,7 @@ import java.util.List; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.IntPredicate; import java.util.function.Predicate; /** @@ -189,6 +191,9 @@ public class CompatUIController implements OnDisplaysChangedListener, @NonNull private final CompatUIStatusManager mCompatUIStatusManager; + @NonNull + private final IntPredicate mInDesktopModePredicate; + public CompatUIController(@NonNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, @@ -202,7 +207,8 @@ public class CompatUIController implements OnDisplaysChangedListener, @NonNull CompatUIConfiguration compatUIConfiguration, @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler, @NonNull AccessibilityManager accessibilityManager, - @NonNull CompatUIStatusManager compatUIStatusManager) { + @NonNull CompatUIStatusManager compatUIStatusManager, + @NonNull IntPredicate isDesktopModeEnablePredicate) { mContext = context; mShellController = shellController; mDisplayController = displayController; @@ -218,6 +224,7 @@ public class CompatUIController implements OnDisplaysChangedListener, mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis( DISAPPEAR_DELAY_MS, flags); mCompatUIStatusManager = compatUIStatusManager; + mInDesktopModePredicate = isDesktopModeEnablePredicate; shellInit.addInitCallback(this::onInit, this); } @@ -251,7 +258,9 @@ public class CompatUIController implements OnDisplaysChangedListener, updateActiveTaskInfo(taskInfo); } - if (taskInfo.configuration == null || taskListener == null) { + // We close all the Compat UI educations in case we're in desktop mode. + if (taskInfo.configuration == null || taskListener == null + || isInDesktopMode(taskInfo.displayId)) { // Null token means the current foreground activity is not in compatibility mode. removeLayouts(taskInfo.taskId); return; @@ -350,7 +359,6 @@ public class CompatUIController implements OnDisplaysChangedListener, mOnInsetsChangedListeners.remove(displayId); } - @Override public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { updateDisplayLayout(displayId); @@ -692,7 +700,8 @@ public class CompatUIController implements OnDisplaysChangedListener, mContext.startActivityAsUser(intent, userHandle); } - private void removeLayouts(int taskId) { + @VisibleForTesting + void removeLayouts(int taskId) { final CompatUIWindowManager compatLayout = mActiveCompatLayouts.get(taskId); if (compatLayout != null) { compatLayout.release(); @@ -825,4 +834,9 @@ public class CompatUIController implements OnDisplaysChangedListener, boolean mHasShownCameraCompatHint; boolean mHasShownUserAspectRatioSettingsButtonHint; } + + private boolean isInDesktopMode(int displayId) { + return Flags.skipCompatUiEducationInDesktopMode() + && mInDesktopModePredicate.test(displayId); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 98536bf98f0b..42937c134e7f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -137,6 +137,7 @@ import dagger.Module; import dagger.Provides; import java.util.Optional; +import java.util.function.IntPredicate; /** * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only @@ -261,6 +262,7 @@ public abstract class WMShellBaseModule { Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler, Lazy<AccessibilityManager> accessibilityManager, CompatUIRepository compatUIRepository, + Optional<DesktopModeTaskRepository> desktopModeTaskRepository, @NonNull CompatUIState compatUIState, @NonNull CompatUIComponentIdGenerator componentIdGenerator, @NonNull CompatUIComponentFactory compatUIComponentFactory, @@ -273,6 +275,10 @@ public abstract class WMShellBaseModule { new DefaultCompatUIHandler(compatUIRepository, compatUIState, componentIdGenerator, compatUIComponentFactory, mainExecutor)); } + final IntPredicate inDesktopModePredicate = + desktopModeTaskRepository.<IntPredicate>map(modeTaskRepository -> displayId -> + modeTaskRepository.getVisibleTaskCount(displayId) > 0) + .orElseGet(() -> displayId -> false); return Optional.of( new CompatUIController( context, @@ -288,7 +294,8 @@ public abstract class WMShellBaseModule { compatUIConfiguration.get(), compatUIShellCommandHandler.get(), accessibilityManager.get(), - compatUIStatusManager)); + compatUIStatusManager, + inDesktopModePredicate)); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt index a489c4ffdd94..423fe579a29a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.dagger +import android.os.Handler import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.shared.annotations.ShellMainThread @@ -24,22 +25,37 @@ import dagger.Provides import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainCoroutineDispatcher import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher -/** Providers for various WmShell-specific coroutines-related constructs. */ +/** + * Providers for various WmShell-specific coroutines-related constructs. + * + * Providers of [MainCoroutineDispatcher] intentionally creates the dispatcher with a [Handler] + * backing it instead of a [ShellExecutor] because [ShellExecutor.asCoroutineDispatcher] will + * create a [CoroutineDispatcher] whose [CoroutineDispatcher.isDispatchNeeded] is effectively never + * dispatching. This is because even if dispatched, the backing [ShellExecutor.execute] always runs + * the [Runnable] immediately if called from the same thread, whereas + * [Handler.asCoroutineDispatcher] will create a [MainCoroutineDispatcher] that correctly + * dispatches (queues) when [CoroutineDispatcher.isDispatchNeeded] is true using [Handler.post]. + * For callers that do need a non-dispatching version, [MainCoroutineDispatcher.immediate] is + * available. + */ @Module class WMShellCoroutinesModule { @Provides @ShellMainThread - fun provideMainDispatcher(@ShellMainThread mainExecutor: ShellExecutor): CoroutineDispatcher = - mainExecutor.asCoroutineDispatcher() + fun provideMainDispatcher( + @ShellMainThread mainHandler: Handler + ): MainCoroutineDispatcher = mainHandler.asCoroutineDispatcher() @Provides @ShellBackgroundThread fun provideBackgroundDispatcher( - @ShellBackgroundThread backgroundExecutor: ShellExecutor - ): CoroutineDispatcher = backgroundExecutor.asCoroutineDispatcher() + @ShellBackgroundThread backgroundHandler: Handler + ): MainCoroutineDispatcher = backgroundHandler.asCoroutineDispatcher() @Provides @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index 1a103d345ca7..d72ec90957fc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -22,13 +22,15 @@ import android.graphics.Rect import android.os.Bundle import android.os.IBinder import android.os.SystemClock +import android.os.SystemProperties import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CLOSE import android.window.TransitionInfo import android.window.TransitionInfo.Change import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction -import androidx.dynamicanimation.animation.SpringForce +import com.android.internal.annotations.VisibleForTesting +import com.android.internal.dynamicanimation.animation.SpringForce import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE import com.android.internal.jank.InteractionJankMonitor @@ -893,13 +895,10 @@ constructor( ) { private val positionSpringConfig = - PhysicsAnimator.SpringConfig( - SpringForce.STIFFNESS_LOW, - SpringForce.DAMPING_RATIO_LOW_BOUNCY - ) + PhysicsAnimator.SpringConfig(POSITION_SPRING_STIFFNESS, POSITION_SPRING_DAMPING_RATIO) private val sizeSpringConfig = - PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY) + PhysicsAnimator.SpringConfig(SIZE_SPRING_STIFFNESS, SIZE_SPRING_DAMPING_RATIO) /** * @return layers in order: @@ -929,7 +928,7 @@ constructor( finishTransaction.hide(homeLeash) // Setup freeform tasks before animation state.freeformTaskChanges.forEach { change -> - val startScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE + val startScale = FREEFORM_TASKS_INITIAL_SCALE val startX = change.endAbsBounds.left + change.endAbsBounds.width() * (1 - startScale) / 2 val startY = @@ -994,9 +993,22 @@ constructor( (animBounds.width() - startBounds.width()).toFloat() / (endBounds.width() - startBounds.width()) val animScale = startScale + animFraction * (1 - startScale) - // Freeform animation starts 50% in the animation - val freeformAnimFraction = max(animFraction - 0.5f, 0f) * 2f - val freeformStartScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE + // Freeform animation starts with freeform animation offset relative to the commit + // animation and plays until the commit animation ends. For instance: + // - if the freeform animation offset is `0.0` the freeform tasks animate alongside + // - if the freeform animation offset is `0.6` the freeform tasks will + // start animating at 60% fraction of the commit animation and will complete when + // the commit animation fraction is 100%. + // - if the freeform animation offset is `1.0` then freeform tasks will appear + // without animation after commit animation finishes. + val freeformAnimFraction = + if (FREEFORM_TASKS_ANIM_OFFSET != 1f) { + max(animFraction - FREEFORM_TASKS_ANIM_OFFSET, 0f) / + (1f - FREEFORM_TASKS_ANIM_OFFSET) + } else { + 0f + } + val freeformStartScale = FREEFORM_TASKS_INITIAL_SCALE val freeformAnimScale = freeformStartScale + freeformAnimFraction * (1 - freeformStartScale) tx.apply { @@ -1032,10 +1044,53 @@ constructor( } companion object { + /** The freeform tasks initial scale when committing the drag-to-desktop gesture. */ + private val FREEFORM_TASKS_INITIAL_SCALE = + propertyValue("freeform_tasks_initial_scale", scale = 100f, default = 0.9f) + + /** The freeform tasks animation offset relative to the whole animation duration. */ + private val FREEFORM_TASKS_ANIM_OFFSET = + propertyValue("freeform_tasks_anim_offset", scale = 100f, default = 0.5f) + + /** The spring force stiffness used to place the window into the final position. */ + private val POSITION_SPRING_STIFFNESS = + propertyValue("position_stiffness", default = SpringForce.STIFFNESS_LOW) + + /** The spring force damping ratio used to place the window into the final position. */ + private val POSITION_SPRING_DAMPING_RATIO = + propertyValue( + "position_damping_ratio", + scale = 100f, + default = SpringForce.DAMPING_RATIO_LOW_BOUNCY + ) + + /** The spring force stiffness used to resize the window into the final bounds. */ + private val SIZE_SPRING_STIFFNESS = + propertyValue("size_stiffness", default = SpringForce.STIFFNESS_LOW) + + /** The spring force damping ratio used to resize the window into the final bounds. */ + private val SIZE_SPRING_DAMPING_RATIO = + propertyValue( + "size_damping_ratio", + scale = 100f, + default = SpringForce.DAMPING_RATIO_NO_BOUNCY + ) + + /** Drag to desktop transition system properties group. */ + @VisibleForTesting + const val SYSTEM_PROPERTIES_GROUP = "persist.wm.debug.desktop_transitions.drag_to_desktop" + /** - * The initial scale of the freeform tasks in the animation to commit the drag-to-desktop - * gesture. + * Drag to desktop transition system property value with [name]. + * + * @param scale an optional scale to apply to the value read from the system property. + * @param default a default value to return if the system property isn't set. */ - private const val DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE = 0.9f + @VisibleForTesting + fun propertyValue(name: String, scale: Float = 1f, default: Float = 0f): Float = + SystemProperties.getInt( + /* key= */ "$SYSTEM_PROPERTIES_GROUP.$name", + /* def= */ (default * scale).toInt() + ) / scale } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md index 84f6af4125b8..72d1a76b17e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md @@ -27,10 +27,13 @@ building to check the log state (is enabled) before printing the print format st traces in Winscope) ### Kotlin +Kotlin protologging is supported but not as optimized as in Java. -Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)). -For logging in Kotlin, use the [KtProtoLog](/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt) -class which has a similar API to the Java ProtoLog class. +The Protolog tool does not yet have support for Kotlin code ([b/168581922](https://b.corp.google.com/issues/168581922)). + +What this implies is that ProtoLogs are not pre-processed to extract the static strings out when used in Kotlin. So, +there is no memory gain when using ProtoLogging in Kotlin. The logs will still be traced to Perfetto, but with a subtly +worse performance due to the additional string interning that needs to be done at run time instead of at build time. ### Enabling ProtoLog command line logging Run these commands to enable protologs (in logcat) for WM Core ([list of all core tags](/core/java/com/android/internal/protolog/ProtoLogGroup.java)): diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 95f864a775be..8921ceb6175d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -21,6 +21,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; @@ -684,6 +685,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); addActivityOptions(options1, mSideStage); + prepareTasksForSplitScreen(new int[] {taskId1, taskId2}, wct); wct.startTask(taskId1, options1); startWithTask(wct, taskId2, options2, snapPosition, remoteTransition, instanceId); @@ -714,6 +716,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, options1 = options1 != null ? options1 : new Bundle(); addActivityOptions(options1, mSideStage); wct.sendPendingIntent(pendingIntent, fillInIntent, options1); + prepareTasksForSplitScreen(new int[] {taskId}, wct); startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId); } @@ -757,11 +760,30 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, options1 = options1 != null ? options1 : new Bundle(); addActivityOptions(options1, mSideStage); wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); + prepareTasksForSplitScreen(new int[] {taskId}, wct); startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId); } /** + * Prepares the tasks whose IDs are provided in `taskIds` for split screen by clearing their + * bounds and windowing mode so that they can inherit the bounds and the windowing mode of + * their root stages. + * + * @param taskIds an array of task IDs whose bounds will be cleared. + * @param wct transaction to clear the bounds on the tasks. + */ + private void prepareTasksForSplitScreen(int[] taskIds, WindowContainerTransaction wct) { + for (int taskId : taskIds) { + ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); + if (task != null) { + wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED) + .setBounds(task.token, null); + } + } + } + + /** * Starts with the second task to a split pair in one transition. * * @param wct transaction to start the first task diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index b39cf19a155a..d5287e742c2c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -35,9 +35,12 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.TaskInfo; import android.content.Context; import android.content.res.Configuration; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.view.InsetsSource; import android.view.InsetsState; @@ -90,6 +93,9 @@ public class CompatUIControllerTest extends ShellTestCase { public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private CompatUIController mController; private ShellInit mShellInit; @Mock @@ -122,7 +128,6 @@ public class CompatUIControllerTest extends ShellTestCase { private CompatUIConfiguration mCompatUIConfiguration; @Mock private CompatUIShellCommandHandler mCompatUIShellCommandHandler; - @Mock private AccessibilityManager mAccessibilityManager; @@ -132,6 +137,8 @@ public class CompatUIControllerTest extends ShellTestCase { @NonNull private CompatUIStatusManager mCompatUIStatusManager; + private boolean mInDesktopModePredicateResult; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -157,7 +164,7 @@ public class CompatUIControllerTest extends ShellTestCase { mMockDisplayController, mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader, mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager, - mCompatUIStatusManager) { + mCompatUIStatusManager, i -> mInDesktopModePredicateResult) { @Override CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { @@ -685,6 +692,7 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() { TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true); taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(false); @@ -695,6 +703,34 @@ public class CompatUIControllerTest extends ShellTestCase { eq(mMockTaskListener)); } + @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) + @EnableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE) + public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagEnabled() { + mInDesktopModePredicateResult = false; + TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + verify(mController, never()).removeLayouts(taskInfo.taskId); + + mInDesktopModePredicateResult = true; + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + verify(mController).removeLayouts(taskInfo.taskId); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) + @DisableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE) + public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagDisabled() { + mInDesktopModePredicateResult = false; + TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + verify(mController, never()).removeLayouts(taskInfo.taskId); + + mInDesktopModePredicateResult = true; + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + verify(mController, never()).removeLayouts(taskInfo.taskId); + } + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat) { return createTaskInfo(displayId, taskId, hasSizeCompat, /* isVisible */ false, /* isFocused */ false, /* isTopActivityTransparent */ false); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index 16a234b9e2f2..5b028371be2b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -8,6 +8,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WindowingMode import android.graphics.PointF import android.os.IBinder +import android.os.SystemProperties import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.SurfaceControl @@ -16,6 +17,7 @@ import android.window.TransitionInfo import android.window.TransitionInfo.FLAG_IS_WALLPAPER import android.window.WindowContainerTransaction import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase @@ -29,19 +31,24 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import java.util.function.Supplier +import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock +import org.mockito.MockitoSession import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyZeroInteractions import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness /** Tests of [DragToDesktopTransitionHandler]. */ @SmallTest @@ -61,10 +68,12 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { private lateinit var defaultHandler: DragToDesktopTransitionHandler private lateinit var springHandler: SpringDragToDesktopTransitionHandler + private lateinit var mockitoSession: MockitoSession @Before fun setUp() { - defaultHandler = DefaultDragToDesktopTransitionHandler( + defaultHandler = + DefaultDragToDesktopTransitionHandler( context, transitions, taskDisplayAreaOrganizer, @@ -72,7 +81,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { transactionSupplier, ) .apply { setSplitScreenController(splitScreenController) } - springHandler = SpringDragToDesktopTransitionHandler( + springHandler = + SpringDragToDesktopTransitionHandler( context, transitions, taskDisplayAreaOrganizer, @@ -80,6 +90,16 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { transactionSupplier, ) .apply { setSplitScreenController(splitScreenController) } + mockitoSession = + ExtendedMockito.mockitoSession() + .strictness(Strictness.LENIENT) + .mockStatic(SystemProperties::class.java) + .startMocking() + } + + @After + fun tearDown() { + mockitoSession.finishMocking() } @Test @@ -357,6 +377,77 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { verify(finishCallback).onTransitionFinished(null) } + @Test + fun propertyValue_returnsSystemPropertyValue() { + val name = "property_name" + val value = 10f + + whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), anyInt())) + .thenReturn(value.toInt()) + + assertEquals( + "Expects to return system properties stored value", + /* expected= */ value, + /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name) + ) + } + + @Test + fun propertyValue_withScale_returnsScaledSystemPropertyValue() { + val name = "property_name" + val value = 10f + val scale = 100f + + whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), anyInt())) + .thenReturn(value.toInt()) + + assertEquals( + "Expects to return scaled system properties stored value", + /* expected= */ value / scale, + /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name, scale = scale) + ) + } + + @Test + fun propertyValue_notSet_returnsDefaultValue() { + val name = "property_name" + val defaultValue = 50f + + whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), eq(defaultValue.toInt()))) + .thenReturn(defaultValue.toInt()) + + assertEquals( + "Expects to return the default value", + /* expected= */ defaultValue, + /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue( + name, + default = defaultValue + ) + ) + } + + @Test + fun propertyValue_withScaleNotSet_returnsDefaultValue() { + val name = "property_name" + val defaultValue = 0.5f + val scale = 100f + // Default value is multiplied when provided as a default value for [SystemProperties] + val scaledDefault = (defaultValue * scale).toInt() + + whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), eq(scaledDefault))) + .thenReturn(scaledDefault) + + assertEquals( + "Expects to return the default value", + /* expected= */ defaultValue, + /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue( + name, + default = defaultValue, + scale = scale + ) + ) + } + private fun startDrag( handler: DragToDesktopTransitionHandler, task: RunningTaskInfo = createTask(), @@ -462,4 +553,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) } } + + private fun systemPropertiesKey(name: String) = + "${SpringDragToDesktopTransitionHandler.SYSTEM_PROPERTIES_GROUP}.$name" } diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 2c71ee01b3f1..d14275ff2fd6 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -18,6 +18,7 @@ package android.media.tv.tuner; import android.annotation.BytesLong; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -32,6 +33,7 @@ import android.hardware.tv.tuner.Constant64Bit; import android.hardware.tv.tuner.FrontendScanType; import android.media.MediaCodec; import android.media.tv.TvInputService; +import android.media.tv.flags.Flags; import android.media.tv.tuner.dvr.DvrPlayback; import android.media.tv.tuner.dvr.DvrRecorder; import android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener; @@ -2529,6 +2531,50 @@ public class Tuner implements AutoCloseable { } /** + * Request a frontend by frontend type. + * + * <p> This API is used if the applications want to select a frontend with desired type when + * there are multiple frontends of the same type is there before {@link tune}. The applied + * frontend will be one of the not in-use frontend. If all frontends are in-use, this API will + * reclaim and apply the frontend owned by the lowest priority client if current client has + * higher priority. Otherwise, this API will not apply any frontend and return + * {@link #RESULT_UNAVAILABLE}. + * + * @param desiredFrontendType the Type of the desired fronted. Should be one of + * {@link android.media.tv.tuner.frontend.FrontendSettings.Type} + * @return result status of open operation. + */ + @Result + @FlaggedApi(Flags.FLAG_TUNER_W_APIS) + @RequiresPermission( + allOf = {"android.permission.TUNER_RESOURCE_ACCESS", "android.permission.ACCESS_TV_TUNER"}) + public int applyFrontendByType(@FrontendSettings.Type int desiredFrontendType) { + mFrontendLock.lock(); + try { + if (mFeOwnerTuner != null) { + Log.e(TAG, "Operation connot be done by sharee of tuner"); + return RESULT_INVALID_STATE; + } + if (mFrontendHandle != null) { + Log.e(TAG, "A frontend has been opened before"); + return RESULT_INVALID_STATE; + } + + mDesiredFrontendId = null; + mFrontendType = desiredFrontendType; + if (DEBUG) { + Log.d(TAG, "Applying frontend with type " + mFrontendType); + } + if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) { + return RESULT_UNAVAILABLE; + } + return RESULT_SUCCESS; + } finally { + mFrontendLock.unlock(); + } + } + + /** * Open a shared filter instance. * * @param context the context of the caller. diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS new file mode 100644 index 000000000000..134a56ecb27e --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS @@ -0,0 +1 @@ +include /packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index a129ac170d89..8a1d81be5e11 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1120,16 +1120,6 @@ flag { } flag { - name: "glanceable_hub_back_gesture" - namespace: "systemui" - description: "Enables back gesture on the glanceable hub" - bug: "346331399" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "glanceable_hub_allow_keyguard_when_dreaming" namespace: "systemui" description: "Allows users to exit dream to keyguard with glanceable hub enabled" diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 872bef256f3a..ed1277666372 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey @@ -47,7 +46,6 @@ import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions import com.android.compose.theme.LocalAndroidColorScheme import com.android.internal.R.attr.focusable -import com.android.systemui.Flags.glanceableHubBackGesture import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys @@ -198,15 +196,7 @@ fun CommunalContainer( Box(modifier = Modifier.fillMaxSize()) } - val userActions = - if (glanceableHubBackGesture()) { - mapOf( - Swipe(SwipeDirection.End) to CommunalScenes.Blank, - Back to CommunalScenes.Blank, - ) - } else { - mapOf(Swipe(SwipeDirection.End) to CommunalScenes.Blank) - } + val userActions = mapOf(Swipe(SwipeDirection.End) to CommunalScenes.Blank) scene(CommunalScenes.Communal, userActions = userActions) { CommunalScene( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index d5874d1a7d3f..e17cb31407da 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -16,6 +16,8 @@ package com.android.systemui.scene.ui.composable +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Text @@ -28,6 +30,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout @@ -100,7 +103,13 @@ fun SceneContainer( } Box( - modifier = Modifier.fillMaxSize(), + modifier = + Modifier.fillMaxSize().pointerInput(Unit) { + awaitEachGesture { + awaitFirstDown(false) + viewModel.onSceneContainerUserInputStarted() + } + }, ) { SceneTransitionLayout(state = state, modifier = modifier.fillMaxSize()) { sceneByKey.forEach { (sceneKey, composableScene) -> diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS new file mode 100644 index 000000000000..f6f98e934dde --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS @@ -0,0 +1 @@ +include /packages/SystemUI/src/com/android/systemui/keyguard/OWNERS diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt index d850f17cd89a..65236f02b635 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt @@ -30,8 +30,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer -import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER -import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository @@ -90,8 +88,6 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { .thenReturn(needsEmergencyAffordance) whenever(telecomManager.isInCall).thenReturn(false) - kosmos.fakeFeatureFlagsClassic.set(REFACTOR_GETCURRENTUSER, true) - kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true) kosmos.telecomManager = telecomManager diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index c63381687f18..5b987b309b6b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -175,10 +175,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { emergencyAffordanceManager = kosmos.emergencyAffordanceManager whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true) - kosmos.fakeFeatureFlagsClassic.apply { - set(Flags.NEW_NETWORK_SLICE_UI, false) - set(Flags.REFACTOR_GETCURRENTUSER, true) - } + kosmos.fakeFeatureFlagsClassic.apply { set(Flags.NEW_NETWORK_SLICE_UI, false) } mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository mobileConnectionsRepository.isAnySimSecure.value = false diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index e3a69a964b45..35cefa6b58df 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -401,10 +401,10 @@ class SceneInteractorTest : SysuiTestCase() { underTest.setVisible(false, "reason") val isVisible by collectLastValue(underTest.isVisible) assertThat(isVisible).isFalse() - underTest.onRemoteUserInteractionStarted("reason") + underTest.onRemoteUserInputStarted("reason") assertThat(isVisible).isTrue() - underTest.onUserInteractionFinished() + underTest.onUserInputFinished() assertThat(isVisible).isFalse() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index f856c559454c..832e7b1bcc0c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -237,7 +237,7 @@ class SceneContainerViewModelTest : SysuiTestCase() { sceneInteractor.setVisible(false, "reason") runCurrent() assertThat(underTest.isVisible).isFalse() - sceneInteractor.onRemoteUserInteractionStarted("reason") + sceneInteractor.onRemoteUserInputStarted("reason") runCurrent() assertThat(underTest.isVisible).isTrue() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt index 6a886643cebb..8b97739af1db 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt @@ -22,6 +22,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -83,4 +85,69 @@ class NotificationShadeWindowModelTest : SysuiTestCase() { ) assertThat(isKeyguardOccluded).isFalse() } + + @Test + fun transitionFromOccludedToDreamingTransitionRemainsTrue() = + testScope.runTest { + val isKeyguardOccluded by collectLastValue(underTest.isKeyguardOccluded) + assertThat(isKeyguardOccluded).isFalse() + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + value = 0f, + transitionState = TransitionState.STARTED, + ), + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + value = 0.5f, + transitionState = TransitionState.RUNNING, + ), + ), + testScope, + ) + assertThat(isKeyguardOccluded).isFalse() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + value = 1f, + transitionState = TransitionState.FINISHED, + ), + ) + assertThat(isKeyguardOccluded).isTrue() + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + TransitionStep( + from = KeyguardState.DREAMING, + to = KeyguardState.OCCLUDED, + value = 0f, + transitionState = TransitionState.STARTED, + ), + TransitionStep( + from = KeyguardState.DREAMING, + to = KeyguardState.OCCLUDED, + value = 0.5f, + transitionState = TransitionState.RUNNING, + ), + ), + testScope, + ) + assertThat(isKeyguardOccluded).isTrue() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.DREAMING, + to = KeyguardState.OCCLUDED, + value = 1f, + transitionState = TransitionState.FINISHED, + ), + ) + assertThat(isKeyguardOccluded).isTrue() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt index 1356e93db549..06a2c5af2986 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding @@ -84,4 +85,48 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() { ) ) } + + @Test + fun shouldCloseGuts_userInputOngoing_currentGestureInGuts() = + testScope.runTest { + val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts) + + kosmos.sceneInteractor.onSceneContainerUserInputStarted() + underTest.setCurrentGestureInGuts(true) + + assertThat(shouldCloseGuts).isFalse() + } + + @Test + fun shouldCloseGuts_userInputOngoing_currentGestureNotInGuts() = + testScope.runTest { + val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts) + + kosmos.sceneInteractor.onSceneContainerUserInputStarted() + underTest.setCurrentGestureInGuts(false) + + assertThat(shouldCloseGuts).isTrue() + } + + @Test + fun shouldCloseGuts_userInputNotOngoing_currentGestureInGuts() = + testScope.runTest { + val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts) + + kosmos.sceneInteractor.onUserInputFinished() + underTest.setCurrentGestureInGuts(true) + + assertThat(shouldCloseGuts).isFalse() + } + + @Test + fun shouldCloseGuts_userInputNotOngoing_currentGestureNotInGuts() = + testScope.runTest { + val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts) + + kosmos.sceneInteractor.onUserInputFinished() + underTest.setCurrentGestureInGuts(false) + + assertThat(shouldCloseGuts).isFalse() + } } diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index 0c11d2fa1d11..fc6d20e11d3b 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -27,8 +27,6 @@ <!-- Whether to use the split 2-column notification shade --> <bool name="config_use_split_notification_shade">true</bool> - <bool name="config_use_large_screen_shade_header">true</bool> - <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">2</integer> diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index c594f1cd9313..b4383156dc71 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -35,6 +35,8 @@ <!-- How many lines to show in the security footer --> <integer name="qs_security_footer_maxLines">1</integer> + <bool name="config_use_large_screen_shade_header">true</bool> + <!-- Whether to show bottom sheets edge to edge --> <bool name="config_edgeToEdgeBottomSheetDialog">false</bool> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 4ef1f93481f7..121577e438b0 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -342,8 +342,7 @@ public class QuickStepContract { // the keyguard) if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0 || (sysuiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0 - || (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0 - || (sysuiStateFlags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) { + || (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0) { return false; } if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 60fff282d041..9b45fa47cf21 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -483,22 +483,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting SparseArray<BiometricAuthenticated> mUserFingerprintAuthenticated = new SparseArray<>(); - private static int sCurrentUser; - - @Deprecated - public synchronized static void setCurrentUser(int currentUser) { - sCurrentUser = currentUser; - } - - /** - * @deprecated This can potentially return unexpected values in a multi user scenario - * as this state is managed by another component. Consider using {@link SelectedUserInteractor}. - */ - @Deprecated - public synchronized static int getCurrentUser() { - return sCurrentUser; - } - @Override public void onTrustChanged(boolean enabled, boolean newlyUnlocked, int userId, int flags, List<String> trustGrantedMessages) { @@ -969,7 +953,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.removeCallbacks(mFpCancelNotReceived); } try { - final int userId = mSelectedUserInteractor.getSelectedUserId(true); + final int userId = mSelectedUserInteractor.getSelectedUserId(); if (userId != authUserId) { mLogger.logFingerprintAuthForWrongUser(authUserId); return; @@ -1220,7 +1204,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.d("Aborted successful auth because device is going to sleep."); return; } - final int userId = mSelectedUserInteractor.getSelectedUserId(true); + final int userId = mSelectedUserInteractor.getSelectedUserId(); if (userId != authUserId) { mLogger.logFaceAuthForWrongUser(authUserId); return; @@ -2462,7 +2446,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener); - int user = mSelectedUserInteractor.getSelectedUserId(true); + int user = mSelectedUserInteractor.getSelectedUserId(); boolean isUserUnlocked = mUserManager.isUserUnlocked(user); mLogger.logUserUnlockedInitialState(user, isUserUnlocked); mUserIsUnlocked.put(user, isUserUnlocked); @@ -4081,7 +4065,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" " + subId + "=" + mServiceStates.get(subId)); } if (isFingerprintSupported()) { - final int userId = mSelectedUserInteractor.getSelectedUserId(true); + final int userId = mSelectedUserInteractor.getSelectedUserId(); final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId); pw.println(" Fingerprint state (user=" + userId + ")"); @@ -4124,7 +4108,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintListenBuffer.toList() ).printTableData(pw); } else if (mFpm != null && mFingerprintSensorProperties.isEmpty()) { - final int userId = mSelectedUserInteractor.getSelectedUserId(true); + final int userId = mSelectedUserInteractor.getSelectedUserId(); pw.println(" Fingerprint state (user=" + userId + ")"); pw.println(" mFingerprintSensorProperties.isEmpty=" + mFingerprintSensorProperties.isEmpty()); @@ -4137,7 +4121,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintListenBuffer.toList() ).printTableData(pw); } - final int userId = mSelectedUserInteractor.getSelectedUserId(true); + final int userId = mSelectedUserInteractor.getSelectedUserId(); final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); pw.println(" authSinceBoot=" + getStrongAuthTracker().hasUserAuthenticatedSinceBoot()); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java new file mode 100644 index 000000000000..c94487848b81 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java @@ -0,0 +1,67 @@ +/* + * 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.accessibility; + +import android.content.Context; +import android.provider.Settings; + +import androidx.annotation.MainThread; +import androidx.annotation.Nullable; + +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.settings.UserTracker; + +import javax.inject.Inject; + +/** + * Controller for tracking the current accessibility gesture list. + * + * @see Settings.Secure#ACCESSIBILITY_GESTURE_TARGETS + */ +@MainThread +@SysUISingleton +public class AccessibilityGestureTargetsObserver extends + SecureSettingsContentObserver<AccessibilityGestureTargetsObserver.TargetsChangedListener> { + + /** Listener for accessibility gesture targets changes. */ + public interface TargetsChangedListener { + + /** + * Called when accessibility gesture targets changes. + * + * @param targets Current content of {@link Settings.Secure#ACCESSIBILITY_GESTURE_TARGETS} + */ + void onAccessibilityGestureTargetsChanged(String targets); + } + + @Inject + public AccessibilityGestureTargetsObserver(Context context, UserTracker userTracker) { + super(context, userTracker, Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS); + } + + @Override + void onValueChanged(TargetsChangedListener listener, String value) { + listener.onAccessibilityGestureTargetsChanged(value); + } + + /** Returns the current string from settings key + * {@link Settings.Secure#ACCESSIBILITY_GESTURE_TARGETS}. */ + @Nullable + public String getCurrentAccessibilityGestureTargets() { + return getSettingsValue(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt index 6757edba8ac3..b2d02edf3c45 100644 --- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt @@ -120,7 +120,7 @@ constructor( Intent.FLAG_ACTIVITY_NEW_TASK, null, activityOptions.toBundle(), - selectedUserInteractor.getSelectedUserId(true), + selectedUserInteractor.getSelectedUserId(), ) } catch (e: RemoteException) { Log.w("CameraGestureHelper", "Unable to start camera activity", e) diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java index a43447f7fcf4..aae21b97b163 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java @@ -66,7 +66,8 @@ public class EditTextActivity extends Activity @Override public WindowInsets onApplyWindowInsets(@NonNull View view, @NonNull WindowInsets windowInsets) { - Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars()); + Insets insets = windowInsets.getInsets( + WindowInsets.Type.systemBars() | WindowInsets.Type.ime()); ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); layoutParams.leftMargin = insets.left; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index e07b5c228585..21922ff22afe 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -256,7 +256,7 @@ public class DozeSensors { Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE, mConfig.wakeScreenGestureAvailable() && mConfig.alwaysOnEnabled( - mSelectedUserInteractor.getSelectedUserId(true)), + mSelectedUserInteractor.getSelectedUserId()), DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE, false /* reports touch coordinates */, false /* touchscreen */ @@ -297,7 +297,7 @@ public class DozeSensors { private boolean udfpsLongPressConfigured() { return mUdfpsEnrolled - && (mConfig.alwaysOnEnabled(mSelectedUserInteractor.getSelectedUserId(true)) + && (mConfig.alwaysOnEnabled(mSelectedUserInteractor.getSelectedUserId()) || mScreenOffUdfpsEnabled); } @@ -477,7 +477,7 @@ public class DozeSensors { private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) { - if (userId != mSelectedUserInteractor.getSelectedUserId(true)) { + if (userId != mSelectedUserInteractor.getSelectedUserId()) { return; } for (TriggerSensor s : mTriggerSensors) { @@ -703,13 +703,13 @@ public class DozeSensors { } protected boolean enabledBySetting() { - if (!mConfig.enabled(mSelectedUserInteractor.getSelectedUserId(true))) { + if (!mConfig.enabled(mSelectedUserInteractor.getSelectedUserId())) { return false; } else if (TextUtils.isEmpty(mSetting)) { return true; } return mSecureSettings.getIntForUser(mSetting, mSettingDefault ? 1 : 0, - mSelectedUserInteractor.getSelectedUserId(true)) != 0; + mSelectedUserInteractor.getSelectedUserId()) != 0; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 4a9f741494f4..dd08d3262546 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -251,7 +251,7 @@ public class DozeTriggers implements DozeMachine.Part { return; } mNotificationPulseTime = SystemClock.elapsedRealtime(); - if (!mConfig.pulseOnNotificationEnabled(mSelectedUserInteractor.getSelectedUserId(true))) { + if (!mConfig.pulseOnNotificationEnabled(mSelectedUserInteractor.getSelectedUserId())) { runIfNotNull(onPulseSuppressedListener); mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled"); return; diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 4d75d661de49..bb73f569d945 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -55,19 +55,13 @@ object Flags { // TODO(b/254512624): Tracking Bug @JvmField val NOTIFICATION_DRAG_TO_CONTENTS = - resourceBooleanFlag( - R.bool.config_notificationToContents, - "notification_drag_to_contents" - ) + resourceBooleanFlag(R.bool.config_notificationToContents, "notification_drag_to_contents") // TODO(b/280783617): Tracking Bug @Keep @JvmField val BUILDER_EXTRAS_OVERRIDE = - sysPropBooleanFlag( - "persist.sysui.notification.builder_extras_override", - default = true - ) + sysPropBooleanFlag("persist.sysui.notification.builder_extras_override", default = true) // 200 - keyguard/lockscreen // ** Flag retired ** @@ -81,10 +75,7 @@ object Flags { // TODO(b/254512676): Tracking Bug @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = - resourceBooleanFlag( - R.bool.config_enableLockScreenCustomClocks, - "lockscreen_custom_clocks" - ) + resourceBooleanFlag(R.bool.config_enableLockScreenCustomClocks, "lockscreen_custom_clocks") /** * Whether the clock on a wide lock screen should use the new "stepping" animation for moving @@ -99,10 +90,6 @@ object Flags { // TODO(b/255607168): Tracking Bug @JvmField val DOZING_MIGRATION_1 = unreleasedFlag("dozing_migration_1") - // TODO(b/305984787): - @JvmField - val REFACTOR_GETCURRENTUSER = unreleasedFlag("refactor_getcurrentuser", teamfood = true) - /** Flag to control the revamp of keyguard biometrics progress animation */ // TODO(b/244313043): Tracking bug @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp") @@ -125,13 +112,11 @@ object Flags { /** Whether the long-press gesture to open wallpaper picker is enabled. */ // TODO(b/266242192): Tracking Bug - @JvmField - val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag("lock_screen_long_press_enabled") + @JvmField val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag("lock_screen_long_press_enabled") /** Inflate and bind views upon emitting a blueprint value . */ // TODO(b/297365780): Tracking Bug - @JvmField - val LAZY_INFLATE_KEYGUARD = releasedFlag("lazy_inflate_keyguard") + @JvmField val LAZY_INFLATE_KEYGUARD = releasedFlag("lazy_inflate_keyguard") /** Enables UI updates for AI wallpapers in the wallpaper picker. */ // TODO(b/267722622): Tracking Bug @@ -145,8 +130,7 @@ object Flags { /** Add "Apply" button to wall paper picker's grid preview page. */ // TODO(b/294866904): Tracking bug. @JvmField - val WALLPAPER_PICKER_GRID_APPLY_BUTTON = - unreleasedFlag("wallpaper_picker_grid_apply_button") + val WALLPAPER_PICKER_GRID_APPLY_BUTTON = unreleasedFlag("wallpaper_picker_grid_apply_button") /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */ // TODO(b/286563884): Tracking bug @@ -190,10 +174,7 @@ object Flags { // TODO(b/254512383): Tracking Bug @JvmField val FULL_SCREEN_USER_SWITCHER = - resourceBooleanFlag( - R.bool.config_enableFullscreenUserSwitcher, - "full_screen_user_switcher" - ) + resourceBooleanFlag(R.bool.config_enableFullscreenUserSwitcher, "full_screen_user_switcher") // TODO(b/244064524): Tracking Bug @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag("qs_secondary_data_sub_info") @@ -212,16 +193,15 @@ object Flags { @JvmField val NEW_NETWORK_SLICE_UI = releasedFlag("new_network_slice_ui") // TODO(b/311222557): Tracking bug - val ROAMING_INDICATOR_VIA_DISPLAY_INFO = - releasedFlag("roaming_indicator_via_display_info") + val ROAMING_INDICATOR_VIA_DISPLAY_INFO = releasedFlag("roaming_indicator_via_display_info") // TODO(b/308138154): Tracking bug val FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS = releasedFlag("filter_provisioning_network_subscriptions") // TODO(b/293863612): Tracking Bug - @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON = - releasedFlag("incompatible_charging_battery_icon") + @JvmField + val INCOMPATIBLE_CHARGING_BATTERY_ICON = releasedFlag("incompatible_charging_battery_icon") // TODO(b/293585143): Tracking Bug val INSTANT_TETHER = releasedFlag("instant_tether") @@ -230,8 +210,7 @@ object Flags { val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks") // TODO(b/290676905): Tracking Bug - val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS = - releasedFlag("new_shade_carrier_group_mobile_icons") + val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS = releasedFlag("new_shade_carrier_group_mobile_icons") // 800 - general visual/theme @JvmField val MONET = resourceBooleanFlag(R.bool.flag_monet, "monet") @@ -280,8 +259,7 @@ object Flags { // TODO(b/273509374): Tracking Bug @JvmField - val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = - releasedFlag("always_show_home_controls_on_dreams") + val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = releasedFlag("always_show_home_controls_on_dreams") // 1100 - windowing @Keep @@ -304,9 +282,7 @@ object Flags { ) // TODO(b/293252410) : Tracking Bug - @JvmField - val LOCKSCREEN_ENABLE_LANDSCAPE = - unreleasedFlag("lockscreen.enable_landscape") + @JvmField val LOCKSCREEN_ENABLE_LANDSCAPE = unreleasedFlag("lockscreen.enable_landscape") // 1200 - predictive back @Keep @@ -327,8 +303,7 @@ object Flags { val QUICK_TAP_IN_PCC = releasedFlag("quick_tap_in_pcc") // TODO(b/261979569): Tracking Bug - val QUICK_TAP_FLOW_FRAMEWORK = - unreleasedFlag("quick_tap_flow_framework", teamfood = false) + val QUICK_TAP_FLOW_FRAMEWORK = unreleasedFlag("quick_tap_flow_framework", teamfood = false) // 1500 - chooser aka sharesheet @@ -364,14 +339,12 @@ object Flags { // TODO(b/265764985): Tracking Bug @Keep @JvmField - val ENABLE_DARK_VIGNETTE_WHEN_FOLDING = - unreleasedFlag("enable_dark_vignette_when_folding") + val ENABLE_DARK_VIGNETTE_WHEN_FOLDING = unreleasedFlag("enable_dark_vignette_when_folding") // TODO(b/265764985): Tracking Bug @Keep @JvmField - val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS = - unreleasedFlag("enable_unfold_status_bar_animations") + val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS = unreleasedFlag("enable_unfold_status_bar_animations") // TODO(b/316157842): Tracking Bug // Adds extra delay to notifications measure @@ -415,28 +388,26 @@ object Flags { unreleasedFlag("bigpicture_notification_lazy_loading") // TODO(b/283740863): Tracking Bug - @JvmField - val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog") + @JvmField val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog") // TODO(b/302144438): Tracking Bug - @JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE = - unreleasedFlag("decouple_remote_input_delegate_and_callback_update") + @JvmField + val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE = + unreleasedFlag("decouple_remote_input_delegate_and_callback_update") /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */ @JvmField val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation") /** Enable the share wifi button in Quick Settings internet dialog. */ - @JvmField - val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button") + @JvmField val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button") /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */ - @JvmField - val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog") + @JvmField val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog") // TODO(b/300995746): Tracking Bug /** A resource flag for whether the communal service is enabled. */ @JvmField - val COMMUNAL_SERVICE_ENABLED = resourceBooleanFlag(R.bool.config_communalServiceEnabled, - "communal_service_enabled") + val COMMUNAL_SERVICE_ENABLED = + resourceBooleanFlag(R.bool.config_communalServiceEnabled, "communal_service_enabled") } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 871d04693452..0feb5ec277b4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -35,8 +35,6 @@ import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionOldType; import static android.view.WindowManager.TransitionType; -import static com.android.systemui.Flags.refactorGetCurrentUser; - import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -79,6 +77,7 @@ import com.android.systemui.SystemUIApplication; import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardWakeDirectlyToGoneInteractor; @@ -319,6 +318,7 @@ public class KeyguardService extends Service { private final WindowManagerOcclusionManager mWmOcclusionManager; private final KeyguardEnabledInteractor mKeyguardEnabledInteractor; private final KeyguardWakeDirectlyToGoneInteractor mKeyguardWakeDirectlyToGoneInteractor; + private final KeyguardDismissInteractor mKeyguardDismissInteractor; private final Lazy<FoldGracePeriodProvider> mFoldGracePeriodProvider = new Lazy<>() { @Override public FoldGracePeriodProvider get() { @@ -346,7 +346,8 @@ public class KeyguardService extends Service { KeyguardInteractor keyguardInteractor, KeyguardEnabledInteractor keyguardEnabledInteractor, Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy, - KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor) { + KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor, + KeyguardDismissInteractor keyguardDismissInteractor) { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; @@ -375,6 +376,7 @@ public class KeyguardService extends Service { mWmOcclusionManager = windowManagerOcclusionManager; mKeyguardEnabledInteractor = keyguardEnabledInteractor; mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor; + mKeyguardDismissInteractor = keyguardDismissInteractor; } @Override @@ -482,7 +484,11 @@ public class KeyguardService extends Service { public void dismiss(IKeyguardDismissCallback callback, CharSequence message) { trace("dismiss message=" + message); checkPermission(); - mKeyguardViewMediator.dismiss(callback, message); + if (KeyguardWmStateRefactor.isEnabled()) { + mKeyguardDismissInteractor.dismissKeyguardWithCallback(callback); + } else { + mKeyguardViewMediator.dismiss(callback, message); + } } @Override // Binder interface @@ -672,9 +678,6 @@ public class KeyguardService extends Service { public void setCurrentUser(int userId) { trace("Deprecated/NOT USED: setCurrentUser userId=" + userId); checkPermission(); - if (!refactorGetCurrentUser()) { - mKeyguardViewMediator.setCurrentUser(userId); - } } @Override // Binder interface diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 17c5977fc80a..8c82900810be 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -41,7 +41,6 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE; import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground; -import static com.android.systemui.Flags.refactorGetCurrentUser; import static com.android.systemui.Flags.relockWithPowerButtonImmediately; import static com.android.systemui.Flags.translucentOccludingActivityFix; import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS; @@ -626,11 +625,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onUserSwitching(int userId) { - if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId)); + Log.d(TAG, String.format("onUserSwitching %d", userId)); synchronized (KeyguardViewMediator.this) { - if (refactorGetCurrentUser()) { - notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId)); - } + notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId)); resetKeyguardDonePendingLocked(); dismiss(null /* callback */, null /* message */); adjustStatusBarLocked(); @@ -639,7 +636,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onUserSwitchComplete(int userId) { - if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); + Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); // We are calling dismiss again and with a delay as there are race conditions // in some scenarios caused by async layout listeners mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500); @@ -1580,10 +1577,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - if (!refactorGetCurrentUser()) { - KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId()); - } - // Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard // is disabled. if (isKeyguardServiceEnabled()) { @@ -2546,19 +2539,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } /** - * Update the newUserId. Call while holding WindowManagerService lock. - * NOTE: Should only be called by KeyguardViewMediator in response to the user id changing. - * - * @param newUserId The id of the incoming user. - */ - public void setCurrentUser(int newUserId) { - KeyguardUpdateMonitor.setCurrentUser(newUserId); - synchronized (this) { - notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(newUserId)); - } - } - - /** * This broadcast receiver should be registered with the SystemUI permission. */ private final BroadcastReceiver mDelayedLockBroadcastReceiver = new BroadcastReceiver() { @@ -3553,7 +3533,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, try { mStatusBarService.disableForUser(flags, mStatusBarDisableToken, mContext.getPackageName(), - mSelectedUserInteractor.getSelectedUserId(true)); + mSelectedUserInteractor.getSelectedUserId()); } catch (RemoteException e) { Log.d(TAG, "Failed to force clear flags", e); } @@ -3591,7 +3571,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, try { mStatusBarService.disableForUser(flags, mStatusBarDisableToken, mContext.getPackageName(), - mSelectedUserInteractor.getSelectedUserId(true)); + 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/OWNERS b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS index 443e98762c47..208a17c0a220 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS @@ -9,3 +9,4 @@ chandruis@google.com jglazier@google.com mpietal@google.com tsuji@google.com +yuandizhou@google.com diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt index 628e912253eb..d7e6bdb8f02c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt @@ -16,9 +16,13 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.internal.policy.IKeyguardDismissCallback import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.keyguard.shared.model.DismissAction @@ -28,23 +32,30 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.Utils.Companion.toQuad import com.android.systemui.util.kotlin.sample import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** Encapsulates business logic for requesting the keyguard to dismiss/finish/done. */ @SysUISingleton class KeyguardDismissInteractor @Inject constructor( - trustRepository: TrustRepository, + @Main private val mainDispatcher: CoroutineDispatcher, + @Application private val scope: CoroutineScope, private val keyguardRepository: KeyguardRepository, - primaryBouncerInteractor: PrimaryBouncerInteractor, + private val primaryBouncerInteractor: PrimaryBouncerInteractor, + private val selectedUserInteractor: SelectedUserInteractor, + private val dismissCallbackRegistry: DismissCallbackRegistry, + trustRepository: TrustRepository, alternateBouncerInteractor: AlternateBouncerInteractor, powerInteractor: PowerInteractor, - private val selectedUserInteractor: SelectedUserInteractor, ) { /* * Updates when a biometric has authenticated the device and is requesting to dismiss @@ -127,4 +138,29 @@ constructor( suspend fun setKeyguardDone(keyguardDoneTiming: KeyguardDone) { keyguardRepository.setKeyguardDone(keyguardDoneTiming) } + + /** + * Dismiss the keyguard (or show the bouncer) and invoke the provided callback once dismissed. + * + * TODO(b/358412565): Support dismiss messages. + */ + fun dismissKeyguardWithCallback( + callback: IKeyguardDismissCallback?, + ) { + scope.launch { + withContext(mainDispatcher) { + if (callback != null) { + dismissCallbackRegistry.addCallback(callback) + } + + // This will either show the bouncer, or dismiss the keyguard if insecure. + // We currently need to request showing the primary bouncer in order to start a + // transition to PRIMARY_BOUNCER. Once we refactor that so that starting the + // transition is what causes the bouncer to show, we can remove this entire method, + // and simply ask KeyguardTransitionInteractor to transition to a bouncer state or + // dismiss keyguard. + primaryBouncerInteractor.show(true) + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index 31b0bf7fe425..d9c48fa7e581 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -28,6 +28,7 @@ constructor( private val interactors: Set<TransitionInteractor>, private val auditLogger: KeyguardTransitionAuditLogger, private val bootInteractor: KeyguardTransitionBootInteractor, + private val statusBarDisableFlagsInteractor: StatusBarDisableFlagsInteractor, ) : CoreStartable { override fun start() { @@ -53,6 +54,7 @@ constructor( } auditLogger.start() bootInteractor.start() + statusBarDisableFlagsInteractor.start() } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt new file mode 100644 index 000000000000..47818cbfd2f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt @@ -0,0 +1,157 @@ +/* + * 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.keyguard.domain.interactor + +import android.annotation.SuppressLint +import android.app.StatusBarManager +import android.content.Context +import android.os.Binder +import android.os.IBinder +import android.os.RemoteException +import android.provider.DeviceConfig +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags +import com.android.internal.statusbar.IStatusBarService +import com.android.systemui.CoreStartable +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.deviceconfig.domain.interactor.DeviceConfigInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor +import com.android.systemui.keyguard.KeyguardWmStateRefactor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.navigation.domain.interactor.NavigationInteractor +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessModel +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * Logic around StatusBarService#disableForUser, which is used to disable the home and recents + * button in certain device states. + * + * TODO(b/362313975): Remove post-Flexiglass, this duplicates StatusBarStartable logic. + */ +@SysUISingleton +class StatusBarDisableFlagsInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + @Application private val applicationContext: Context, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor, + private val statusBarService: IStatusBarService, + keyguardTransitionInteractor: KeyguardTransitionInteractor, + selectedUserInteractor: SelectedUserInteractor, + deviceConfigInteractor: DeviceConfigInteractor, + navigationInteractor: NavigationInteractor, + authenticationInteractor: AuthenticationInteractor, + powerInteractor: PowerInteractor, +) : CoreStartable { + + private val disableToken: IBinder = Binder() + + private val disableFlagsForUserId = + combine( + selectedUserInteractor.selectedUser, + keyguardTransitionInteractor.startedKeyguardState, + deviceConfigInteractor.property( + namespace = DeviceConfig.NAMESPACE_SYSTEMUI, + name = SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN, + default = true, + ), + navigationInteractor.isGesturalMode, + authenticationInteractor.authenticationMethod, + powerInteractor.detailedWakefulness, + ) { values -> + val selectedUserId = values[0] as Int + val startedState = values[1] as KeyguardState + val isShowHomeOverLockscreen = values[2] as Boolean + val isGesturalMode = values[3] as Boolean + val authenticationMethod = values[4] as AuthenticationMethodModel + val wakefulnessModel = values[5] as WakefulnessModel + val isOccluded = startedState == KeyguardState.OCCLUDED + + val hideHomeAndRecentsForBouncer = + startedState == KeyguardState.PRIMARY_BOUNCER || + startedState == KeyguardState.ALTERNATE_BOUNCER + val isKeyguardShowing = startedState != KeyguardState.GONE + val isPowerGestureIntercepted = + with(wakefulnessModel) { + isAwake() && + powerButtonLaunchGestureTriggered && + lastSleepReason == WakeSleepReason.POWER_BUTTON + } + + var flags = StatusBarManager.DISABLE_NONE + + if (hideHomeAndRecentsForBouncer || (isKeyguardShowing && !isOccluded)) { + if (!isShowHomeOverLockscreen || !isGesturalMode) { + flags = flags or StatusBarManager.DISABLE_HOME + } + flags = flags or StatusBarManager.DISABLE_RECENT + } + + if ( + isPowerGestureIntercepted && + isOccluded && + authenticationMethod.isSecure && + deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled() + ) { + flags = flags or StatusBarManager.DISABLE_RECENT + } + + selectedUserId to flags + } + .distinctUntilChanged() + + @SuppressLint("WrongConstant", "NonInjectedService") + override fun start() { + if (!KeyguardWmStateRefactor.isEnabled) { + return + } + + scope.launch { + disableFlagsForUserId.collect { (selectedUserId, flags) -> + if (applicationContext.getSystemService(Context.STATUS_BAR_SERVICE) == null) { + return@collect + } + + withContext(backgroundDispatcher) { + try { + statusBarService.disableForUser( + flags, + disableToken, + applicationContext.packageName, + selectedUserId, + ) + } catch (e: RemoteException) { + e.printStackTrace() + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt index 9c29bab80d14..ed5080d66c33 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt @@ -22,7 +22,7 @@ import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManage import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor -import com.android.systemui.media.controls.util.MediaFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import dagger.Binds import dagger.Module import dagger.Provides @@ -51,9 +51,8 @@ interface MediaDomainModule { fun providesMediaDataManager( legacyProvider: Provider<LegacyMediaDataManagerImpl>, newProvider: Provider<MediaCarouselInteractor>, - mediaFlags: MediaFlags, ): MediaDataManager { - return if (mediaFlags.isSceneContainerEnabled()) { + return if (SceneContainerFlag.isEnabled) { newProvider.get() } else { legacyProvider.get() diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt index 9e2804edb9ff..916f8b2e1730 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt @@ -91,6 +91,7 @@ import com.android.systemui.media.controls.util.SmallHash import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.notification.row.HybridGroupManager @@ -272,7 +273,7 @@ class MediaDataProcessor( } override fun start() { - if (!mediaFlags.isSceneContainerEnabled()) { + if (!SceneContainerFlag.isEnabled) { return } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt index 9d7160cbaffc..270ab72e291d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt @@ -105,7 +105,7 @@ constructor( val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia override fun start() { - if (!mediaFlags.isSceneContainerEnabled()) { + if (!SceneContainerFlag.isEnabled) { return } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index fb2bbde37a18..19cdee7befdd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -220,7 +220,7 @@ constructor( private val animationScaleObserver: ContentObserver = object : ContentObserver(executor, 0) { override fun onChange(selfChange: Boolean) { - if (!mediaFlags.isSceneContainerEnabled()) { + if (!SceneContainerFlag.isEnabled) { MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() } } else { controllerById.values.forEach { it.updateAnimatorDurationScale() } @@ -350,7 +350,7 @@ constructor( inflateSettingsButton() mediaContent = mediaCarousel.requireViewById(R.id.media_carousel) configurationController.addCallback(configListener) - if (!mediaFlags.isSceneContainerEnabled()) { + if (!SceneContainerFlag.isEnabled) { setUpListeners() } else { val visualStabilityCallback = OnReorderingAllowedListener { @@ -391,7 +391,7 @@ constructor( listenForAnyStateToGoneKeyguardTransition(this) listenForAnyStateToLockscreenTransition(this) - if (!mediaFlags.isSceneContainerEnabled()) return@repeatOnLifecycle + if (!SceneContainerFlag.isEnabled) return@repeatOnLifecycle listenForMediaItemsChanges(this) } } @@ -733,7 +733,7 @@ constructor( when (commonViewModel) { is MediaCommonViewModel.MediaControl -> { val viewHolder = MediaViewHolder.create(LayoutInflater.from(context), mediaContent) - if (mediaFlags.isSceneContainerEnabled()) { + if (SceneContainerFlag.isEnabled) { viewController.widthInSceneContainerPx = widthInSceneContainerPx viewController.heightInSceneContainerPx = heightInSceneContainerPx } @@ -965,7 +965,7 @@ constructor( .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) if (existingPlayer == null) { val newPlayer = mediaControlPanelFactory.get() - if (mediaFlags.isSceneContainerEnabled()) { + if (SceneContainerFlag.isEnabled) { newPlayer.mediaViewController.widthInSceneContainerPx = widthInSceneContainerPx newPlayer.mediaViewController.heightInSceneContainerPx = heightInSceneContainerPx @@ -1140,7 +1140,7 @@ constructor( } private fun updatePlayers(recreateMedia: Boolean) { - if (mediaFlags.isSceneContainerEnabled()) { + if (SceneContainerFlag.isEnabled) { updateMediaPlayers(recreateMedia) return } @@ -1240,7 +1240,7 @@ constructor( currentStartLocation = startLocation currentEndLocation = endLocation currentTransitionProgress = progress - if (!mediaFlags.isSceneContainerEnabled()) { + if (!SceneContainerFlag.isEnabled) { for (mediaPlayer in MediaPlayerData.players()) { updateViewControllerToState(mediaPlayer.mediaViewController, immediately) } @@ -1300,7 +1300,7 @@ constructor( /** Update listening to seekbar. */ private fun updateSeekbarListening(visibleToUser: Boolean) { - if (!mediaFlags.isSceneContainerEnabled()) { + if (!SceneContainerFlag.isEnabled) { for (player in MediaPlayerData.players()) { player.setListening(visibleToUser && currentlyExpanded) } @@ -1313,7 +1313,7 @@ constructor( private fun updateCarouselDimensions() { var width = 0 var height = 0 - if (!mediaFlags.isSceneContainerEnabled()) { + if (!SceneContainerFlag.isEnabled) { for (mediaPlayer in MediaPlayerData.players()) { val controller = mediaPlayer.mediaViewController // When transitioning the view to gone, the view gets smaller, but the translation @@ -1405,7 +1405,7 @@ constructor( !mediaManager.hasActiveMediaOrRecommendation() && desiredHostState.showsOnlyActiveMedia - if (!mediaFlags.isSceneContainerEnabled()) { + if (!SceneContainerFlag.isEnabled) { for (mediaPlayer in MediaPlayerData.players()) { if (animate) { mediaPlayer.mediaViewController.animatePendingStateChange( @@ -1445,7 +1445,7 @@ constructor( } fun closeGuts(immediate: Boolean = true) { - if (!mediaFlags.isSceneContainerEnabled()) { + if (!SceneContainerFlag.isEnabled) { MediaPlayerData.players().forEach { it.closeGuts(immediate) } } else { controllerById.values.forEach { it.closeGuts(immediate) } @@ -1596,7 +1596,7 @@ constructor( @VisibleForTesting fun onSwipeToDismiss() { - if (mediaFlags.isSceneContainerEnabled()) { + if (SceneContainerFlag.isEnabled) { mediaCarouselViewModel.onSwipeToDismiss(currentEndLocation) return } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index addb0147889f..87610cf774a3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -111,7 +111,6 @@ import com.android.systemui.media.controls.ui.view.MediaViewHolder; import com.android.systemui.media.controls.ui.view.RecommendationViewHolder; import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel; import com.android.systemui.media.controls.util.MediaDataUtils; -import com.android.systemui.media.controls.util.MediaFlags; import com.android.systemui.media.controls.util.MediaUiEventLogger; import com.android.systemui.media.controls.util.SmallHash; import com.android.systemui.media.dialog.MediaOutputDialogManager; @@ -120,6 +119,7 @@ import com.android.systemui.monet.Style; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -209,7 +209,6 @@ public class MediaControlPanel { static final long TURBULENCE_NOISE_PLAY_DURATION = 7500L; private final SeekBarViewModel mSeekBarViewModel; - private final MediaFlags mMediaFlags; private final CommunalSceneInteractor mCommunalSceneInteractor; private SeekBarObserver mSeekBarObserver; protected final Executor mBackgroundExecutor; @@ -323,8 +322,7 @@ public class MediaControlPanel { CommunalSceneInteractor communalSceneInteractor, NotificationLockscreenUserManager lockscreenUserManager, BroadcastDialogController broadcastDialogController, - GlobalSettings globalSettings, - MediaFlags mediaFlags + GlobalSettings globalSettings ) { mContext = context; mBackgroundExecutor = backgroundExecutor; @@ -343,7 +341,6 @@ public class MediaControlPanel { mActivityIntentHelper = activityIntentHelper; mLockscreenUserManager = lockscreenUserManager; mBroadcastDialogController = broadcastDialogController; - mMediaFlags = mediaFlags; mCommunalSceneInteractor = communalSceneInteractor; mSeekBarViewModel.setLogSeek(() -> { @@ -641,7 +638,7 @@ public class MediaControlPanel { // State refresh interferes with the translation animation, only run it if it's not running. if (!mMetadataAnimationHandler.isRunning()) { // Don't refresh in scene framework, because it will calculate with invalid layout sizes - if (!mMediaFlags.isSceneContainerEnabled()) { + if (!SceneContainerFlag.isEnabled()) { mMediaViewController.refreshState(); } } @@ -909,7 +906,7 @@ public class MediaControlPanel { // Capture width & height from views in foreground for artwork scaling in background int width = mMediaViewHolder.getAlbumView().getMeasuredWidth(); int height = mMediaViewHolder.getAlbumView().getMeasuredHeight(); - if (mMediaFlags.isSceneContainerEnabled() && (width <= 0 || height <= 0)) { + if (SceneContainerFlag.isEnabled() && (width <= 0 || height <= 0)) { // TODO(b/312714128): ensure we have a valid size before setting background width = mMediaViewController.getWidthInSceneContainerPx(); height = mMediaViewController.getHeightInSceneContainerPx(); diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt index 091b886c7ba4..a9d2a541a241 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt @@ -46,10 +46,10 @@ import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import com.android.systemui.media.controls.ui.view.MediaHost -import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.dream.MediaDreamComplication import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.CrossFadeHelper import com.android.systemui.statusbar.StatusBarState @@ -119,7 +119,6 @@ constructor( @Application private val coroutineScope: CoroutineScope, private val splitShadeStateController: SplitShadeStateController, private val logger: MediaViewLogger, - private val mediaFlags: MediaFlags, ) { /** Track the media player setting status on lock screen. */ @@ -1111,7 +1110,7 @@ constructor( private fun updateHostAttachment() = traceSection("MediaHierarchyManager#updateHostAttachment") { - if (mediaFlags.isSceneContainerEnabled()) { + if (SceneContainerFlag.isEnabled) { // No need to manage transition states - just update the desired location directly logger.logMediaHostAttachment(desiredLocation) mediaCarouselController.onDesiredLocationChanged( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index 584908ff2aad..e57de09f1063 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -46,8 +46,8 @@ import com.android.systemui.media.controls.ui.view.MediaViewHolder import com.android.systemui.media.controls.ui.view.RecommendationViewHolder import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel -import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.surfaceeffects.PaintDrawCallback import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect @@ -82,7 +82,6 @@ constructor( private val logger: MediaViewLogger, private val seekBarViewModel: SeekBarViewModel, @Main private val mainExecutor: DelayableExecutor, - private val mediaFlags: MediaFlags, private val globalSettings: GlobalSettings, ) { @@ -125,7 +124,7 @@ constructor( set(value) { if (field != value) { field = value - if (!mediaFlags.isSceneContainerEnabled()) return + if (!SceneContainerFlag.isEnabled) return locationChangeListener(value) } } @@ -212,7 +211,7 @@ constructor( private val scrubbingChangeListener = object : SeekBarViewModel.ScrubbingChangeListener { override fun onScrubbingChanged(scrubbing: Boolean) { - if (!mediaFlags.isSceneContainerEnabled()) return + if (!SceneContainerFlag.isEnabled) return if (isScrubbing == scrubbing) return isScrubbing = scrubbing updateDisplayForScrubbingChange() @@ -222,7 +221,7 @@ constructor( private val enabledChangeListener = object : SeekBarViewModel.EnabledChangeListener { override fun onEnabledChanged(enabled: Boolean) { - if (!mediaFlags.isSceneContainerEnabled()) return + if (!SceneContainerFlag.isEnabled) return if (isSeekBarEnabled == enabled) return isSeekBarEnabled = enabled MediaControlViewBinder.updateSeekBarVisibility(expandedLayout, isSeekBarEnabled) @@ -238,7 +237,7 @@ constructor( * @param listening True when player should be active. Otherwise, false. */ fun setListening(listening: Boolean) { - if (!mediaFlags.isSceneContainerEnabled()) return + if (!SceneContainerFlag.isEnabled) return seekBarViewModel.listening = listening } @@ -272,7 +271,7 @@ constructor( ) ) } - if (mediaFlags.isSceneContainerEnabled()) { + if (SceneContainerFlag.isEnabled) { if ( this@MediaViewController::recsConfigurationChangeListener.isInitialized ) { @@ -344,7 +343,7 @@ constructor( * Notify this controller that the view has been removed and all listeners should be destroyed */ fun onDestroy() { - if (mediaFlags.isSceneContainerEnabled()) { + if (SceneContainerFlag.isEnabled) { if (this::seekBarObserver.isInitialized) { seekBarViewModel.progress.removeObserver(seekBarObserver) } @@ -565,7 +564,7 @@ constructor( state: MediaHostState?, isGutsAnimation: Boolean = false ): TransitionViewState? { - if (mediaFlags.isSceneContainerEnabled()) { + if (SceneContainerFlag.isEnabled) { return obtainSceneContainerViewState() } @@ -667,7 +666,7 @@ constructor( } fun attachPlayer(mediaViewHolder: MediaViewHolder) { - if (!mediaFlags.isSceneContainerEnabled()) return + if (!SceneContainerFlag.isEnabled) return this.mediaViewHolder = mediaViewHolder // Setting up seek bar. @@ -741,7 +740,7 @@ constructor( } fun updateAnimatorDurationScale() { - if (!mediaFlags.isSceneContainerEnabled()) return + if (!SceneContainerFlag.isEnabled) return if (this::seekBarObserver.isInitialized) { seekBarObserver.animationEnabled = globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f @@ -801,7 +800,7 @@ constructor( } fun attachRecommendations(recommendationViewHolder: RecommendationViewHolder) { - if (!mediaFlags.isSceneContainerEnabled()) return + if (!SceneContainerFlag.isEnabled) return this.recommendationViewHolder = recommendationViewHolder attach(recommendationViewHolder.recommendations, TYPE.RECOMMENDATION) @@ -810,13 +809,13 @@ constructor( } fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) { - if (!mediaFlags.isSceneContainerEnabled()) return + if (!SceneContainerFlag.isEnabled) return seekBarViewModel.logSeek = onSeek onBindSeekBar(seekBarViewModel) } fun setUpTurbulenceNoise() { - if (!mediaFlags.isSceneContainerEnabled()) return + if (!SceneContainerFlag.isEnabled) return mediaViewHolder!!.let { if (!this::turbulenceNoiseAnimationConfig.isInitialized) { turbulenceNoiseAnimationConfig = @@ -1049,7 +1048,7 @@ constructor( */ private fun obtainViewStateForLocation(@MediaLocation location: Int): TransitionViewState? { val mediaHostState = mediaHostStatesManager.mediaHostStates[location] ?: return null - if (mediaFlags.isSceneContainerEnabled()) { + if (SceneContainerFlag.isEnabled) { return obtainSceneContainerViewState() } @@ -1080,7 +1079,7 @@ constructor( /** Clear all existing measurements and refresh the state to match the view. */ fun refreshState() = traceSection("MediaViewController#refreshState") { - if (mediaFlags.isSceneContainerEnabled()) { + if (SceneContainerFlag.isEnabled) { // We don't need to recreate measurements for scene container, since it's a known // size. Just get the view state and update the layout controller obtainSceneContainerViewState()?.let { @@ -1169,13 +1168,13 @@ constructor( } fun setUpPrevButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) { - if (!mediaFlags.isSceneContainerEnabled()) return + if (!SceneContainerFlag.isEnabled) return isPrevButtonAvailable = isAvailable prevNotVisibleValue = notVisibleValue } fun setUpNextButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) { - if (!mediaFlags.isSceneContainerEnabled()) return + if (!SceneContainerFlag.isEnabled) return isNextButtonAvailable = isAvailable nextNotVisibleValue = notVisibleValue } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index 21c311191710..a65243dfe315 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -21,7 +21,6 @@ import android.os.UserHandle import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.scene.shared.flag.SceneContainerFlag import javax.inject.Inject @SysUISingleton @@ -49,7 +48,4 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlagsClass /** Check whether we allow remote media to generate resume controls */ fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME) - - /** Check whether to use scene framework */ - fun isSceneContainerEnabled() = SceneContainerFlag.isEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt index 42f66cca2522..7d2a1e178dfc 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt +++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt @@ -18,7 +18,6 @@ package com.android.systemui.model import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey -import com.android.systemui.Flags.glanceableHubBackGesture import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor @@ -107,10 +106,7 @@ constructor( { it.scene == Scenes.Lockscreen && it.invisibleDueToOcclusion }, - SYSUI_STATE_COMMUNAL_HUB_SHOWING to - { - glanceableHubBackGesture() && it.scene == Scenes.Communal - } + SYSUI_STATE_COMMUNAL_HUB_SHOWING to { it.scene == Scenes.Communal } ) } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index ac878c2d698d..6f82d5dfff15 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -19,19 +19,24 @@ package com.android.systemui.navigationbar; import static android.app.StatusBarManager.WINDOW_NAVIGATION_BAR; import static android.app.StatusBarManager.WindowVisibleState; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE; import static com.android.systemui.accessibility.SystemActions.SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON; import static com.android.systemui.accessibility.SystemActions.SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE; import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; import android.content.ContentResolver; import android.content.Context; @@ -60,10 +65,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; -import com.android.internal.accessibility.common.ShortcutConstants; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; +import com.android.systemui.accessibility.AccessibilityGestureTargetsObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; import com.android.systemui.dagger.SysUISingleton; @@ -107,6 +113,7 @@ public final class NavBarHelper implements AccessibilityManager.AccessibilityServicesStateChangeListener, AccessibilityButtonModeObserver.ModeChangedListener, AccessibilityButtonTargetsObserver.TargetsChangedListener, + AccessibilityGestureTargetsObserver.TargetsChangedListener, OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener, Dumpable, CommandQueue.Callbacks, ConfigurationController.ConfigurationListener { private static final String TAG = NavBarHelper.class.getSimpleName(); @@ -122,6 +129,7 @@ public final class NavBarHelper implements private final SystemActions mSystemActions; private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver; + private final AccessibilityGestureTargetsObserver mAccessibilityGestureTargetsObserver; private final List<NavbarTaskbarStateUpdater> mStateListeners = new ArrayList<>(); private final Context mContext; private final NotificationShadeWindowController mNotificationShadeWindowController; @@ -188,6 +196,7 @@ public final class NavBarHelper implements public NavBarHelper(Context context, AccessibilityManager accessibilityManager, AccessibilityButtonModeObserver accessibilityButtonModeObserver, AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver, + AccessibilityGestureTargetsObserver accessibilityGestureTargetsObserver, SystemActions systemActions, OverviewProxyService overviewProxyService, Lazy<AssistManager> assistManagerLazy, @@ -220,6 +229,7 @@ public final class NavBarHelper implements mSystemActions = systemActions; mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver; + mAccessibilityGestureTargetsObserver = accessibilityGestureTargetsObserver; mWm = wm; mDefaultDisplayId = displayTracker.getDefaultDisplayId(); mEdgeBackGestureHandler = edgeBackGestureHandlerFactory.create(context); @@ -249,6 +259,7 @@ public final class NavBarHelper implements mAccessibilityManager.addAccessibilityServicesStateChangeListener(this); mAccessibilityButtonModeObserver.addListener(this); mAccessibilityButtonTargetsObserver.addListener(this); + mAccessibilityGestureTargetsObserver.addListener(this); // Setup assistant listener mContentResolver.registerContentObserver( @@ -291,6 +302,7 @@ public final class NavBarHelper implements mAccessibilityManager.removeAccessibilityServicesStateChangeListener(this); mAccessibilityButtonModeObserver.removeListener(this); mAccessibilityButtonTargetsObserver.removeListener(this); + mAccessibilityGestureTargetsObserver.removeListener(this); // Clean up assistant listeners mContentResolver.unregisterContentObserver(mAssistContentObserver); @@ -380,43 +392,50 @@ public final class NavBarHelper implements } @Override + public void onAccessibilityGestureTargetsChanged(String targets) { + updateA11yState(); + } + + @Override public void onConfigChanged(Configuration newConfig) { mEdgeBackGestureHandler.onConfigurationChanged(newConfig); } + private int getNumOfA11yShortcutTargetsForNavSystem() { + final int buttonMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode(); + final int shortcutType; + if (!android.provider.Flags.a11yStandaloneGestureEnabled()) { + shortcutType = buttonMode + != ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU ? SOFTWARE : DEFAULT; + // If accessibility button is floating menu mode, there are no clickable targets. + } else { + if (mNavBarMode == NAV_BAR_MODE_GESTURAL) { + shortcutType = GESTURE; + } else { + shortcutType = buttonMode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR + ? SOFTWARE : DEFAULT; + } + } + return mAccessibilityManager.getAccessibilityShortcutTargets(shortcutType).size(); + } + /** * Updates the current accessibility button state. The accessibility button state is only * used for {@link Secure#ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR} and * {@link Secure#ACCESSIBILITY_BUTTON_MODE_GESTURE}, otherwise it is reset to 0. */ - private void updateA11yState() { + @VisibleForTesting + void updateA11yState() { final long prevState = mA11yButtonState; final boolean clickable; final boolean longClickable; - if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode() - == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { - // If accessibility button is floating menu mode, click and long click state should be - // disabled. - clickable = false; - longClickable = false; - mA11yButtonState = 0; - } else { - // AccessibilityManagerService resolves services for the current user since the local - // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS - // permission - final List<String> a11yButtonTargets = - mAccessibilityManager.getAccessibilityShortcutTargets( - ShortcutConstants.UserShortcutType.SOFTWARE); - final int requestingServices = a11yButtonTargets.size(); - - clickable = requestingServices >= 1; - - // `longClickable` is used to determine whether to pop up the accessibility chooser - // dialog or not, and it’s also only for multiple services. - longClickable = requestingServices >= 2; - mA11yButtonState = (clickable ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0) - | (longClickable ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0); - } + int clickableServices = getNumOfA11yShortcutTargetsForNavSystem(); + clickable = clickableServices >= 1; + // `longClickable` is used to determine whether to pop up the accessibility chooser + // dialog or not, and it’s also only for multiple services. + longClickable = clickableServices >= 2; + mA11yButtonState = (clickable ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0) + | (longClickable ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0); // Update the system actions if the state has changed if (prevState != mA11yButtonState) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 8887f5857baf..9abc494e56e6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -71,6 +71,7 @@ import com.android.systemui.qs.SideLabelTileLayout; import com.android.systemui.qs.logging.QSLogger; import java.io.PrintWriter; +import java.util.Objects; /** * Base quick-settings tile, extend this to create a new tile. @@ -350,6 +351,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy public void userSwitch(int newUserId) { mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget(); + postStale(); } public void destroy() { @@ -667,6 +669,18 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy public String toString() { return "DrawableIcon"; } + + @Override + public boolean equals(@Nullable Object other) { + // No need to compare equality of the mInvisibleDrawable as that's generated from + // mDrawable's constant state. + return other instanceof DrawableIcon && ((DrawableIcon) other).mDrawable == mDrawable; + } + + @Override + public int hashCode() { + return Objects.hash(mDrawable); + } } public static class DrawableIconWithRes extends DrawableIcon { diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index ecf816b263ff..fe5cbb18f046 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -26,7 +26,6 @@ import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; -import static com.android.systemui.Flags.glanceableHubBackGesture; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER; @@ -86,10 +85,10 @@ import com.android.internal.util.ScreenshotHelper; import com.android.internal.util.ScreenshotRequest; import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.contextualeducation.GestureType; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; -import com.android.systemui.contextualeducation.GestureType; import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardWmStateRefactor; @@ -231,7 +230,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // If scene framework is enabled, set the scene container window to // visible and let the touch "slip" into that window. if (SceneContainerFlag.isEnabled()) { - mSceneInteractor.get().onRemoteUserInteractionStarted("launcher swipe"); + mSceneInteractor.get().onRemoteUserInputStarted("launcher swipe"); } else { mShadeViewControllerLazy.get().startInputFocusTransfer(); } @@ -267,7 +266,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis if (SceneContainerFlag.isEnabled()) { int action = event.getActionMasked(); if (action == ACTION_DOWN) { - mSceneInteractor.get().onRemoteUserInteractionStarted( + mSceneInteractor.get().onRemoteUserInputStarted( "trackpad swipe"); } else if (action == ACTION_UP) { mSceneInteractor.get().changeScene( @@ -837,8 +836,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis .setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing) .setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing) .setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming) - .setFlag(SYSUI_STATE_COMMUNAL_HUB_SHOWING, - glanceableHubBackGesture() && communalShowing) + .setFlag(SYSUI_STATE_COMMUNAL_HUB_SHOWING, communalShowing) .commitUpdate(mContext.getDisplayId()); } diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index 3e2c6306467f..beb6816d70a9 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -56,7 +56,10 @@ constructor( * * For more information see the logic in `SceneInteractor` that mutates this. */ - val isRemoteUserInteractionOngoing = MutableStateFlow(false) + val isRemoteUserInputOngoing = MutableStateFlow(false) + + /** Whether there's ongoing user input on the scene container Composable hierarchy */ + val isSceneContainerUserInputOngoing = MutableStateFlow(false) private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey) private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 1b9c346129c6..4c404e29018d 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -148,11 +148,11 @@ constructor( val isVisible: StateFlow<Boolean> = combine( repository.isVisible, - repository.isRemoteUserInteractionOngoing, + repository.isRemoteUserInputOngoing, ) { isVisible, isRemoteUserInteractionOngoing -> isVisibleInternal( raw = isVisible, - isRemoteUserInteractionOngoing = isRemoteUserInteractionOngoing, + isRemoteUserInputOngoing = isRemoteUserInteractionOngoing, ) } .stateIn( @@ -162,8 +162,13 @@ constructor( ) /** Whether there's an ongoing remotely-initiated user interaction. */ - val isRemoteUserInteractionOngoing: StateFlow<Boolean> = - repository.isRemoteUserInteractionOngoing + val isRemoteUserInteractionOngoing: StateFlow<Boolean> = repository.isRemoteUserInputOngoing + + /** + * Whether there's an ongoing user interaction started in the scene container Compose hierarchy. + */ + val isSceneContainerUserInputOngoing: StateFlow<Boolean> = + repository.isSceneContainerUserInputOngoing /** * The amount of transition into or out of the given [scene]. @@ -284,7 +289,7 @@ constructor( * Please do not call this from outside of the scene framework. If you are trying to force the * visibility to visible or invisible, prefer making changes to the existing caller of this * method or to upstream state used to calculate [isVisible]; for an example of the latter, - * please see [onRemoteUserInteractionStarted] and [onUserInteractionFinished]. + * please see [onRemoteUserInputStarted] and [onUserInputFinished]. */ fun setVisible(isVisible: Boolean, loggingReason: String) { val wasVisible = repository.isVisible.value @@ -301,6 +306,16 @@ constructor( } /** + * Notifies that a scene container user interaction has begun. + * + * This is a user interaction that originates within the Composable hierarchy of the scene + * container. + */ + fun onSceneContainerUserInputStarted() { + repository.isSceneContainerUserInputOngoing.value = true + } + + /** * Notifies that a remote user interaction has begun. * * This is a user interaction that originates outside of the UI of the scene container and @@ -311,18 +326,19 @@ constructor( * then rerouted by window manager to System UI. While the user interaction definitely continues * within the System UI process and code, it also originates remotely. */ - fun onRemoteUserInteractionStarted(loggingReason: String) { - logger.logRemoteUserInteractionStarted(loggingReason) - repository.isRemoteUserInteractionOngoing.value = true + fun onRemoteUserInputStarted(loggingReason: String) { + logger.logRemoteUserInputStarted(loggingReason) + repository.isRemoteUserInputOngoing.value = true } /** * Notifies that the current user interaction (internally or remotely started, see - * [onRemoteUserInteractionStarted]) has finished. + * [onSceneContainerUserInputStarted] and [onRemoteUserInputStarted]) has finished. */ - fun onUserInteractionFinished() { - logger.logUserInteractionFinished() - repository.isRemoteUserInteractionOngoing.value = false + fun onUserInputFinished() { + logger.logUserInputFinished() + repository.isSceneContainerUserInputOngoing.value = false + repository.isRemoteUserInputOngoing.value = false } /** @@ -351,9 +367,9 @@ constructor( private fun isVisibleInternal( raw: Boolean = repository.isVisible.value, - isRemoteUserInteractionOngoing: Boolean = repository.isRemoteUserInteractionOngoing.value, + isRemoteUserInputOngoing: Boolean = repository.isRemoteUserInputOngoing.value, ): Boolean { - return raw || isRemoteUserInteractionOngoing + return raw || isRemoteUserInputOngoing } /** diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt index 893f030fab4d..d7413687eeae 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt @@ -166,7 +166,7 @@ constructor( StatusBarManager.DISABLE_NONE, disableToken, applicationContext.packageName, - selectedUserInteractor.getSelectedUserId(true), + selectedUserInteractor.getSelectedUserId(), ) } catch (e: RemoteException) { Log.d(TAG, "Failed to clear flags", e) diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt index 94c94e22a10b..045a8879f572 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt @@ -115,7 +115,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: ) } - fun logRemoteUserInteractionStarted( + fun logRemoteUserInputStarted( reason: String, ) { logBuffer.log( @@ -126,7 +126,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: ) } - fun logUserInteractionFinished() { + fun logUserInputFinished() { logBuffer.log( tag = TAG, level = LogLevel.INFO, diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index 9dfb7450fd3f..8b4b77f83218 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -31,6 +31,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -97,7 +98,9 @@ constructor( } /** - * Notifies that a [MotionEvent] is first seen at the top of the scene container UI. + * Notifies that a [MotionEvent] is first seen at the top of the scene container UI. This + * includes gestures on [SharedNotificationContainer] as well as the Composable scene container + * hierarchy. * * Call this before the [MotionEvent] starts to propagate through the UI hierarchy. */ @@ -108,11 +111,21 @@ constructor( event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL ) { - sceneInteractor.onUserInteractionFinished() + sceneInteractor.onUserInputFinished() } } /** + * Notifies that a scene container user interaction has begun. + * + * This is a user interaction that has reached the Composable hierarchy of the scene container, + * rather than being handled by [SharedNotificationContainer]. + */ + fun onSceneContainerUserInputStarted() { + sceneInteractor.onSceneContainerUserInputStarted() + } + + /** * Notifies that a [MotionEvent] that was previously sent to [onMotionEvent] has passed through * the scene container UI. * diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 0a1f649691a1..ed590c37c384 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -36,6 +36,13 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.util.Assert +import java.io.PrintWriter +import java.lang.ref.WeakReference +import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executor +import javax.inject.Provider +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -44,30 +51,23 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex -import java.io.PrintWriter -import java.lang.ref.WeakReference -import java.util.concurrent.CountDownLatch -import java.util.concurrent.Executor -import javax.inject.Provider -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty /** * SystemUI cache for keeping track of the current user and associated values. * - * The values provided asynchronously are NOT copies, but shared among all requesters. Do not - * modify them. + * The values provided asynchronously are NOT copies, but shared among all requesters. Do not modify + * them. * * This class purposefully doesn't use [BroadcastDispatcher] in order to receive the broadcast as - * soon as possible (and reduce its dependency graph). - * Other classes that want to listen to the broadcasts listened here SHOULD - * subscribe to this class instead. + * soon as possible (and reduce its dependency graph). Other classes that want to listen to the + * broadcasts listened here SHOULD subscribe to this class instead. * * @see UserTracker * * Class constructed and initialized in [SettingsModule]. */ -open class UserTrackerImpl internal constructor( +open class UserTrackerImpl +internal constructor( private val context: Context, private val featureFlagsProvider: Provider<FeatureFlagsClassic>, private val userManager: UserManager, @@ -87,8 +87,8 @@ open class UserTrackerImpl internal constructor( private set private val mutex = Any() - private val isBackgroundUserSwitchEnabled: Boolean get() = - featureFlagsProvider.get().isEnabled(Flags.USER_TRACKER_BACKGROUND_CALLBACKS) + private val isBackgroundUserSwitchEnabled: Boolean + get() = featureFlagsProvider.get().isEnabled(Flags.USER_TRACKER_BACKGROUND_CALLBACKS) @Deprecated("Use UserInteractor.getSelectedUserId()") override var userId: Int by SynchronizedDelegate(context.userId) @@ -118,8 +118,7 @@ open class UserTrackerImpl internal constructor( override var userProfiles: List<UserInfo> by SynchronizedDelegate(emptyList()) protected set - @GuardedBy("callbacks") - private val callbacks: MutableList<DataItem> = ArrayList() + @GuardedBy("callbacks") private val callbacks: MutableList<DataItem> = ArrayList() private var userSwitchingJob: Job? = null private var afterUserSwitchingJob: Job? = null @@ -128,23 +127,25 @@ open class UserTrackerImpl internal constructor( if (initialized) { return } + Log.i(TAG, "Starting user: $startingUser") initialized = true setUserIdInternal(startingUser) - val filter = IntentFilter().apply { - addAction(Intent.ACTION_LOCALE_CHANGED) - addAction(Intent.ACTION_USER_INFO_CHANGED) - addAction(Intent.ACTION_PROFILE_ADDED) - addAction(Intent.ACTION_PROFILE_REMOVED) - addAction(Intent.ACTION_PROFILE_AVAILABLE) - addAction(Intent.ACTION_PROFILE_UNAVAILABLE) - // These get called when a managed profile goes in or out of quiet mode. - addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) - addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) - addAction(Intent.ACTION_MANAGED_PROFILE_ADDED) - addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED) - addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED) - } + val filter = + IntentFilter().apply { + addAction(Intent.ACTION_LOCALE_CHANGED) + addAction(Intent.ACTION_USER_INFO_CHANGED) + addAction(Intent.ACTION_PROFILE_ADDED) + addAction(Intent.ACTION_PROFILE_REMOVED) + addAction(Intent.ACTION_PROFILE_AVAILABLE) + addAction(Intent.ACTION_PROFILE_UNAVAILABLE) + // These get called when a managed profile goes in or out of quiet mode. + addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) + addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) + addAction(Intent.ACTION_MANAGED_PROFILE_ADDED) + addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED) + addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED) + } context.registerReceiverForAllUsers(this, filter, null, backgroundHandler) registerUserSwitchObserver() @@ -191,36 +192,39 @@ open class UserTrackerImpl internal constructor( } private fun registerUserSwitchObserver() { - iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() { - override fun onBeforeUserSwitching(newUserId: Int) { - handleBeforeUserSwitching(newUserId) - } + iActivityManager.registerUserSwitchObserver( + object : UserSwitchObserver() { + override fun onBeforeUserSwitching(newUserId: Int) { + handleBeforeUserSwitching(newUserId) + } - override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { - if (isBackgroundUserSwitchEnabled) { - userSwitchingJob?.cancel() - userSwitchingJob = appScope.launch(backgroundContext) { - handleUserSwitchingCoroutines(newUserId) { - reply?.sendResult(null) - } + override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { + if (isBackgroundUserSwitchEnabled) { + userSwitchingJob?.cancel() + userSwitchingJob = + appScope.launch(backgroundContext) { + handleUserSwitchingCoroutines(newUserId) { reply?.sendResult(null) } + } + } else { + handleUserSwitching(newUserId) + reply?.sendResult(null) } - } else { - handleUserSwitching(newUserId) - reply?.sendResult(null) } - } - override fun onUserSwitchComplete(newUserId: Int) { - if (isBackgroundUserSwitchEnabled) { - afterUserSwitchingJob?.cancel() - afterUserSwitchingJob = appScope.launch(backgroundContext) { + override fun onUserSwitchComplete(newUserId: Int) { + if (isBackgroundUserSwitchEnabled) { + afterUserSwitchingJob?.cancel() + afterUserSwitchingJob = + appScope.launch(backgroundContext) { + handleUserSwitchComplete(newUserId) + } + } else { handleUserSwitchComplete(newUserId) } - } else { - handleUserSwitchComplete(newUserId) } - } - }, TAG) + }, + TAG + ) } @WorkerThread @@ -228,9 +232,10 @@ open class UserTrackerImpl internal constructor( setUserIdInternal(newUserId) notifySubscribers { callback, resultCallback -> - callback.onBeforeUserSwitching(newUserId) - resultCallback.run() - }.await() + callback.onBeforeUserSwitching(newUserId) + resultCallback.run() + } + .await() } @WorkerThread @@ -239,31 +244,34 @@ open class UserTrackerImpl internal constructor( Log.i(TAG, "Switching to user $newUserId") notifySubscribers { callback, resultCallback -> - callback.onUserChanging(newUserId, userContext, resultCallback) - }.await() + callback.onUserChanging(newUserId, userContext, resultCallback) + } + .await() } @WorkerThread protected open suspend fun handleUserSwitchingCoroutines(newUserId: Int, onDone: () -> Unit) = - coroutineScope { - Assert.isNotMainThread() - Log.i(TAG, "Switching to user $newUserId") + coroutineScope { + Assert.isNotMainThread() + Log.i(TAG, "Switching to user $newUserId") - for (callbackDataItem in synchronized(callbacks) { callbacks.toList() }) { - val callback: UserTracker.Callback = callbackDataItem.callback.get() ?: continue - launch(callbackDataItem.executor.asCoroutineDispatcher()) { + for (callbackDataItem in synchronized(callbacks) { callbacks.toList() }) { + val callback: UserTracker.Callback = callbackDataItem.callback.get() ?: continue + launch(callbackDataItem.executor.asCoroutineDispatcher()) { val mutex = Mutex(true) - val thresholdLogJob = launch(backgroundContext) { - delay(USER_CHANGE_THRESHOLD) - Log.e(TAG, "Failed to finish $callback in time") - } + val thresholdLogJob = + launch(backgroundContext) { + delay(USER_CHANGE_THRESHOLD) + Log.e(TAG, "Failed to finish $callback in time") + } callback.onUserChanging(userId, userContext) { mutex.unlock() } mutex.lock() thresholdLogJob.cancel() - }.join() - } - onDone() + } + .join() } + onDone() + } @WorkerThread protected open fun handleUserSwitchComplete(newUserId: Int) { @@ -284,36 +292,26 @@ open class UserTrackerImpl internal constructor( synchronized(mutex) { userProfiles = profiles.map { UserInfo(it) } // save a "deep" copy } - notifySubscribers { callback, _ -> - callback.onProfilesChanged(profiles) - } + notifySubscribers { callback, _ -> callback.onProfilesChanged(profiles) } } override fun addCallback(callback: UserTracker.Callback, executor: Executor) { - synchronized(callbacks) { - callbacks.add(DataItem(WeakReference(callback), executor)) - } + synchronized(callbacks) { callbacks.add(DataItem(WeakReference(callback), executor)) } } override fun removeCallback(callback: UserTracker.Callback) { - synchronized(callbacks) { - callbacks.removeIf { it.sameOrEmpty(callback) } - } + synchronized(callbacks) { callbacks.removeIf { it.sameOrEmpty(callback) } } } private inline fun notifySubscribers( - crossinline action: (UserTracker.Callback, resultCallback: Runnable) -> Unit + crossinline action: (UserTracker.Callback, resultCallback: Runnable) -> Unit ): CountDownLatch { - val list = synchronized(callbacks) { - callbacks.toList() - } + val list = synchronized(callbacks) { callbacks.toList() } val latch = CountDownLatch(list.size) list.forEach { val callback = it.callback.get() if (callback != null) { - it.executor.execute { - action(callback) { latch.countDown() } - } + it.executor.execute { action(callback) { latch.countDown() } } } else { latch.countDown() } @@ -328,20 +326,13 @@ open class UserTrackerImpl internal constructor( val ids = userProfiles.map { it.toFullString() } pw.println("userProfiles: $ids") } - val list = synchronized(callbacks) { - callbacks.toList() - } + val list = synchronized(callbacks) { callbacks.toList() } pw.println("Callbacks:") - list.forEach { - it.callback.get()?.let { - pw.println(" $it") - } - } + list.forEach { it.callback.get()?.let { pw.println(" $it") } } } - private class SynchronizedDelegate<T : Any>( - private var value: T - ) : ReadWriteProperty<UserTrackerImpl, T> { + private class SynchronizedDelegate<T : Any>(private var value: T) : + ReadWriteProperty<UserTrackerImpl, T> { @GuardedBy("mutex") override fun getValue(thisRef: UserTrackerImpl, property: KProperty<*>): T { diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 6223ca778815..4639e2235346 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -17,7 +17,6 @@ package com.android.systemui.shade import android.content.Context -import android.graphics.Insets import android.graphics.Rect import android.os.PowerManager import android.os.SystemClock @@ -26,7 +25,6 @@ import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.ViewGroup -import android.view.WindowInsets import android.widget.FrameLayout import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner @@ -42,7 +40,6 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.compose.theme.PlatformTheme import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Flags -import com.android.systemui.Flags.glanceableHubBackGesture import com.android.systemui.ambient.touch.TouchMonitor import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent import com.android.systemui.communal.dagger.Communal @@ -322,21 +319,13 @@ constructor( // Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not // occluded. lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) { - // Avoid adding exclusion to end/start edges to allow back gestures. - val insets = - if (glanceableHubBackGesture()) { - containerView.rootWindowInsets.getInsets(WindowInsets.Type.systemGestures()) - } else { - Insets.NONE - } - val ltr = containerView.layoutDirection == View.LAYOUT_DIRECTION_LTR val backGestureInset = Rect( - if (ltr) 0 else insets.left, 0, - if (ltr) insets.right else containerView.right, + 0, + if (ltr) 0 else containerView.right, containerView.bottom, ) @@ -352,9 +341,9 @@ constructor( // Only allow swipe up to bouncer and swipe down to shade in the very // top/bottom to avoid conflicting with widgets in the hub grid. Rect( - insets.left, + 0, topEdgeSwipeRegionWidth, - containerView.right - insets.right, + containerView.right, containerView.bottom - bottomEdgeSwipeRegionWidth ), // Disable back gestures on the left side of the screen, to avoid diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt index 2f9848863059..f270e821840a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt @@ -17,14 +17,15 @@ package com.android.systemui.shade.ui.viewmodel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge 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.OCCLUDED -import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.util.kotlin.BooleanFlowOperators.any import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map /** Models UI state for the shade window. */ @SysUISingleton @@ -32,11 +33,38 @@ class NotificationShadeWindowModel @Inject constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, - keyguardInteractor: KeyguardInteractor, ) { + /** + * Considered to be occluded if in OCCLUDED, DREAMING, GLANCEABLE_HUB/Communal, or transitioning + * between those states. Every permutation is listed so we can use optimal flows and support + * Scenes. + */ val isKeyguardOccluded: Flow<Boolean> = - anyOf( - keyguardTransitionInteractor.transitionValue(OCCLUDED).map { it == 1f }, - keyguardTransitionInteractor.transitionValue(DREAMING).map { it == 1f }, - ) + listOf( + // Finished in state... + keyguardTransitionInteractor.isFinishedIn(OCCLUDED), + keyguardTransitionInteractor.isFinishedIn(DREAMING), + keyguardTransitionInteractor.isFinishedIn(Scenes.Communal, GLANCEABLE_HUB), + + // ... or transitions between those states + keyguardTransitionInteractor.isInTransition(Edge.create(OCCLUDED, DREAMING)), + keyguardTransitionInteractor.isInTransition(Edge.create(DREAMING, OCCLUDED)), + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = OCCLUDED, to = Scenes.Communal), + edgeWithoutSceneContainer = Edge.create(from = OCCLUDED, to = GLANCEABLE_HUB), + ), + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = Scenes.Communal, to = OCCLUDED), + edgeWithoutSceneContainer = Edge.create(from = GLANCEABLE_HUB, to = OCCLUDED), + ), + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = DREAMING, to = Scenes.Communal), + edgeWithoutSceneContainer = Edge.create(from = DREAMING, to = GLANCEABLE_HUB), + ), + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = Scenes.Communal, to = DREAMING), + edgeWithoutSceneContainer = Edge.create(from = GLANCEABLE_HUB, to = DREAMING), + ), + ) + .any() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index d0c51bc28126..bf00a39c42ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -1255,6 +1255,11 @@ public class NotificationStackScrollLayout } @Override + public void closeGutsOnSceneTouch() { + mController.closeControlsDueToOutsideTouch(); + } + + @Override public void setSyntheticScrollConsumer(@Nullable Consumer<Float> consumer) { mScrollViewFields.setSyntheticScrollConsumer(consumer); } @@ -1265,6 +1270,11 @@ public class NotificationStackScrollLayout } @Override + public void setCurrentGestureInGutsConsumer(@Nullable Consumer<Boolean> consumer) { + mScrollViewFields.setCurrentGestureInGutsConsumer(consumer); + } + + @Override public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) { mScrollViewFields.setHeadsUpHeightConsumer(consumer); } @@ -3548,33 +3558,41 @@ public class NotificationStackScrollLayout @Override public boolean dispatchTouchEvent(MotionEvent ev) { - if (SceneContainerFlag.isEnabled() && mIsBeingDragged) { + if (SceneContainerFlag.isEnabled()) { int action = ev.getActionMasked(); - boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL; - if (mSendingTouchesToSceneFramework) { - MotionEvent adjustedEvent = MotionEvent.obtain(ev); - adjustedEvent.setLocation(ev.getRawX(), ev.getRawY()); - mController.sendTouchToSceneFramework(adjustedEvent); - mScrollViewFields.sendCurrentGestureOverscroll( - getExpandedInThisMotion() && !isUpOrCancel); - adjustedEvent.recycle(); - } else if (!isUpOrCancel) { - // if this is the first touch being sent to the scene framework, - // convert it into a synthetic DOWN event. - mSendingTouchesToSceneFramework = true; - MotionEvent downEvent = MotionEvent.obtain(ev); - downEvent.setAction(MotionEvent.ACTION_DOWN); - downEvent.setLocation(ev.getRawX(), ev.getRawY()); - mController.sendTouchToSceneFramework(downEvent); - mScrollViewFields.sendCurrentGestureOverscroll(getExpandedInThisMotion()); - downEvent.recycle(); - } - - if (isUpOrCancel) { - mScrollViewFields.sendCurrentGestureOverscroll(false); - setIsBeingDragged(false); + boolean isTouchInGuts = mController.isTouchInGutsView(ev); + if (action == MotionEvent.ACTION_DOWN && !isTouchInGuts) { + mController.closeControlsDueToOutsideTouch(); + } + if (mIsBeingDragged) { + boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL; + if (mSendingTouchesToSceneFramework) { + MotionEvent adjustedEvent = MotionEvent.obtain(ev); + adjustedEvent.setLocation(ev.getRawX(), ev.getRawY()); + mScrollViewFields.sendCurrentGestureOverscroll( + getExpandedInThisMotion() && !isUpOrCancel); + mController.sendTouchToSceneFramework(adjustedEvent); + adjustedEvent.recycle(); + } else if (!isUpOrCancel) { + // if this is the first touch being sent to the scene framework, + // convert it into a synthetic DOWN event. + mSendingTouchesToSceneFramework = true; + MotionEvent downEvent = MotionEvent.obtain(ev); + downEvent.setAction(MotionEvent.ACTION_DOWN); + downEvent.setLocation(ev.getRawX(), ev.getRawY()); + mScrollViewFields.sendCurrentGestureInGuts(isTouchInGuts); + mScrollViewFields.sendCurrentGestureOverscroll(getExpandedInThisMotion()); + mController.sendTouchToSceneFramework(downEvent); + downEvent.recycle(); + } + + if (isUpOrCancel) { + mScrollViewFields.sendCurrentGestureInGuts(false); + mScrollViewFields.sendCurrentGestureOverscroll(false); + setIsBeingDragged(false); + } + return false; } - return false; } return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index c25b30db7754..4e73529b911d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1690,7 +1690,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { mVisibilityProvider.obtain(entry, true)); } - public void closeControlsIfOutsideTouch(MotionEvent ev) { + private View getGutsView() { NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); View translatingParentView = mSwipeHelper.getTranslatingParentView(); @@ -1703,15 +1703,35 @@ public class NotificationStackScrollLayoutController implements Dumpable { // Checking menu view = translatingParentView; } + return view; + } + + public void closeControlsIfOutsideTouch(MotionEvent ev) { + SceneContainerFlag.assertInLegacyMode(); + View view = getGutsView(); if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) { // Touch was outside visible guts / menu notification, close what's visible - mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */, - false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, - false /* resetMenu */); - mSwipeHelper.resetExposedMenuView(true /* animate */, true /* force */); + closeAndSaveGuts(); } } + void closeControlsDueToOutsideTouch() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + closeAndSaveGuts(); + } + + private void closeAndSaveGuts() { + mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */, + false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, + false /* resetMenu */); + mSwipeHelper.resetExposedMenuView(true /* animate */, true /* force */); + } + + boolean isTouchInGutsView(MotionEvent event) { + View view = getGutsView(); + return NotificationSwipeHelper.isTouchInView(event, view); + } + public void clearSilentNotifications() { FooterViewRefactor.assertInLegacyMode(); // Leave the shade open if there will be other notifs left over to clear diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt index 383d8b3b26b5..aa3953987c10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -53,6 +53,11 @@ class ScrollViewFields { */ var currentGestureOverscrollConsumer: Consumer<Boolean>? = null /** + * When a gesture is on open notification guts, which means scene container should not close the + * guts off of this gesture, we can notify the placeholder through here. + */ + var currentGestureInGutsConsumer: Consumer<Boolean>? = null + /** * Any time the heads up height is recalculated, it should be updated here to be used by the * placeholder */ @@ -66,6 +71,10 @@ class ScrollViewFields { fun sendCurrentGestureOverscroll(isCurrentGestureOverscroll: Boolean) = currentGestureOverscrollConsumer?.accept(isCurrentGestureOverscroll) + /** send [isCurrentGestureInGuts] to the [currentGestureInGutsConsumer], if present. */ + fun sendCurrentGestureInGuts(isCurrentGestureInGuts: Boolean) = + currentGestureInGutsConsumer?.accept(isCurrentGestureInGuts) + /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */ fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt index f6d9351952f1..4907d444070d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt @@ -39,4 +39,7 @@ class NotificationViewHeightRepository @Inject constructor() { * consumed part of the gesture. */ val isCurrentGestureOverscroll = MutableStateFlow(false) + + /** Whether the current touch gesture is on any open notification guts. */ + val isCurrentGestureInGuts = MutableStateFlow(false) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 8557afc6ebd3..756cd87970a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.data.repository.NotificationPlaceholderRepository @@ -39,6 +40,7 @@ class NotificationStackAppearanceInteractor constructor( private val viewHeightRepository: NotificationViewHeightRepository, private val placeholderRepository: NotificationPlaceholderRepository, + sceneInteractor: SceneInteractor, shadeInteractor: ShadeInteractor, ) { /** The bounds of the notification stack in the current scene. */ @@ -93,6 +95,15 @@ constructor( val isCurrentGestureOverscroll: Flow<Boolean> = viewHeightRepository.isCurrentGestureOverscroll.asStateFlow() + /** Whether we should close any notification guts that are currently open. */ + val shouldCloseGuts: Flow<Boolean> = + combine( + sceneInteractor.isSceneContainerUserInputOngoing, + viewHeightRepository.isCurrentGestureInGuts + ) { isUserInputOngoing, isCurrentGestureInGuts -> + isUserInputOngoing && !isCurrentGestureInGuts + } + /** Sets the alpha to apply to the NSSL for the brightness mirror */ fun setAlphaForBrightnessMirror(alpha: Float) { placeholderRepository.alphaForBrightnessMirror.value = alpha @@ -119,6 +130,10 @@ constructor( viewHeightRepository.isCurrentGestureOverscroll.value = isOverscroll } + fun setCurrentGestureInGuts(isInGuts: Boolean) { + viewHeightRepository.isCurrentGestureInGuts.value = isInGuts + } + fun setConstrainedAvailableSpace(height: Int) { placeholderRepository.constrainedAvailableSpace.value = height } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index 1289cec3a282..235b4da3f029 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -71,6 +71,9 @@ interface NotificationScrollView { /** Set a consumer for current gesture overscroll events */ fun setCurrentGestureOverscrollConsumer(consumer: Consumer<Boolean>?) + /** Set a consumer for current gesture in guts events */ + fun setCurrentGestureInGutsConsumer(consumer: Consumer<Boolean>?) + /** Set a consumer for heads up height changed events */ fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?) @@ -92,6 +95,12 @@ interface NotificationScrollView { /** Gets the inset for HUNs when they are not visible */ fun getHeadsUpInset(): Int + /** + * Signals that any open Notification guts should be closed, as scene container is handling + * touch events. + */ + fun closeGutsOnSceneTouch() + /** Adds a listener to be notified, when the stack height might have changed. */ fun addStackHeightChangedListener(runnable: Runnable) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index c044f6f6a9b1..3cc6e81986c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -36,6 +36,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch /** Binds the [NotificationScrollView]. */ @@ -98,13 +99,18 @@ constructor( .filter { it } .collect { view.setStackTop(-(view.getHeadsUpInset().toFloat())) } } + launch { + viewModel.shouldCloseGuts.filter { it }.collect { view.closeGutsOnSceneTouch() } + } launchAndDispose { view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer) + view.setCurrentGestureInGutsConsumer(viewModel.currentGestureInGutsConsumer) DisposableHandle { view.setSyntheticScrollConsumer(null) view.setCurrentGestureOverscrollConsumer(null) + view.setCurrentGestureInGutsConsumer(null) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index b2045fe7569a..3999578b83e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -136,6 +136,9 @@ constructor( val qsExpandFraction: Flow<Float> = shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction") + /** Whether we should close any open notification guts. */ + val shouldCloseGuts: Flow<Boolean> = stackAppearanceInteractor.shouldCloseGuts + val shouldResetStackTop: Flow<Boolean> = sceneInteractor.transitionState .mapNotNull { state -> state is Idle && state.currentScene == Scenes.Gone } @@ -202,6 +205,10 @@ constructor( val currentGestureOverscrollConsumer: (Boolean) -> Unit = stackAppearanceInteractor::setCurrentGestureOverscroll + /** Receives whether the current touch gesture is inside any open guts. */ + val currentGestureInGutsConsumer: (Boolean) -> Unit = + stackAppearanceInteractor::setCurrentGestureInGuts + /** Whether the notification stack is scrollable or not. */ val isScrollable: Flow<Boolean> = sceneInteractor.currentScene diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 0f93ff2b70ed..f11fd7b29c18 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -68,7 +68,6 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardWmStateRefactor; import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor; @@ -104,6 +103,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.unfold.FoldAodAnimationController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.kotlin.JavaAdapter; import dagger.Lazy; @@ -179,6 +179,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private float mFraction = -1f; private boolean mTracking = false; private boolean mBouncerShowingOverDream; + private int mAttemptsToShowBouncer = 0; + private DelayableExecutor mExecutor; private final PrimaryBouncerExpansionCallback mExpansionCallback = new PrimaryBouncerExpansionCallback() { @@ -315,8 +317,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mLastScreenOffAnimationPlaying; private float mQsExpansion; - private FeatureFlags mFlags; - final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>(); private boolean mIsBackAnimationEnabled; private final UdfpsOverlayInteractor mUdfpsOverlayInteractor; @@ -399,9 +399,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb JavaAdapter javaAdapter, Lazy<SceneInteractor> sceneInteractorLazy, StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor, + @Main DelayableExecutor executor, Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy ) { mContext = context; + mExecutor = executor; mViewMediatorCallback = callback; mLockPatternUtils = lockPatternUtils; mConfigurationController = configurationController; @@ -711,13 +713,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * {@link #needsFullscreenBouncer()}. */ protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) { - boolean isDozing = mDozing; - if (Flags.simPinRaceConditionOnRestart()) { - KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue() - .getTo(); - isDozing = mDozing || toState == KeyguardState.DOZING || toState == KeyguardState.AOD; - } - if (needsFullscreenBouncer() && !isDozing) { + if (needsFullscreenBouncer() && !mDozing) { // The keyguard might be showing (already). So we need to hide it. if (!primaryBouncerIsShowing()) { if (SceneContainerFlag.isEnabled()) { @@ -727,9 +723,22 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } else { if (Flags.simPinRaceConditionOnRestart()) { if (mPrimaryBouncerInteractor.show(/* isScrimmed= */ true)) { + mAttemptsToShowBouncer = 0; mCentralSurfaces.hideKeyguard(); } else { - mCentralSurfaces.showKeyguard(); + if (mAttemptsToShowBouncer > 6) { + mAttemptsToShowBouncer = 0; + Log.e(TAG, "Too many failed attempts to show bouncer, showing " + + "keyguard instead"); + mCentralSurfaces.showKeyguard(); + } else { + Log.v(TAG, "Failed to show bouncer, attempt #: " + + mAttemptsToShowBouncer++); + mExecutor.executeDelayed(() -> + showBouncerOrKeyguard(hideBouncerWhenShowing, + isFalsingReset), + 500); + } } } else { mCentralSurfaces.hideKeyguard(); @@ -1874,6 +1883,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb || mode == KeyguardSecurityModel.SecurityMode.SimPuk; } + @VisibleForTesting + void setAttemptsToShowBouncer(int attempts) { + mAttemptsToShowBouncer = attempts; + } + /** * Delegate used to send show and hide events to an alternate authentication method instead of * the regular pin/pattern/password bouncer. diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt index 59c819d41493..cd32718dbe82 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt @@ -3,8 +3,6 @@ package com.android.systemui.user.domain.interactor import android.annotation.UserIdInt import android.content.pm.UserInfo import android.os.UserManager -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.Flags.refactorGetCurrentUser import com.android.systemui.dagger.SysUISingleton import com.android.systemui.user.data.repository.UserRepository import javax.inject.Inject @@ -21,23 +19,11 @@ class SelectedUserInteractor @Inject constructor(private val repository: UserRep /** Flow providing the [UserInfo] of the currently selected user. */ val selectedUserInfo = repository.selectedUserInfo - /** - * Returns the ID of the currently-selected user. - * - * @param bypassFlag this will ignore the feature flag and get the data from the repository - * instead. This is used for refactored methods that were previously pointing to `userTracker` - * and therefore should not be routed back to KeyguardUpdateMonitor when flag is disabled. - * KeyguardUpdateMonitor.getCurrentUser() is deprecated and will be removed soon (together - * with this flag). - */ + /** Returns the ID of the currently-selected user. */ @UserIdInt @JvmOverloads - fun getSelectedUserId(bypassFlag: Boolean = false): Int { - return if (bypassFlag || refactorGetCurrentUser()) { - repository.getSelectedUserInfo().id - } else { - KeyguardUpdateMonitor.getCurrentUser() - } + fun getSelectedUserId(): Int { + return repository.getSelectedUserInfo().id } /** diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 7aa415b64316..52fde7ed72c9 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -354,7 +354,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID) .when(SubscriptionManager::getDefaultSubscriptionId); when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mCurrentUserId); - when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mCurrentUserId); mContext.getOrCreateTestableResources().addOverride( com.android.systemui.res.R.integer.config_face_auth_supported_posture, diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java new file mode 100644 index 000000000000..ba990efd5162 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java @@ -0,0 +1,104 @@ +/* + * 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.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.settings.UserTracker; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Test for {@link AccessibilityGestureTargetsObserver}. */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class AccessibilityGestureTargetsObserverTest extends SysuiTestCase { + private static final int MY_USER_ID = ActivityManager.getCurrentUser(); + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private UserTracker mUserTracker; + @Mock + private AccessibilityGestureTargetsObserver.TargetsChangedListener mListener; + + private AccessibilityGestureTargetsObserver mAccessibilityGestureTargetsObserver; + + private static final String TEST_A11Y_BTN_TARGETS = "Magnification"; + + @Before + public void setUp() { + when(mUserTracker.getUserId()).thenReturn(MY_USER_ID); + mAccessibilityGestureTargetsObserver = new AccessibilityGestureTargetsObserver(mContext, + mUserTracker); + } + + @Test + public void onChange_haveListener_invokeCallback() { + mAccessibilityGestureTargetsObserver.addListener(mListener); + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS, TEST_A11Y_BTN_TARGETS, + MY_USER_ID); + + mAccessibilityGestureTargetsObserver.mContentObserver.onChange(false); + + verify(mListener).onAccessibilityGestureTargetsChanged(TEST_A11Y_BTN_TARGETS); + } + + @Test + public void onChange_listenerRemoved_noInvokeCallback() { + mAccessibilityGestureTargetsObserver.addListener(mListener); + mAccessibilityGestureTargetsObserver.removeListener(mListener); + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS, TEST_A11Y_BTN_TARGETS, + MY_USER_ID); + + mAccessibilityGestureTargetsObserver.mContentObserver.onChange(false); + + verify(mListener, never()).onAccessibilityGestureTargetsChanged(anyString()); + } + + @Test + public void getCurrentAccessibilityGestureTargets_expectedValue() { + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS, TEST_A11Y_BTN_TARGETS, + MY_USER_ID); + + final String actualValue = + mAccessibilityGestureTargetsObserver.getCurrentAccessibilityGestureTargets(); + + assertThat(actualValue).isEqualTo(TEST_A11Y_BTN_TARGETS); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 37f1a3d73b0c..597ffef20ace 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -26,7 +26,6 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR; -import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER; import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION; import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT; import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE; @@ -250,7 +249,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { when(mCommunalTransitionViewModel.getTransitionFromOccludedEnded()) .thenReturn(mock(Flow.class)); when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId); - when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId); when(mProcessWrapper.isSystemUser()).thenReturn(true); mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl( mContext, @@ -275,7 +273,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mKosmos.getNotificationShadeWindowModel(), mKosmos::getCommunalInteractor); mFeatureFlags = new FakeFeatureFlags(); - mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER); mSetFlagsRule.disableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR); DejankUtils.setImmediate(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt index a0fe538bdd2b..3cbbb648af94 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt @@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.EnableSceneContainer @@ -62,26 +63,19 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { private val keyguardRepository = kosmos.fakeKeyguardRepository private val testScope = kosmos.testScope - private lateinit var dismissInteractorWithDependencies: - KeyguardDismissInteractorFactory.WithDependencies + private lateinit var dismissInteractor: KeyguardDismissInteractor private lateinit var underTest: KeyguardDismissActionInteractor @Before fun setUp() { MockitoAnnotations.initMocks(this) - dismissInteractorWithDependencies = - KeyguardDismissInteractorFactory.create( - context = context, - testScope = testScope, - keyguardRepository = keyguardRepository, - ) - + dismissInteractor = kosmos.keyguardDismissInteractor underTest = KeyguardDismissActionInteractor( repository = keyguardRepository, transitionInteractor = kosmos.keyguardTransitionInteractor, - dismissInteractor = dismissInteractorWithDependencies.interactor, + dismissInteractor = dismissInteractor, applicationScope = testScope.backgroundScope, sceneInteractor = kosmos.sceneInteractor, deviceEntryInteractor = kosmos.deviceEntryInteractor, @@ -166,9 +160,7 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { willAnimateOnLockscreen = true, ) ) - dismissInteractorWithDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics( - true - ) + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true) assertThat(executeDismissAction).isEqualTo(onDismissAction) } @@ -307,8 +299,7 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { @Test fun setKeyguardDone() = testScope.runTest { - val keyguardDoneTiming by - collectLastValue(dismissInteractorWithDependencies.interactor.keyguardDone) + val keyguardDoneTiming by collectLastValue(dismissInteractor.keyguardDone) runCurrent() underTest.setKeyguardDone(KeyguardDone.LATER) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt index ecb46bdd06c4..fabed03bc18c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt @@ -23,11 +23,18 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.TrustGrantFlags import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeTrustRepository import com.android.systemui.keyguard.shared.model.DismissAction import com.android.systemui.keyguard.shared.model.KeyguardDone import com.android.systemui.keyguard.shared.model.TrustModel +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.TestScope @@ -38,14 +45,16 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class KeyguardDismissInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private lateinit var dispatcher: TestDispatcher private lateinit var testScope: TestScope - private lateinit var underTestDependencies: KeyguardDismissInteractorFactory.WithDependencies - private lateinit var underTest: KeyguardDismissInteractor + private val underTest = kosmos.keyguardDismissInteractor private val userInfo = UserInfo(0, "", 0) @Before @@ -54,13 +63,7 @@ class KeyguardDismissInteractorTest : SysuiTestCase() { dispatcher = StandardTestDispatcher() testScope = TestScope(dispatcher) - underTestDependencies = - KeyguardDismissInteractorFactory.create( - context = context, - testScope = testScope, - ) - underTest = underTestDependencies.interactor - underTestDependencies.userRepository.setUserInfos(listOf(userInfo)) + kosmos.fakeUserRepository.setUserInfos(listOf(userInfo)) } @Test @@ -69,10 +72,10 @@ class KeyguardDismissInteractorTest : SysuiTestCase() { val dismissKeyguardRequestWithoutImmediateDismissAction by collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction) - underTestDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(null) + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(null) assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull() - underTestDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(true) + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true) assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit) } @@ -81,7 +84,7 @@ class KeyguardDismissInteractorTest : SysuiTestCase() { testScope.runTest { val dismissKeyguardRequestWithoutImmediateDismissAction by collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction) - underTestDependencies.trustRepository.setRequestDismissKeyguard( + kosmos.fakeTrustRepository.setRequestDismissKeyguard( TrustModel( true, 0, @@ -90,8 +93,8 @@ class KeyguardDismissInteractorTest : SysuiTestCase() { ) assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull() - underTestDependencies.powerRepository.setInteractive(true) - underTestDependencies.trustRepository.setRequestDismissKeyguard( + kosmos.fakePowerRepository.setInteractive(true) + kosmos.fakeTrustRepository.setRequestDismissKeyguard( TrustModel( true, 0, @@ -106,15 +109,15 @@ class KeyguardDismissInteractorTest : SysuiTestCase() { testScope.runTest { val dismissKeyguardRequestWithoutImmediateDismissAction by collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction) - underTestDependencies.userRepository.setSelectedUserInfo(userInfo) + kosmos.fakeUserRepository.setSelectedUserInfo(userInfo) runCurrent() // authenticated different user - underTestDependencies.bouncerRepository.setKeyguardAuthenticatedPrimaryAuth(22) + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedPrimaryAuth(22) assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull() // authenticated correct user - underTestDependencies.bouncerRepository.setKeyguardAuthenticatedPrimaryAuth(userInfo.id) + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedPrimaryAuth(userInfo.id) assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit) } @@ -123,17 +126,15 @@ class KeyguardDismissInteractorTest : SysuiTestCase() { testScope.runTest { val dismissKeyguardRequestWithoutImmediateDismissAction by collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction) - underTestDependencies.userRepository.setSelectedUserInfo(userInfo) + kosmos.fakeUserRepository.setSelectedUserInfo(userInfo) runCurrent() // requested from different user - underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated( - 22 - ) + kosmos.fakeKeyguardBouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(22) assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull() // requested from correct user - underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated( + kosmos.fakeKeyguardBouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated( userInfo.id ) assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit) @@ -159,10 +160,10 @@ class KeyguardDismissInteractorTest : SysuiTestCase() { collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction) val dismissKeyguardRequestWithImmediateDismissAction by collectLastValue(underTest.dismissKeyguardRequestWithImmediateDismissAction) - underTestDependencies.userRepository.setSelectedUserInfo(userInfo) + kosmos.fakeUserRepository.setSelectedUserInfo(userInfo) runCurrent() - underTestDependencies.keyguardRepository.setDismissAction( + kosmos.fakeKeyguardRepository.setDismissAction( DismissAction.RunImmediately( onDismissAction = { KeyguardDone.IMMEDIATE }, onCancelAction = {}, @@ -170,7 +171,7 @@ class KeyguardDismissInteractorTest : SysuiTestCase() { willAnimateOnLockscreen = true, ) ) - underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated( + kosmos.fakeKeyguardBouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated( userInfo.id ) assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull() @@ -184,10 +185,10 @@ class KeyguardDismissInteractorTest : SysuiTestCase() { collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction) val dismissKeyguardRequestWithImmediateDismissAction by collectLastValue(underTest.dismissKeyguardRequestWithImmediateDismissAction) - underTestDependencies.userRepository.setSelectedUserInfo(userInfo) + kosmos.fakeUserRepository.setSelectedUserInfo(userInfo) runCurrent() - underTestDependencies.keyguardRepository.setDismissAction( + kosmos.fakeKeyguardRepository.setDismissAction( DismissAction.RunAfterKeyguardGone( dismissAction = {}, onCancelAction = {}, @@ -195,7 +196,7 @@ class KeyguardDismissInteractorTest : SysuiTestCase() { willAnimateOnLockscreen = true, ) ) - underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated( + kosmos.fakeKeyguardBouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated( userInfo.id ) assertThat(dismissKeyguardRequestWithImmediateDismissAction).isNull() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt index 2cc8cc7d7896..99c5b7cdfdc5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt @@ -57,6 +57,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.data.repository.MediaDataRepository import com.android.systemui.media.controls.data.repository.MediaFilterRepository @@ -134,6 +135,7 @@ private fun <T> anyObject(): T { @SmallTest @RunWithLooper(setAsMainLooper = true) @RunWith(AndroidJUnit4::class) +@EnableSceneContainer class MediaDataProcessorTest : SysuiTestCase() { val kosmos = testKosmos() @@ -200,8 +202,6 @@ class MediaDataProcessorTest : SysuiTestCase() { @Before fun setup() { - whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) - staticMockSession = ExtendedMockito.mockitoSession() .mockStatic<UriGrantsManager>(UriGrantsManager::class.java) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt index f8358c51ed5c..850916be35bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt @@ -186,7 +186,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { sceneInteractor = kosmos.sceneInteractor, ) verify(configurationController).addCallback(capture(configListener)) - verify(mediaDataManager).addListener(capture(listener)) verify(visualStabilityProvider) .addPersistentReorderingAllowedListener(capture(visualStabilityCallback)) verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCallback)) @@ -405,8 +404,11 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec) } + @DisableSceneContainer @Test fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() { + verify(mediaDataManager).addListener(capture(listener)) + testPlayerOrdering() // If smartspace is prioritized @@ -439,8 +441,11 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec) } + @DisableSceneContainer @Test fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() { + verify(mediaDataManager).addListener(capture(listener)) + testPlayerOrdering() // playing paused player listener.value.onMediaDataLoaded( @@ -547,8 +552,11 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!)) } + @DisableSceneContainer @Test fun testMediaLoaded_ScrollToActivePlayer() { + verify(mediaDataManager).addListener(capture(listener)) + listener.value.onMediaDataLoaded( PLAYING_LOCAL, null, @@ -604,8 +612,11 @@ class MediaCarouselControllerTest : SysuiTestCase() { ) } + @DisableSceneContainer @Test fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() { + verify(mediaDataManager).addListener(capture(listener)) + listener.value.onSmartspaceMediaDataLoaded( SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true), @@ -647,8 +658,11 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertEquals(playerIndex, 0) } + @DisableSceneContainer @Test fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() { + verify(mediaDataManager).addListener(capture(listener)) + var result = false mediaCarouselController.updateHostVisibility = { result = true } @@ -658,8 +672,11 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertEquals(true, result) } + @DisableSceneContainer @Test fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() { + verify(mediaDataManager).addListener(capture(listener)) + var result = false mediaCarouselController.updateHostVisibility = { result = true } @@ -788,8 +805,11 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(pageIndicator, times(4)).setNumPages(any()) } + @DisableSceneContainer @Test fun testRecommendation_persistentEnabled_newSmartspaceLoaded_updatesSort() { + verify(mediaDataManager).addListener(capture(listener)) + testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded() // When an update to existing smartspace data is loaded @@ -804,8 +824,11 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active) } + @DisableSceneContainer @Test fun testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded() { + verify(mediaDataManager).addListener(capture(listener)) + whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true) // When inactive smartspace data is loaded @@ -1023,11 +1046,13 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(panel).updateAnimatorDurationScale() } + @DisableSceneContainer @Test fun swipeToDismiss_pausedAndResumeOff_userInitiated() { + verify(mediaDataManager).addListener(capture(listener)) + // When resumption is disabled, paused media should be dismissed after being swiped away Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0) - val pausedMedia = DATA.copy(isPlaying = false) listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia) mediaCarouselController.onSwipeToDismiss() @@ -1042,8 +1067,11 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true)) } + @DisableSceneContainer @Test fun swipeToDismiss_pausedAndResumeOff_delayed_userInitiated() { + verify(mediaDataManager).addListener(capture(listener)) + // When resumption is disabled, paused media should be dismissed after being swiped away Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0) mediaCarouselController.updateHostVisibility = {} @@ -1068,6 +1096,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { * @param function called when a certain configuration change occurs. */ private fun testConfigurationChange(function: () -> Unit) { + verify(mediaDataManager).addListener(capture(listener)) mediaCarouselController.pageIndicator = pageIndicator listener.value.onMediaDataLoaded( PLAYING_LOCAL, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt index 521aa5a7352b..1260a65b9c1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt @@ -70,6 +70,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.bluetooth.BroadcastDialogController import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor +import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA import com.android.systemui.media.controls.domain.pipeline.MediaDataManager @@ -84,7 +85,6 @@ import com.android.systemui.media.controls.ui.view.GutsViewHolder import com.android.systemui.media.controls.ui.view.MediaViewHolder import com.android.systemui.media.controls.ui.view.RecommendationViewHolder import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel -import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.media.dialog.MediaOutputDialogManager import com.android.systemui.monet.ColorScheme @@ -141,6 +141,7 @@ private const val APP_NAME = "APP_NAME" @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) +@DisableSceneContainer public class MediaControlPanelTest : SysuiTestCase() { @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() @@ -233,9 +234,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var recProgressBar1: SeekBar @Mock private lateinit var recProgressBar2: SeekBar @Mock private lateinit var recProgressBar3: SeekBar - private var shouldShowBroadcastButton: Boolean = false @Mock private lateinit var globalSettings: GlobalSettings - @Mock private lateinit var mediaFlags: MediaFlags @JvmField @Rule val mockito = MockitoJUnit.rule() @@ -254,7 +253,6 @@ public class MediaControlPanelTest : SysuiTestCase() { .thenReturn(applicationInfo) whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE) context.setMockPackageManager(packageManager) - whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false) player = object : @@ -278,7 +276,6 @@ public class MediaControlPanelTest : SysuiTestCase() { lockscreenUserManager, broadcastDialogController, globalSettings, - mediaFlags, ) { override fun loadAnimator( animId: Int, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt index 6c350cb4a5b0..2370bca52951 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq import com.android.systemui.dreams.DreamOverlayStateController +import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -40,7 +41,6 @@ import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.controls.ui.view.MediaHostState -import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.dream.MediaDreamComplication import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R @@ -85,6 +85,7 @@ import org.mockito.kotlin.anyOrNull @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) +@DisableSceneContainer class MediaHierarchyManagerTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -105,7 +106,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController @Mock private lateinit var shadeInteractor: ShadeInteractor @Mock lateinit var logger: MediaViewLogger - @Mock private lateinit var mediaFlags: MediaFlags @Captor private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)> @Captor @@ -139,7 +139,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() { shadeExpansion = MutableStateFlow(0f) whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade) whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansion) - whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false) mediaHierarchyManager = MediaHierarchyManager( context, @@ -160,7 +159,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() { testScope.backgroundScope, ResourcesSplitShadeStateController(), logger, - mediaFlags, ) verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture()) verify(statusBarStateController).addCallback(statusBarCallback.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt index 00b9a46f340a..e765b6f77155 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt @@ -38,12 +38,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.CachingIconView import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.media.controls.ui.view.GutsViewHolder import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.controls.ui.view.MediaViewHolder import com.android.systemui.media.controls.ui.view.RecommendationViewHolder import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel -import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.res.R import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView import com.android.systemui.surfaceeffects.ripple.MultiRippleView @@ -113,7 +113,6 @@ class MediaViewControllerTest : SysuiTestCase() { @Mock private lateinit var mediaTitleWidgetState: WidgetState @Mock private lateinit var mediaSubTitleWidgetState: WidgetState @Mock private lateinit var mediaContainerWidgetState: WidgetState - @Mock private lateinit var mediaFlags: MediaFlags @Mock private lateinit var seekBarViewModel: SeekBarViewModel @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress> @Mock private lateinit var globalSettings: GlobalSettings @@ -140,7 +139,6 @@ class MediaViewControllerTest : SysuiTestCase() { logger, seekBarViewModel, mainExecutor, - mediaFlags, globalSettings, ) { override fun loadAnimator( @@ -374,10 +372,9 @@ class MediaViewControllerTest : SysuiTestCase() { verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } } + @EnableSceneContainer @Test fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() { - whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) - mediaViewController.attachPlayer(viewHolder) getEnabledChangeListener().onEnabledChanged(enabled = true) getEnabledChangeListener().onEnabledChanged(enabled = false) @@ -386,10 +383,9 @@ class MediaViewControllerTest : SysuiTestCase() { .isEqualTo(ConstraintSet.INVISIBLE) } + @EnableSceneContainer @Test fun attachPlayer_seekBarEnabled_seekBarVisible() { - whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) - mediaViewController.attachPlayer(viewHolder) getEnabledChangeListener().onEnabledChanged(enabled = true) @@ -397,10 +393,9 @@ class MediaViewControllerTest : SysuiTestCase() { .isEqualTo(ConstraintSet.VISIBLE) } + @EnableSceneContainer @Test fun attachPlayer_seekBarStatusUpdate_seekBarVisibilityChanges() { - whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) - mediaViewController.attachPlayer(viewHolder) getEnabledChangeListener().onEnabledChanged(enabled = true) @@ -413,10 +408,9 @@ class MediaViewControllerTest : SysuiTestCase() { .isEqualTo(ConstraintSet.INVISIBLE) } + @EnableSceneContainer @Test fun attachPlayer_notScrubbing_scrubbingViewsGone() { - whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) - mediaViewController.attachPlayer(viewHolder) mediaViewController.canShowScrubbingTime = true getScrubbingChangeListener().onScrubbingChanged(true) @@ -433,10 +427,9 @@ class MediaViewControllerTest : SysuiTestCase() { .isEqualTo(ConstraintSet.GONE) } + @EnableSceneContainer @Test fun setIsScrubbing_noSemanticActions_scrubbingViewsGone() { - whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) - mediaViewController.attachPlayer(viewHolder) mediaViewController.canShowScrubbingTime = false getScrubbingChangeListener().onScrubbingChanged(true) @@ -452,10 +445,9 @@ class MediaViewControllerTest : SysuiTestCase() { .isEqualTo(ConstraintSet.GONE) } + @EnableSceneContainer @Test fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() { - whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) - mediaViewController.attachPlayer(viewHolder) mediaViewController.setUpNextButtonInfo(true) mediaViewController.setUpPrevButtonInfo(false) @@ -474,10 +466,9 @@ class MediaViewControllerTest : SysuiTestCase() { .isEqualTo(ConstraintSet.GONE) } + @EnableSceneContainer @Test fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() { - whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) - mediaViewController.attachPlayer(viewHolder) mediaViewController.setUpNextButtonInfo(false) mediaViewController.setUpPrevButtonInfo(true) @@ -496,10 +487,9 @@ class MediaViewControllerTest : SysuiTestCase() { .isEqualTo(ConstraintSet.GONE) } + @EnableSceneContainer @Test fun setIsScrubbing_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() { - whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) - mediaViewController.attachPlayer(viewHolder) mediaViewController.setUpNextButtonInfo(true) mediaViewController.setUpPrevButtonInfo(true) @@ -522,10 +512,9 @@ class MediaViewControllerTest : SysuiTestCase() { .isEqualTo(ConstraintSet.VISIBLE) } + @EnableSceneContainer @Test fun setIsScrubbing_trueThenFalse_reservePrevAndNextButtons() { - whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) - mediaViewController.attachPlayer(viewHolder) mediaViewController.setUpNextButtonInfo(true, ConstraintSet.INVISIBLE) mediaViewController.setUpPrevButtonInfo(true, ConstraintSet.INVISIBLE) diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java index bfbb7ceee6b5..a770ee199ba6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java @@ -18,7 +18,9 @@ package com.android.systemui.navigationbar; import static android.app.StatusBarManager.WINDOW_NAVIGATION_BAR; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; @@ -37,6 +39,9 @@ import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.res.Configuration; import android.os.Handler; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.provider.Flags; import android.view.IWindowManager; import android.view.accessibility.AccessibilityManager; @@ -47,6 +52,7 @@ import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutT import com.android.systemui.SysuiTestCase; import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; +import com.android.systemui.accessibility.AccessibilityGestureTargetsObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; import com.android.systemui.dump.DumpManager; @@ -94,6 +100,8 @@ public class NavBarHelperTest extends SysuiTestCase { @Mock AccessibilityButtonTargetsObserver mAccessibilityButtonTargetObserver; @Mock + AccessibilityGestureTargetsObserver mAccessibilityGestureTargetObserver; + @Mock SystemActions mSystemActions; @Mock OverviewProxyService mOverviewProxyService; @@ -152,6 +160,7 @@ public class NavBarHelperTest extends SysuiTestCase { mAccessibilityManager).addAccessibilityServicesStateChangeListener(any()); mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager, mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver, + mAccessibilityGestureTargetObserver, mSystemActions, mOverviewProxyService, mAssistManagerLazy, () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class), mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker, @@ -171,6 +180,7 @@ public class NavBarHelperTest extends SysuiTestCase { mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); verify(mAccessibilityButtonModeObserver, times(1)).addListener(mNavBarHelper); verify(mAccessibilityButtonTargetObserver, times(1)).addListener(mNavBarHelper); + verify(mAccessibilityGestureTargetObserver, times(1)).addListener(mNavBarHelper); verify(mAccessibilityManager, times(1)).addAccessibilityServicesStateChangeListener( mNavBarHelper); verify(mAssistManager, times(1)).getAssistInfoForUser(anyInt()); @@ -185,6 +195,7 @@ public class NavBarHelperTest extends SysuiTestCase { mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); verify(mAccessibilityButtonModeObserver, times(1)).removeListener(mNavBarHelper); verify(mAccessibilityButtonTargetObserver, times(1)).removeListener(mNavBarHelper); + verify(mAccessibilityGestureTargetObserver, times(1)).removeListener(mNavBarHelper); verify(mAccessibilityManager, times(1)).removeAccessibilityServicesStateChangeListener( mNavBarHelper); verify(mWm, times(1)).removeRotationWatcher(any()); @@ -353,6 +364,83 @@ public class NavBarHelperTest extends SysuiTestCase { verify(mEdgeBackGestureHandler, times(1)).onConfigurationChanged(any()); } + @Test + public void updateA11yState_navBarMode_softwareTargets_isClickable() { + when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn( + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.SOFTWARE)) + .thenReturn(createFakeShortcutTargets()); + + mNavBarHelper.updateA11yState(); + long state = mNavBarHelper.getA11yButtonState(); + assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo( + SYSUI_STATE_A11Y_BUTTON_CLICKABLE); + assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo( + SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE); + } + + @Test + @DisableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED) + public void updateA11yState_gestureMode_softwareTargets_isClickable() { + when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn( + ACCESSIBILITY_BUTTON_MODE_GESTURE); + when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.SOFTWARE)) + .thenReturn(createFakeShortcutTargets()); + + mNavBarHelper.updateA11yState(); + long state = mNavBarHelper.getA11yButtonState(); + assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo( + SYSUI_STATE_A11Y_BUTTON_CLICKABLE); + assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo( + SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE); + } + + @Test + @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED) + public void updateA11yState_gestureNavMode_floatingButtonMode_gestureTargets_isClickable() { + mNavBarHelper.onNavigationModeChanged(NAV_BAR_MODE_GESTURAL); + when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn( + ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.GESTURE)) + .thenReturn(createFakeShortcutTargets()); + + mNavBarHelper.updateA11yState(); + long state = mNavBarHelper.getA11yButtonState(); + assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo( + SYSUI_STATE_A11Y_BUTTON_CLICKABLE); + assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo( + SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE); + } + + @Test + @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED) + public void updateA11yState_navBarMode_gestureTargets_isNotClickable() { + when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn( + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.GESTURE)) + .thenReturn(createFakeShortcutTargets()); + + mNavBarHelper.updateA11yState(); + long state = mNavBarHelper.getA11yButtonState(); + assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo(0); + assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo(0); + } + + @Test + @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED) + public void updateA11yState_singleTarget_clickableButNotLongClickable() { + when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn( + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.SOFTWARE)) + .thenReturn(new ArrayList<>(List.of("a"))); + + mNavBarHelper.updateA11yState(); + long state = mNavBarHelper.getA11yButtonState(); + assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo( + SYSUI_STATE_A11Y_BUTTON_CLICKABLE); + assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo(0); + } + private List<String> createFakeShortcutTargets() { return new ArrayList<>(List.of("a", "b", "c", "d")); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java index 04d140c458e8..2905a7329d21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java @@ -85,6 +85,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestableContext; import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; +import com.android.systemui.accessibility.AccessibilityGestureTargetsObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; import com.android.systemui.dump.DumpManager; @@ -287,6 +288,7 @@ public class NavigationBarTest extends SysuiTestCase { mNavBarHelper = spy(new NavBarHelper(mContext, mock(AccessibilityManager.class), mock(AccessibilityButtonModeObserver.class), mock(AccessibilityButtonTargetsObserver.class), + mock(AccessibilityGestureTargetsObserver.class), mSystemActions, mOverviewProxyService, () -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces), mKeyguardStateController, mock(NavigationModeController.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index ebab04989590..748c7d9d939b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -497,6 +497,17 @@ public class QSTileImplTest extends SysuiTestCase { assertThat(mTile.mRefreshes).isEqualTo(1); } + @Test + public void testStaleTriggeredOnUserSwitch() { + mTile.clearRefreshes(); + + mTile.userSwitch(10); + mTestableLooper.processAllMessages(); + + assertFalse(mTile.isListening()); + assertThat(mTile.mRefreshes).isEqualTo(1); + } + private void assertEvent(UiEventLogger.UiEventEnum eventType, UiEventLoggerFake.FakeUiEvent fakeEvent) { assertEquals(eventType.getId(), fakeEvent.eventId); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 90c70f26e26b..5a5cdcd99054 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -33,7 +33,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.test.filters.SmallTest import com.android.compose.animation.scene.SceneKey import com.android.systemui.Flags -import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BACK_GESTURE import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX import com.android.systemui.SysuiTestCase import com.android.systemui.ambient.touch.TouchHandler @@ -442,7 +441,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) + @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) fun gestureExclusionZone_setAfterInit() = with(kosmos) { testScope.runTest { @@ -468,7 +467,6 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE) @EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) fun gestureExclusionZone_setAfterInit_fullSwipe() = with(kosmos) { @@ -489,7 +487,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) + @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) fun gestureExclusionZone_setAfterInit_rtl() = with(kosmos) { testScope.runTest { @@ -514,7 +512,6 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } - @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE) @EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) fun gestureExclusionZone_setAfterInit_rtl_fullSwipe() = with(kosmos) { @@ -535,102 +532,6 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE) - @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) - fun gestureExclusionZone_setAfterInit_backGestureEnabled() = - with(kosmos) { - testScope.runTest { - whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR) - goToScene(CommunalScenes.Communal) - - assertThat(containerView.systemGestureExclusionRects) - .containsExactly( - Rect( - /* left= */ FAKE_INSETS.left, - /* top= */ TOP_SWIPE_REGION_WIDTH, - /* right= */ CONTAINER_WIDTH - FAKE_INSETS.right, - /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH - ), - Rect( - /* left= */ 0, - /* top= */ 0, - /* right= */ FAKE_INSETS.right, - /* bottom= */ CONTAINER_HEIGHT - ) - ) - } - } - - @Test - @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) - fun gestureExclusionZone_setAfterInit_backGestureEnabled_fullSwipe() = - with(kosmos) { - testScope.runTest { - whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR) - goToScene(CommunalScenes.Communal) - - assertThat(containerView.systemGestureExclusionRects) - .containsExactly( - Rect( - /* left= */ 0, - /* top= */ 0, - /* right= */ FAKE_INSETS.right, - /* bottom= */ CONTAINER_HEIGHT - ) - ) - } - } - - @Test - @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE) - @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) - fun gestureExclusionZone_setAfterInit_backGestureEnabled_rtl() = - with(kosmos) { - testScope.runTest { - whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL) - goToScene(CommunalScenes.Communal) - - assertThat(containerView.systemGestureExclusionRects) - .containsExactly( - Rect( - /* left= */ FAKE_INSETS.left, - /* top= */ TOP_SWIPE_REGION_WIDTH, - /* right= */ CONTAINER_WIDTH - FAKE_INSETS.right, - /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH - ), - Rect( - /* left= */ FAKE_INSETS.left, - /* top= */ 0, - /* right= */ CONTAINER_WIDTH, - /* bottom= */ CONTAINER_HEIGHT - ) - ) - } - } - - @Test - @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) - fun gestureExclusionZone_setAfterInit_backGestureEnabled_rtl_fullSwipe() = - with(kosmos) { - testScope.runTest { - whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL) - goToScene(CommunalScenes.Communal) - - assertThat(containerView.systemGestureExclusionRects) - .containsExactly( - Rect( - Rect( - /* left= */ FAKE_INSETS.left, - /* top= */ 0, - /* right= */ CONTAINER_WIDTH, - /* bottom= */ CONTAINER_HEIGHT - ) - ) - ) - } - } - - @Test fun gestureExclusionZone_unsetWhenShadeOpen() = with(kosmos) { testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 9b611057c059..b75ac2bc9bde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -108,7 +108,9 @@ import com.android.systemui.statusbar.domain.interactor.StatusBarKeyguardViewMan import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.kotlin.JavaAdapter; +import com.android.systemui.util.time.FakeSystemClock; import com.google.common.truth.Truth; @@ -175,6 +177,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mBouncerExpansionCallback; private FakeKeyguardStateController mKeyguardStateController = spy(new FakeKeyguardStateController()); + private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); @Mock private ViewRootImpl mViewRootImpl; @@ -238,6 +241,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mock(JavaAdapter.class), () -> mSceneInteractor, mock(StatusBarKeyguardViewManagerInteractor.class), + mExecutor, () -> mDeviceEntryInteractor) { @Override public ViewRootImpl getViewRootImpl() { @@ -760,6 +764,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mock(JavaAdapter.class), () -> mSceneInteractor, mock(StatusBarKeyguardViewManagerInteractor.class), + mExecutor, () -> mDeviceEntryInteractor) { @Override public ViewRootImpl getViewRootImpl() { @@ -1084,6 +1089,9 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { .thenReturn(KeyguardState.LOCKSCREEN); reset(mCentralSurfaces); + // Advance past reattempts + mStatusBarKeyguardViewManager.setAttemptsToShowBouncer(10); + mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false); verify(mPrimaryBouncerInteractor).show(true); verify(mCentralSurfaces).showKeyguard(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt index 230ddf9d25db..48c2cc7f11c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt @@ -56,11 +56,11 @@ class StatusBarTouchableRegionManagerTest : SysuiTestCase() { runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() - sceneRepository.isRemoteUserInteractionOngoing.value = true + sceneRepository.isRemoteUserInputOngoing.value = true runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue() - sceneRepository.isRemoteUserInteractionOngoing.value = false + sceneRepository.isRemoteUserInputOngoing.value = false runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() } @@ -71,7 +71,7 @@ class StatusBarTouchableRegionManagerTest : SysuiTestCase() { testScope.runTest { assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() - sceneRepository.isRemoteUserInteractionOngoing.value = true + sceneRepository.isRemoteUserInputOngoing.value = true runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt index 78028f819fa0..26f6f3131585 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt @@ -3,7 +3,6 @@ package com.android.systemui.user.domain.interactor import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER import com.android.systemui.SysuiTestCase import com.android.systemui.user.data.repository.FakeUserRepository import com.google.common.truth.Truth.assertThat @@ -28,7 +27,6 @@ class SelectedUserInteractorTest : SysuiTestCase() { @Test fun getSelectedUserIdReturnsId() { - mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER) runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) } val actualId = underTest.getSelectedUserId() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt deleted file mode 100644 index 9b7bca6d2d34..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.keyguard.domain.interactor - -import android.content.Context -import android.os.Handler -import com.android.keyguard.KeyguardSecurityModel -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository -import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor -import com.android.systemui.bouncer.ui.BouncerView -import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor -import com.android.systemui.keyguard.DismissCallbackRegistry -import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.FakeTrustRepository -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.power.data.repository.FakePowerRepository -import com.android.systemui.power.domain.interactor.PowerInteractorFactory -import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.user.data.repository.FakeUserRepository -import com.android.systemui.user.domain.interactor.SelectedUserInteractor -import com.android.systemui.util.time.FakeSystemClock -import kotlinx.coroutines.test.TestScope -import org.mockito.Mockito.mock - -/** - * Helper to create a new KeyguardDismissInteractor in a way that doesn't require modifying many - * tests whenever we add a constructor param. - */ -object KeyguardDismissInteractorFactory { - @JvmOverloads - @JvmStatic - fun create( - context: Context, - testScope: TestScope, - trustRepository: FakeTrustRepository = FakeTrustRepository(), - keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(), - bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(), - keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(KeyguardUpdateMonitor::class.java), - powerRepository: FakePowerRepository = FakePowerRepository(), - userRepository: FakeUserRepository = FakeUserRepository(), - ): WithDependencies { - val primaryBouncerInteractor = - PrimaryBouncerInteractor( - bouncerRepository, - mock(BouncerView::class.java), - mock(Handler::class.java), - mock(KeyguardStateController::class.java), - mock(KeyguardSecurityModel::class.java), - mock(PrimaryBouncerCallbackInteractor::class.java), - mock(FalsingCollector::class.java), - mock(DismissCallbackRegistry::class.java), - context, - keyguardUpdateMonitor, - trustRepository, - testScope.backgroundScope, - mock(SelectedUserInteractor::class.java), - mock(DeviceEntryFaceAuthInteractor::class.java), - ) - val alternateBouncerInteractor = - AlternateBouncerInteractor( - mock(StatusBarStateController::class.java), - mock(KeyguardStateController::class.java), - bouncerRepository, - FakeFingerprintPropertyRepository(), - FakeBiometricSettingsRepository(), - FakeSystemClock(), - keyguardUpdateMonitor, - { mock(DeviceEntryBiometricsAllowedInteractor::class.java) }, - { mock(KeyguardInteractor::class.java) }, - { mock(KeyguardTransitionInteractor::class.java) }, - { mock(SceneInteractor::class.java) }, - testScope.backgroundScope, - ) - val powerInteractorWithDeps = - PowerInteractorFactory.create( - repository = powerRepository, - ) - val selectedUserInteractor = SelectedUserInteractor(repository = userRepository) - return WithDependencies( - trustRepository = trustRepository, - keyguardRepository = keyguardRepository, - bouncerRepository = bouncerRepository, - keyguardUpdateMonitor = keyguardUpdateMonitor, - powerRepository = powerRepository, - userRepository = userRepository, - interactor = - KeyguardDismissInteractor( - trustRepository, - keyguardRepository, - primaryBouncerInteractor, - alternateBouncerInteractor, - powerInteractorWithDeps.powerInteractor, - selectedUserInteractor, - ), - ) - } - - data class WithDependencies( - val trustRepository: FakeTrustRepository, - val keyguardRepository: FakeKeyguardRepository, - val bouncerRepository: FakeKeyguardBouncerRepository, - val keyguardUpdateMonitor: KeyguardUpdateMonitor, - val powerRepository: FakePowerRepository, - val userRepository: FakeUserRepository, - val interactor: KeyguardDismissInteractor, - ) -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt index f33ca95e488d..ace11573c7c6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt @@ -20,7 +20,10 @@ import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.keyguard.data.repository.trustRepository +import com.android.systemui.keyguard.dismissCallbackRegistry import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.user.domain.interactor.selectedUserInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -29,11 +32,14 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.keyguardDismissInteractor by Kosmos.Fixture { KeyguardDismissInteractor( - trustRepository = trustRepository, + mainDispatcher = testDispatcher, + scope = applicationCoroutineScope, keyguardRepository = keyguardRepository, primaryBouncerInteractor = primaryBouncerInteractor, + selectedUserInteractor = selectedUserInteractor, + dismissCallbackRegistry = dismissCallbackRegistry, + trustRepository = trustRepository, alternateBouncerInteractor = alternateBouncerInteractor, powerInteractor = powerInteractor, - selectedUserInteractor = selectedUserInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt index 6252d4498a5e..4b42e07f1f54 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt @@ -16,7 +16,6 @@ package com.android.systemui.shade.ui.viewmodel -import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos @@ -24,6 +23,5 @@ val Kosmos.notificationShadeWindowModel: NotificationShadeWindowModel by Kosmos.Fixture { NotificationShadeWindowModel( keyguardTransitionInteractor, - keyguardInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt index dbfd9de2aa8c..2772d3698d88 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notification.stack.data.repository.notificationPlaceholderRepository import com.android.systemui.statusbar.notification.stack.data.repository.notificationViewHeightRepository @@ -26,6 +27,7 @@ val Kosmos.notificationStackAppearanceInteractor by Fixture { NotificationStackAppearanceInteractor( viewHeightRepository = notificationViewHeightRepository, placeholderRepository = notificationPlaceholderRepository, + sceneInteractor = sceneInteractor, shadeInteractor = shadeInteractor, ) } diff --git a/services/Android.bp b/services/Android.bp index 0006455f41b0..653cd3c3b680 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -136,6 +136,7 @@ filegroup { ":services.searchui-sources", ":services.smartspace-sources", ":services.soundtrigger-sources", + ":services.supervision-sources", ":services.systemcaptions-sources", ":services.translation-sources", ":services.texttospeech-sources", @@ -237,6 +238,7 @@ system_java_library { "services.searchui", "services.smartspace", "services.soundtrigger", + "services.supervision", "services.systemcaptions", "services.translation", "services.texttospeech", diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 19279a887d11..07e5f2e34ab8 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3527,7 +3527,8 @@ class StorageManagerService extends IStorageManager.Stub // of the calling App final long token = Binder.clearCallingIdentity(); try { - Context targetAppContext = mContext.createPackageContext(packageName, 0); + Context targetAppContext = mContext.createPackageContextAsUser(packageName, + /* flags= */ 0, UserHandle.of(UserHandle.getUserId(originalUid))); Intent intent = new Intent(Intent.ACTION_DEFAULT); intent.setClassName(packageName, appInfo.manageSpaceActivityName); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b599a2fe64fe..d80b38e32b6c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -12168,6 +12168,7 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(" -p: dump also private dirty memory usage."); pw.println(" --oom: only show processes organized by oom adj."); pw.println(" --local: only collect details locally, don't call process."); + pw.println(" --logstats: dump native allocator stats to log"); pw.println(" --package: interpret process arg as package, dumping all"); pw.println(" processes that have loaded that package."); pw.println(" --checkin: dump data for a checkin"); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java index cf677d541fb2..7b1186c9d4c7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java @@ -80,13 +80,16 @@ public class AidlResponseHandler extends ISessionCallback.Stub { private final AuthSessionCoordinator mAuthSessionCoordinator; @NonNull private final AidlResponseHandlerCallback mAidlResponseHandlerCallback; + @NonNull + private final FaceUtils mBiometricUtils; public AidlResponseHandler(@NonNull Context context, @NonNull BiometricScheduler scheduler, int sensorId, int userId, @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull AuthSessionCoordinator authSessionCoordinator, - @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) { + @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback, + @NonNull FaceUtils biometricUtils) { mContext = context; mScheduler = scheduler; mSensorId = sensorId; @@ -95,6 +98,7 @@ public class AidlResponseHandler extends ISessionCallback.Stub { mLockoutResetDispatcher = lockoutResetDispatcher; mAuthSessionCoordinator = authSessionCoordinator; mAidlResponseHandlerCallback = aidlResponseHandlerCallback; + mBiometricUtils = biometricUtils; } @Override @@ -167,8 +171,7 @@ public class AidlResponseHandler extends ISessionCallback.Stub { } else { currentUserId = client.getTargetUserId(); } - final CharSequence name = FaceUtils.getInstance(mSensorId) - .getUniqueName(mContext, currentUserId); + final CharSequence name = mBiometricUtils.getUniqueName(mContext, currentUserId); final Face face = new Face(name, enrollmentId, mSensorId); handleResponse(FaceEnrollClient.class, (c) -> { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index 3eecc6de7450..d4ec573e1667 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -60,7 +60,6 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnrollClient; import com.android.server.biometrics.sensors.face.FaceService; -import com.android.server.biometrics.sensors.face.FaceUtils; import java.io.IOException; import java.util.ArrayList; @@ -85,6 +84,7 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { private final int mMaxTemplatesPerUser; private final boolean mDebugConsent; private final @android.hardware.face.FaceEnrollOptions.EnrollReason int mEnrollReason; + private final BiometricUtils<Face> mBiometricUtils; private final ClientMonitorCallback mPreviewHandleDeleterCallback = new ClientMonitorCallback() { @@ -107,7 +107,8 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, int maxTemplatesPerUser, boolean debugConsent, android.hardware.face.FaceEnrollOptions options, - @NonNull AuthenticationStateListeners authenticationStateListeners) { + @NonNull AuthenticationStateListeners authenticationStateListeners, + @NonNull BiometricUtils<Face> biometricUtils) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils, timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext, BiometricFaceConstants.reasonToMetric(options.getEnrollReason())); @@ -122,6 +123,7 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { mDebugConsent = debugConsent; mDisabledFeatures = disabledFeatures; mPreviewSurface = previewSurface; + mBiometricUtils = biometricUtils; Slog.w(TAG, "EnrollOptions " + android.hardware.face.FaceEnrollOptions.enrollReasonToString( options.getEnrollReason())); @@ -144,7 +146,7 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { @Override protected boolean hasReachedEnrollmentLimit() { - return FaceUtils.getInstance(getSensorId()).getBiometricsForUser(getContext(), + return mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).size() >= mMaxTemplatesPerUser; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java index 964bf6cad63c..c27b7c483afc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java @@ -30,7 +30,6 @@ import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.InternalCleanupClient; import com.android.server.biometrics.sensors.InternalEnumerateClient; import com.android.server.biometrics.sensors.RemovalClient; -import com.android.server.biometrics.sensors.face.FaceUtils; import java.util.List; import java.util.Map; @@ -75,7 +74,7 @@ public class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlS @Override protected void onAddUnknownTemplate(int userId, @NonNull BiometricAuthenticator.Identifier identifier) { - FaceUtils.getInstance(getSensorId()).addBiometricForUser( + mBiometricUtils.addBiometricForUser( getContext(), getTargetUserId(), (Face) identifier); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index f0a418951505..bb213bfa79e6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -72,7 +72,6 @@ import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.SensorList; -import com.android.server.biometrics.sensors.face.FaceUtils; import com.android.server.biometrics.sensors.face.ServiceProvider; import com.android.server.biometrics.sensors.face.UsageStats; import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSensorAdapter; @@ -326,8 +325,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } if (Build.isDebuggable()) { - BiometricUtils<Face> utils = FaceUtils.getInstance( - mFaceSensors.keyAt(0)); + BiometricUtils<Face> utils = mFaceSensors.get( + mFaceSensors.keyAt(0)).getFaceUtilsInstance(); for (UserInfo user : UserManager.get(mContext).getAliveUsers()) { List<Face> enrollments = utils.getBiometricsForUser(mContext, user.id); Slog.d(getTag(), "Expecting enrollments for user " + user.id + ": " @@ -386,7 +385,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { new InvalidationRequesterClient<>(mContext, userId, sensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, - FaceUtils.getInstance(sensorId)); + mFaceSensors.get(sensorId).getFaceUtilsInstance()); scheduleForSensor(sensorId, client); }); } @@ -415,7 +414,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull @Override public List<Face> getEnrolledFaces(int sensorId, int userId) { - return FaceUtils.getInstance(sensorId).getBiometricsForUser(mContext, userId); + return mFaceSensors.get(sensorId).getFaceUtilsInstance() + .getBiometricsForUser(mContext, userId); } @Override @@ -497,13 +497,14 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { final FaceEnrollClient client = new FaceEnrollClient(mContext, mFaceSensors.get(sensorId).getLazySession(), token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, - opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures, - ENROLL_TIMEOUT_SEC, previewSurface, sensorId, + opPackageName, id, mFaceSensors.get(sensorId).getFaceUtilsInstance(), + disabledFeatures, ENROLL_TIMEOUT_SEC, previewSurface, sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), mBiometricContext, maxTemplatesPerUser, debugConsent, options, - mAuthenticationStateListeners); + mAuthenticationStateListeners, + mFaceSensors.get(sensorId).getFaceUtilsInstance()); scheduleForSensor(sensorId, client, mBiometricStateCallback); }); return id; @@ -615,7 +616,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @Override public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { - final List<Face> faces = FaceUtils.getInstance(sensorId) + final List<Face> faces = mFaceSensors.get(sensorId).getFaceUtilsInstance() .getBiometricsForUser(mContext, userId); final int[] faceIds = new int[faces.size()]; for (int i = 0; i < faces.size(); i++) { @@ -632,7 +633,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { final FaceRemovalClient client = new FaceRemovalClient(mContext, mFaceSensors.get(sensorId).getLazySession(), token, new ClientMonitorCallbackConverter(receiver), faceIds, userId, - opPackageName, FaceUtils.getInstance(sensorId), sensorId, + opPackageName, mFaceSensors.get(sensorId).getFaceUtilsInstance(), sensorId, createLogger(BiometricsProtoEnums.ACTION_REMOVE, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), @@ -666,7 +667,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId); - final List<Face> faces = FaceUtils.getInstance(sensorId) + final List<Face> faces = mFaceSensors.get(sensorId).getFaceUtilsInstance() .getBiometricsForUser(mContext, userId); if (faces.isEmpty()) { Slog.w(getTag(), "Ignoring setFeature, no templates enrolled for user: " + userId); @@ -687,7 +688,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) { mHandler.post(() -> { mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId); - final List<Face> faces = FaceUtils.getInstance(sensorId) + final List<Face> faces = mFaceSensors.get(sensorId).getFaceUtilsInstance() .getBiometricsForUser(mContext, userId); if (faces.isEmpty()) { Slog.w(getTag(), "Ignoring getFeature, no templates enrolled for user: " + userId); @@ -727,7 +728,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), mBiometricContext, - FaceUtils.getInstance(sensorId), + mFaceSensors.get(sensorId).getFaceUtilsInstance(), mFaceSensors.get(sensorId).getAuthenticatorIds()); if (favorHalEnrollments) { client.setFavorHalEnrollments(); @@ -768,7 +769,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { JSONArray sets = new JSONArray(); for (UserInfo user : UserManager.get(mContext).getUsers()) { final int userId = user.getUserHandle().getIdentifier(); - final int c = FaceUtils.getInstance(sensorId) + final int c = mFaceSensors.get(sensorId).getFaceUtilsInstance() .getBiometricsForUser(mContext, userId).size(); JSONObject set = new JSONObject(); set.put("id", userId); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index b0e7575689ba..6f9534993a3f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -158,7 +158,7 @@ public class Sensor { Slog.e(TAG, "Face sensor hardware unavailable."); mCurrentSession = null; } - }); + }, getFaceUtilsInstance()); return Sensor.this.getStartUserClient(resultController, sensorId, newUserId, provider); @@ -280,8 +280,7 @@ public class Sensor { final long userToken = proto.start(SensorStateProto.USER_STATES); proto.write(UserStateProto.USER_ID, userId); proto.write(UserStateProto.NUM_ENROLLED, - FaceUtils.getInstance(mSensorProperties.sensorId) - .getBiometricsForUser(mContext, userId).size()); + getFaceUtilsInstance().getBiometricsForUser(mContext, userId).size()); proto.end(userToken); } @@ -358,4 +357,8 @@ public class Sensor { Supplier<AidlSession> lazySession) { mLazySession = lazySession; } + + public FaceUtils getFaceUtilsInstance() { + return FaceUtils.getInstance(mSensorProperties.sensorId); + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java index 9a4c29d7e978..444a6d18d27f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java @@ -159,6 +159,11 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe } @Override + public FaceUtils getFaceUtilsInstance() { + return FaceUtils.getLegacyInstance(getSensorProperties().sensorId); + } + + @Override protected LockoutTracker getLockoutTracker(boolean forAuth) { return mLockoutTracker; } @@ -180,7 +185,8 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe mLockoutTracker, mLockoutResetDispatcher, mAuthSessionCoordinator, - mAidlResponseHandlerCallback); + mAidlResponseHandlerCallback, + getFaceUtilsInstance()); } private IBiometricsFace getIBiometricsFace() { @@ -247,8 +253,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe return new FaceUpdateActiveUserClient(getContext(), this::getIBiometricsFace, mUserStartedCallback, userId, TAG, getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()), getBiometricContext(), - !FaceUtils.getInstance(getSensorProperties().sensorId).getBiometricsForUser( - getContext(), userId).isEmpty(), + !getFaceUtilsInstance().getBiometricsForUser(getContext(), userId).isEmpty(), getAuthenticatorIds()); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java index 6d1715f1d500..80b7cde3124c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java @@ -80,13 +80,16 @@ public class AidlResponseHandler extends ISessionCallback.Stub { private final AuthSessionCoordinator mAuthSessionCoordinator; @NonNull private final AidlResponseHandlerCallback mAidlResponseHandlerCallback; + @NonNull + private final FingerprintUtils mBiometricUtils; public AidlResponseHandler(@NonNull Context context, @NonNull BiometricScheduler scheduler, int sensorId, int userId, @NonNull LockoutTracker lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull AuthSessionCoordinator authSessionCoordinator, - @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) { + @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback, + @NonNull FingerprintUtils biometricUtils) { mContext = context; mScheduler = scheduler; mSensorId = sensorId; @@ -95,6 +98,7 @@ public class AidlResponseHandler extends ISessionCallback.Stub { mLockoutResetDispatcher = lockoutResetDispatcher; mAuthSessionCoordinator = authSessionCoordinator; mAidlResponseHandlerCallback = aidlResponseHandlerCallback; + mBiometricUtils = biometricUtils; } @Override @@ -158,8 +162,7 @@ public class AidlResponseHandler extends ISessionCallback.Stub { } else { currentUserId = client.getTargetUserId(); } - final CharSequence name = FingerprintUtils.getInstance(mSensorId) - .getUniqueName(mContext, currentUserId); + final CharSequence name = mBiometricUtils.getUniqueName(mContext, currentUserId); final Fingerprint fingerprint = new Fingerprint(name, currentUserId, enrollmentId, mSensorId); handleResponse(FingerprintEnrollClient.class, (c) -> { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java index 1fc517906c58..40b8a45beb36 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java @@ -81,7 +81,7 @@ public class FingerprintInternalCleanupClient @Override protected void onAddUnknownTemplate(int userId, @NonNull BiometricAuthenticator.Identifier identifier) { - FingerprintUtils.getInstance(getSensorId()).addBiometricForUser( + mBiometricUtils.addBiometricForUser( getContext(), getTargetUserId(), (Fingerprint) identifier); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 12baf00c1c4a..9edaa4e6d818 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -79,7 +79,6 @@ import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.SensorList; -import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler; import com.android.server.biometrics.sensors.fingerprint.ServiceProvider; @@ -354,8 +353,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } if (Build.isDebuggable()) { - BiometricUtils<Fingerprint> utils = FingerprintUtils.getInstance( - mFingerprintSensors.keyAt(0)); + final int sensorId = mFingerprintSensors.keyAt(0); + final BiometricUtils<Fingerprint> utils = mFingerprintSensors.get(sensorId) + .getFingerprintUtilsInstance(); for (UserInfo user : UserManager.get(mContext).getAliveUsers()) { List<Fingerprint> enrollments = utils.getBiometricsForUser(mContext, user.id); Slog.d(getTag(), "Expecting enrollments for user " + user.id + ": " @@ -442,7 +442,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi new InvalidationRequesterClient<>(mContext, userId, sensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, - FingerprintUtils.getInstance(sensorId)); + mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance()); scheduleForSensor(sensorId, client); }); } @@ -507,7 +507,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, id, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, - opPackageName, FingerprintUtils.getInstance(sensorId), + opPackageName, mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance(), sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL, BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), mBiometricContext, @@ -638,8 +638,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver, int userId, @NonNull String opPackageName) { - final List<Fingerprint> fingers = FingerprintUtils.getInstance(sensorId) - .getBiometricsForUser(mContext, userId); + final List<Fingerprint> fingers = mFingerprintSensors.get(sensorId) + .getFingerprintUtilsInstance().getBiometricsForUser(mContext, userId); final int[] fingerIds = new int[fingers.size()]; for (int i = 0; i < fingers.size(); i++) { fingerIds[i] = fingers.get(i).getBiometricId(); @@ -655,11 +655,10 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId, - opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, - createLogger(BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN, - mAuthenticationStatsCollector), - mBiometricContext, + opPackageName, mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance(), + sensorId, createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN, + mAuthenticationStatsCollector), mBiometricContext, mFingerprintSensors.get(sensorId).getAuthenticatorIds()); scheduleForSensor(sensorId, client, mBiometricStateCallback); }); @@ -683,7 +682,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), mBiometricContext, - FingerprintUtils.getInstance(sensorId), + mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance(), mFingerprintSensors.get(sensorId).getAuthenticatorIds()); if (favorHalEnrollments) { client.setFavorHalEnrollments(); @@ -706,14 +705,15 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @Override public void rename(int sensorId, int fingerId, int userId, @NonNull String name) { - FingerprintUtils.getInstance(sensorId) + mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance() .renameBiometricForUser(mContext, userId, fingerId, name); } @NonNull @Override public List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId) { - return FingerprintUtils.getInstance(sensorId).getBiometricsForUser(mContext, userId); + return mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance() + .getBiometricsForUser(mContext, userId); } @Override @@ -842,7 +842,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi JSONArray sets = new JSONArray(); for (UserInfo user : UserManager.get(mContext).getUsers()) { final int userId = user.getUserHandle().getIdentifier(); - final int c = FingerprintUtils.getInstance(sensorId) + final int c = mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance() .getBiometricsForUser(mContext, userId).size(); JSONObject set = new JSONObject(); set.put("id", userId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index 1c6dfe0f5b24..d12d7b2dc89a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -170,7 +170,7 @@ public class Sensor { "Fingerprint sensor hardware unavailable."); mCurrentSession = null; } - }); + }, getFingerprintUtilsInstance()); return Sensor.this.getStartUserClient(resultController, sensorId, newUserId); @@ -187,7 +187,7 @@ public class Sensor { + halInterfaceVersion); mCurrentSession = new AidlSession(halInterfaceVersion, newSession, userIdStarted, resultController); - if (FingerprintUtils.getInstance(sensorId) + if (getFingerprintUtilsInstance() .isInvalidationInProgress(mContext, userIdStarted)) { Slog.w(TAG, "Scheduling unfinished invalidation request for " @@ -307,9 +307,8 @@ public class Sensor { final long userToken = proto.start(SensorStateProto.USER_STATES); proto.write(UserStateProto.USER_ID, userId); - proto.write(UserStateProto.NUM_ENROLLED, - FingerprintUtils.getInstance(mSensorProperties.sensorId) - .getBiometricsForUser(mContext, userId).size()); + proto.write(UserStateProto.NUM_ENROLLED, getFingerprintUtilsInstance() + .getBiometricsForUser(mContext, userId).size()); proto.end(userToken); } @@ -386,4 +385,8 @@ public class Sensor { public FingerprintProvider getProvider() { return mProvider; } + + public FingerprintUtils getFingerprintUtilsInstance() { + return FingerprintUtils.getInstance(mSensorProperties.sensorId); + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java index 3214b6d3363f..8f52d00ad830 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java @@ -148,6 +148,11 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe } @Override + public FingerprintUtils getFingerprintUtilsInstance() { + return FingerprintUtils.getLegacyInstance(getSensorProperties().sensorId); + } + + @Override @Nullable @VisibleForTesting protected AidlSession getSessionForUser(int userId) { @@ -186,7 +191,8 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe mLockoutTracker, mLockoutResetDispatcher, mAuthSessionCoordinator, - mAidlResponseHandlerCallback); + mAidlResponseHandlerCallback, + getFingerprintUtilsInstance()); } @VisibleForTesting IBiometricsFingerprint getIBiometricsFingerprint() { @@ -266,8 +272,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe () -> getSession().getSession(), newUserId, TAG, getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()), getBiometricContext(), () -> mCurrentUserId, - !FingerprintUtils.getInstance(getSensorProperties().sensorId) - .getBiometricsForUser(getContext(), + !getFingerprintUtilsInstance().getBiometricsForUser(getContext(), newUserId).isEmpty(), getAuthenticatorIds(), forceUpdateAuthenticatorIds, mUserStartedCallback); } diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index c31d1d8b271c..d909004e6381 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -1500,10 +1500,18 @@ public class DisplayModeDirector { } private void updateLayoutLimitedFrameRate(int displayId, @Nullable DisplayInfo info) { - Vote vote = info != null && info.layoutLimitedRefreshRate != null - ? Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min, - info.layoutLimitedRefreshRate.max) : null; - mVotesStorage.updateVote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote); + Vote refreshRateVote = null; + Vote frameRateVote = null; + if (info != null && info.layoutLimitedRefreshRate != null) { + refreshRateVote = Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min, + info.layoutLimitedRefreshRate.max); + frameRateVote = Vote.forRenderFrameRates(info.layoutLimitedRefreshRate.min, + info.layoutLimitedRefreshRate.max); + } + mVotesStorage.updateVote( + displayId, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE, refreshRateVote); + mVotesStorage.updateVote( + displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, frameRateVote); } private void removeUserSettingDisplayPreferredSize(int displayId) { diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java index 88ee044810db..459f9a6e8f13 100644 --- a/services/core/java/com/android/server/display/mode/Vote.java +++ b/services/core/java/com/android/server/display/mode/Vote.java @@ -110,37 +110,40 @@ interface Vote { int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 13; // For concurrent displays we want to limit refresh rate on all displays - int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 14; + int PRIORITY_LAYOUT_LIMITED_REFRESH_RATE = 14; + + // For concurrent displays we want to limit refresh rate on all displays + int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 15; // For internal application to limit display modes to specific ids - int PRIORITY_SYSTEM_REQUESTED_MODES = 15; + int PRIORITY_SYSTEM_REQUESTED_MODES = 16; // PRIORITY_LOW_POWER_MODE_MODES limits display modes to specific refreshRate-vsync pairs if // Settings.Global.LOW_POWER_MODE is on. // Lower priority that PRIORITY_LOW_POWER_MODE_RENDER_RATE and if discarded (due to other // higher priority votes), render rate limit can still apply - int PRIORITY_LOW_POWER_MODE_MODES = 16; + int PRIORITY_LOW_POWER_MODE_MODES = 17; // PRIORITY_LOW_POWER_MODE_RENDER_RATE force the render frame rate to [0, 60HZ] if // Settings.Global.LOW_POWER_MODE is on. - int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 17; + int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 18; // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the // higher priority voters' result is a range, it will fix the rate to a single choice. // It's used to avoid refresh rate switches in certain conditions which may result in the // user seeing the display flickering when the switches occur. - int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 18; + int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 19; // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. - int PRIORITY_SKIN_TEMPERATURE = 19; + int PRIORITY_SKIN_TEMPERATURE = 20; // The proximity sensor needs the refresh rate to be locked in order to function, so this is // set to a high priority. - int PRIORITY_PROXIMITY = 20; + int PRIORITY_PROXIMITY = 21; // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order // to function, so this needs to be the highest priority of all votes. - int PRIORITY_UDFPS = 21; + int PRIORITY_UDFPS = 22; @IntDef(prefix = { "PRIORITY_" }, value = { PRIORITY_DEFAULT_RENDER_FRAME_RATE, @@ -157,6 +160,7 @@ interface Vote { PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE, PRIORITY_LIMIT_MODE, PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE, + PRIORITY_LAYOUT_LIMITED_REFRESH_RATE, PRIORITY_LAYOUT_LIMITED_FRAME_RATE, PRIORITY_SYSTEM_REQUESTED_MODES, PRIORITY_LOW_POWER_MODE_MODES, @@ -283,6 +287,8 @@ interface Vote { return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE"; case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE: return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE"; + case PRIORITY_LAYOUT_LIMITED_REFRESH_RATE: + return "PRIORITY_LAYOUT_LIMITED_REFRESH_RATE"; case PRIORITY_LAYOUT_LIMITED_FRAME_RATE: return "PRIORITY_LAYOUT_LIMITED_FRAME_RATE"; case PRIORITY_SYSTEM_REQUESTED_MODES: diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ffb2bb6a4528..54bc6fbfd930 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1583,6 +1583,8 @@ public class NotificationManagerService extends SystemService { // respond to direct replies with updates. So we need to update System UI // immediately. if (lifetimeExtensionRefactor()) { + // We need to reset this to allow the notif to be updated again. + r.setCanceledAfterLifetimeExtension(false); maybeNotifySystemUiListenerLifetimeExtendedLocked(r, r.getSbn().getPackageName(), packageImportance); } @@ -1639,9 +1641,12 @@ public class NotificationManagerService extends SystemService { // respond to direct replies with updates. So we need to update System UI // immediately. if (lifetimeExtensionRefactor()) { + // We need to reset this to allow the notif to be updated again. + r.setCanceledAfterLifetimeExtension(false); maybeNotifySystemUiListenerLifetimeExtendedLocked(r, r.getSbn().getPackageName(), packageImportance); } + r.recordSmartReplied(); LogMaker logMaker = r.getLogMaker() .setCategory(MetricsEvent.SMART_REPLY_ACTION) @@ -11741,17 +11746,37 @@ public class NotificationManagerService extends SystemService { private void maybeNotifySystemUiListenerLifetimeExtendedLocked(NotificationRecord record, String pkg, int packageImportance) { if (record != null && (record.getSbn().getNotification().flags - & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) { + & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0 + && !record.isCanceledAfterLifetimeExtension()) { boolean isAppForeground = pkg != null && packageImportance == IMPORTANCE_FOREGROUND; - // Lifetime extended notifications don't need to alert on state change. + // Save the original Record's post silently value, so we can restore it after we send + // the SystemUI specific silent update. + boolean savedPostSilentlyState = record.shouldPostSilently(); + boolean savedOnlyAlertOnceState = (record.getNotification().flags + & FLAG_ONLY_ALERT_ONCE) > 0; + // Lifetime extended notifications don't need to alert on new state change. record.setPostSilently(true); // We also set FLAG_ONLY_ALERT_ONCE to avoid the notification from HUN-ing again. record.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; + PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker(null); + tracker.addCleanupRunnable(() -> { + synchronized (mNotificationLock) { + // Mark that the notification has been updated due to cancelation, so it won't + // be updated again if the app cancels multiple times. + record.setCanceledAfterLifetimeExtension(true); + // Set the post silently status to the record's previous value. + record.setPostSilently(savedPostSilentlyState); + // Remove FLAG_ONLY_ALERT_ONCE if the notification did not previously have it. + if (!savedOnlyAlertOnceState) { + record.getNotification().flags &= ~FLAG_ONLY_ALERT_ONCE; + } + } + }); + mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(), - record, isAppForeground, /* isAppProvided= */ false, - mPostNotificationTrackerFactory.newTracker(null))); + record, isAppForeground, /* isAppProvided= */ false, tracker)); } } @@ -13351,17 +13376,23 @@ public class NotificationManagerService extends SystemService { @ElapsedRealtimeLong private final long mStartTime; @Nullable private final WakeLock mWakeLock; private boolean mOngoing; + private final List<Runnable> mCleanupRunnables; @VisibleForTesting PostNotificationTracker(@Nullable WakeLock wakeLock) { mStartTime = SystemClock.elapsedRealtime(); mWakeLock = wakeLock; mOngoing = true; + mCleanupRunnables = new ArrayList<Runnable>(); if (DBG) { Slog.d(TAG, "PostNotification: Started"); } } + void addCleanupRunnable(Runnable runnable) { + mCleanupRunnables.add(runnable); + } + @ElapsedRealtimeLong long getStartTime() { return mStartTime; @@ -13373,8 +13404,9 @@ public class NotificationManagerService extends SystemService { } /** - * Cancels the tracker (releasing the acquired WakeLock). Either {@link #finish} or - * {@link #cancel} (exclusively) should be called on this object before it's discarded. + * Cancels the tracker (releasing the acquired WakeLock) and runs any set cleanup runnables. + * Either {@link #finish} or {@link #cancel} (exclusively) should be called on this object + * before it's discarded. */ void cancel() { if (!isOngoing()) { @@ -13385,6 +13417,9 @@ public class NotificationManagerService extends SystemService { if (mWakeLock != null) { Binder.withCleanCallingIdentity(() -> mWakeLock.release()); } + for (Runnable r : mCleanupRunnables) { + r.run(); + } if (DBG) { long elapsedTime = SystemClock.elapsedRealtime() - mStartTime; Slog.d(TAG, TextUtils.formatSimple("PostNotification: Abandoned after %d ms", @@ -13393,9 +13428,10 @@ public class NotificationManagerService extends SystemService { } /** - * Finishes the tracker (releasing the acquired WakeLock) and returns the time elapsed since - * the operation started, in milliseconds. Either {@link #finish} or {@link #cancel} - * (exclusively) should be called on this object before it's discarded. + * Finishes the tracker (releasing the acquired WakeLock), runs any set cleanup runnables, + * and returns the time elapsed since the operation started, in milliseconds. + * Either {@link #finish} or {@link #cancel} (exclusively) should be called on this object + * before it's discarded. */ @DurationMillisLong long finish() { @@ -13408,6 +13444,9 @@ public class NotificationManagerService extends SystemService { if (mWakeLock != null) { Binder.withCleanCallingIdentity(() -> mWakeLock.release()); } + for (Runnable r : mCleanupRunnables) { + r.run(); + } if (DBG) { Slog.d(TAG, TextUtils.formatSimple("PostNotification: Finished in %d ms", elapsedTime)); diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 1392003a13e7..e54124608a26 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -222,6 +222,9 @@ public final class NotificationRecord { private boolean mPendingLogUpdate = false; private int mProposedImportance = IMPORTANCE_UNSPECIFIED; private boolean mSensitiveContent = false; + // Whether an app has attempted to cancel this notification after it has been marked as + // lifetime extended. + private boolean mCanceledAfterLifetimeExtension = false; public NotificationRecord(Context context, StatusBarNotification sbn, NotificationChannel channel) { @@ -535,6 +538,7 @@ public final class NotificationRecord { + NotificationListenerService.Ranking.importanceToString(mProposedImportance)); pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked); pw.println(prefix + "mSensitiveContent=" + mSensitiveContent); + pw.println(prefix + "mCanceledAfterLifetimeExtension=" + mCanceledAfterLifetimeExtension); pw.println(prefix + "mIntercept=" + mIntercept); pw.println(prefix + "mHidden==" + mHidden); pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey); @@ -1620,6 +1624,14 @@ public final class NotificationRecord { mPkgAllowedAsConvo = allowedAsConvo; } + public boolean isCanceledAfterLifetimeExtension() { + return mCanceledAfterLifetimeExtension; + } + + public void setCanceledAfterLifetimeExtension(boolean canceledAfterLifetimeExtension) { + mCanceledAfterLifetimeExtension = canceledAfterLifetimeExtension; + } + /** * Whether this notification is a conversation notification. */ diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java index 9b868bebd868..5f3c5583e024 100644 --- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java +++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java @@ -112,11 +112,16 @@ class TrustedOverlayHost { final SurfaceControl.Transaction t = mWmService.mTransactionFactory.get(); for (int i = mOverlays.size() - 1; i >= 0; i--) { - SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i); - if (l.getSurfaceControl().isSameSurface(p.getSurfaceControl())) { - mOverlays.remove(i); - t.reparent(l.getSurfaceControl(), null); - l.release(); + SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i); + SurfaceControl overlaySurfaceControl = l.getSurfaceControl(); + if (overlaySurfaceControl == null) { + // Remove the overlay if the surfacepackage was released. Ownership + // is shared, so this may happen. + mOverlays.remove(i); + } else if (overlaySurfaceControl.isSameSurface(p.getSurfaceControl())) { + mOverlays.remove(i); + t.reparent(l.getSurfaceControl(), null); + l.release(); } } t.apply(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 1290fb7ef91a..a80ee0f66742 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -2726,22 +2726,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } - if (Flags.securityLogV2Enabled()) { - boolean auditLoggingEnabled = Boolean.TRUE.equals( - mDevicePolicyEngine.getResolvedPolicy( - PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL)); - boolean securityLoggingEnabled = Boolean.TRUE.equals( - mDevicePolicyEngine.getResolvedPolicy( - PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL)); - setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled); - mInjector.runCryptoSelfTest(); - } else { - synchronized (getLockObject()) { - mSecurityLogMonitor.start(getSecurityLoggingEnabledUser()); - mInjector.runCryptoSelfTest(); - maybePauseDeviceWideLoggingLocked(); - } - } + boolean auditLoggingEnabled = Boolean.TRUE.equals( + mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL)); + boolean securityLoggingEnabled = Boolean.TRUE.equals( + mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL)); + setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled); + mInjector.runCryptoSelfTest(); } /** @@ -3399,7 +3391,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @GuardedBy("getLockObject()") private void maybeMigrateSecurityLoggingPolicyLocked() { - if (!Flags.securityLogV2Enabled() || mOwners.isSecurityLoggingMigrated()) { + if (mOwners.isSecurityLoggingMigrated()) { return; } @@ -16304,9 +16296,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void enforceSecurityLoggingPolicy(boolean enabled) { - if (!Flags.securityLogV2Enabled()) { - return; - } Boolean auditLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy( PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL); enforceLoggingPolicy(enabled, Boolean.TRUE.equals(auditLoggingEnabled)); @@ -16314,9 +16303,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void enforceAuditLoggingPolicy(boolean enabled) { - if (!Flags.securityLogV2Enabled()) { - return; - } Boolean securityLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy( PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL); enforceLoggingPolicy(Boolean.TRUE.equals(securityLoggingEnabled), enabled); @@ -18252,45 +18238,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final CallerIdentity caller = getCallerIdentity(who, packageName); - if (Flags.securityLogV2Enabled()) { - EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_SECURITY_LOGGING, - caller.getPackageName(), - caller.getUserId()); - if (enabled) { - mDevicePolicyEngine.setGlobalPolicy( - PolicyDefinition.SECURITY_LOGGING, - admin, - new BooleanPolicyValue(true)); - } else { - mDevicePolicyEngine.removeGlobalPolicy( - PolicyDefinition.SECURITY_LOGGING, - admin); - } + EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( + who, + MANAGE_DEVICE_POLICY_SECURITY_LOGGING, + caller.getPackageName(), + caller.getUserId()); + if (enabled) { + mDevicePolicyEngine.setGlobalPolicy( + PolicyDefinition.SECURITY_LOGGING, + admin, + new BooleanPolicyValue(true)); } else { - synchronized (getLockObject()) { - if (who != null) { - Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDefaultDeviceOwner(caller)); - } else { - // A delegate app passes a null admin component, which is expected - Preconditions.checkCallAuthorization( - isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); - } - - if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) { - return; - } - mInjector.securityLogSetLoggingEnabledProperty(enabled); - if (enabled) { - mSecurityLogMonitor.start(getSecurityLoggingEnabledUser()); - maybePauseDeviceWideLoggingLocked(); - } else { - mSecurityLogMonitor.stop(); - } - } + mDevicePolicyEngine.removeGlobalPolicy( + PolicyDefinition.SECURITY_LOGGING, + admin); } DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_SECURITY_LOGGING_ENABLED) @@ -18312,29 +18273,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mInjector.securityLogGetLoggingEnabledProperty(); } - if (Flags.securityLogV2Enabled()) { - final EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - admin, - MANAGE_DEVICE_POLICY_SECURITY_LOGGING, - caller.getPackageName(), - caller.getUserId()); - final Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin( - PolicyDefinition.SECURITY_LOGGING, enforcingAdmin); - return Boolean.TRUE.equals(policy); - } else { - synchronized (getLockObject()) { - if (admin != null) { - Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDefaultDeviceOwner(caller)); - } else { - // A delegate app passes a null admin component, which is expected - Preconditions.checkCallAuthorization( - isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); - } - return mInjector.securityLogGetLoggingEnabledProperty(); - } - } + final EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( + admin, + MANAGE_DEVICE_POLICY_SECURITY_LOGGING, + caller.getPackageName(), + caller.getUserId()); + final Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin( + PolicyDefinition.SECURITY_LOGGING, enforcingAdmin); + return Boolean.TRUE.equals(policy); } private void recordSecurityLogRetrievalTime() { @@ -18410,42 +18356,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(admin, packageName); - if (Flags.securityLogV2Enabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - admin, - MANAGE_DEVICE_POLICY_SECURITY_LOGGING, - caller.getPackageName(), - caller.getUserId()); - - synchronized (getLockObject()) { - Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() - || areAllUsersAffiliatedWithDeviceLocked()); - } - - Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin( - PolicyDefinition.SECURITY_LOGGING, enforcingAdmin); + EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( + admin, + MANAGE_DEVICE_POLICY_SECURITY_LOGGING, + caller.getPackageName(), + caller.getUserId()); - if (!Boolean.TRUE.equals(policy)) { - Slogf.e(LOG_TAG, "%s hasn't enabled security logging but tries to retrieve logs", - caller.getPackageName()); - return null; - } - } else { - if (admin != null) { - Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDefaultDeviceOwner(caller)); - } else { - // A delegate app passes a null admin component, which is expected - Preconditions.checkCallAuthorization( - isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); - } + synchronized (getLockObject()) { Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() || areAllUsersAffiliatedWithDeviceLocked()); + } - if (!mInjector.securityLogGetLoggingEnabledProperty()) { - return null; - } + Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin( + PolicyDefinition.SECURITY_LOGGING, enforcingAdmin); + + if (!Boolean.TRUE.equals(policy)) { + Slogf.e(LOG_TAG, "%s hasn't enabled security logging but tries to retrieve logs", + caller.getPackageName()); + return null; } recordSecurityLogRetrievalTime(); @@ -18465,10 +18393,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final CallerIdentity caller = getCallerIdentity(callingPackage); - if (!Flags.securityLogV2Enabled()) { - throw new UnsupportedOperationException("Audit log not enabled"); - } - EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( null /* admin */, MANAGE_DEVICE_POLICY_AUDIT_LOGGING, @@ -18493,10 +18417,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } - if (!Flags.securityLogV2Enabled()) { - throw new UnsupportedOperationException("Audit log not enabled"); - } - final CallerIdentity caller = getCallerIdentity(callingPackage); EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( null /* admin */, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java index 2ea5f168bdd1..52a784559510 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java @@ -410,9 +410,8 @@ class OwnersData { out.startTag(null, TAG_POLICY_ENGINE_MIGRATION); out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine); out.attributeBoolean(null, ATTR_MIGRATED_POST_UPGRADE, mPoliciesMigratedPostUpdate); - if (Flags.securityLogV2Enabled()) { - out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated); - } + out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated); + if (Flags.unmanagedModeMigration()) { out.attributeBoolean(null, ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED, mRequiredPasswordComplexityMigrated); @@ -483,8 +482,8 @@ class OwnersData { null, ATTR_MIGRATED_TO_POLICY_ENGINE, false); mPoliciesMigratedPostUpdate = parser.getAttributeBoolean( null, ATTR_MIGRATED_POST_UPGRADE, false); - mSecurityLoggingMigrated = Flags.securityLogV2Enabled() - && parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false); + mSecurityLoggingMigrated = + parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false); mRequiredPasswordComplexityMigrated = Flags.unmanagedModeMigration() && parser.getAttributeBoolean(null, ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED, false); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java index dd0493032c56..474c48a746c9 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java @@ -23,7 +23,6 @@ import android.app.admin.DeviceAdminReceiver; import android.app.admin.IAuditLogEventsCallback; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; -import android.app.admin.flags.Flags; import android.os.Handler; import android.os.IBinder; import android.os.Process; @@ -184,28 +183,6 @@ class SecurityLogMonitor implements Runnable { @GuardedBy("mLock") private final ArrayDeque<SecurityEvent> mAuditLogEventBuffer = new ArrayDeque<>(); - /** - * Start security logging. - * - * @param enabledUser which user logging is enabled on, or USER_ALL to enable logging for all - * users on the device. - */ - void start(int enabledUser) { - Slog.i(TAG, "Starting security logging for user " + enabledUser); - mEnabledUser = enabledUser; - mLock.lock(); - try { - if (mMonitorThread == null) { - resetLegacyBufferLocked(); - startMonitorThreadLocked(); - } else { - Slog.i(TAG, "Security log monitor thread is already running"); - } - } finally { - mLock.unlock(); - } - } - void stop() { Slog.i(TAG, "Stopping security logging."); mLock.lock(); @@ -467,11 +444,11 @@ class SecurityLogMonitor implements Runnable { assignLogId(event); } - if (!Flags.securityLogV2Enabled() || mLegacyLogEnabled) { + if (mLegacyLogEnabled) { addToLegacyBufferLocked(dedupedLogs); } - if (Flags.securityLogV2Enabled() && mAuditLogEnabled) { + if (mAuditLogEnabled) { addAuditLogEventsLocked(dedupedLogs); } } @@ -548,7 +525,7 @@ class SecurityLogMonitor implements Runnable { saveLastEvents(newLogs); newLogs.clear(); - if (!Flags.securityLogV2Enabled() || mLegacyLogEnabled) { + if (mLegacyLogEnabled) { notifyDeviceOwnerOrProfileOwnerIfNeeded(force); } } catch (IOException e) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 37e7cfc6ef98..c5c371ff85d5 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -106,7 +106,9 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.policy.AttributeCache; +import com.android.internal.protolog.ProtoLog; import com.android.internal.protolog.ProtoLogConfigurationService; +import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.util.FrameworkStatsLog; @@ -256,6 +258,7 @@ import com.android.server.stats.bootstrap.StatsBootstrapAtomService; import com.android.server.stats.pull.StatsPullAtomService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; +import com.android.server.supervision.SupervisionService; import com.android.server.systemcaptions.SystemCaptionsManagerService; import com.android.server.telecom.TelecomLoaderService; import com.android.server.testharness.TestHarnessModeService; @@ -1098,6 +1101,10 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } + t.traceBegin("InitializeProtoLog"); + ProtoLog.init(ProtoLogGroup.values()); + t.traceEnd(); + // Platform compat service is used by ActivityManagerService, PackageManagerService, and // possibly others in the future. b/135010838. t.traceBegin("PlatformCompat"); @@ -1598,6 +1605,12 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(ROLE_SERVICE_CLASS); t.traceEnd(); + if (android.app.supervision.flags.Flags.supervisionApi()) { + t.traceBegin("StartSupervisionService"); + mSystemServiceManager.startService(SupervisionService.Lifecycle.class); + t.traceEnd(); + } + if (!isTv) { t.traceBegin("StartVibratorManagerService"); mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class); diff --git a/services/supervision/Android.bp b/services/supervision/Android.bp new file mode 100644 index 000000000000..93a0c4af7891 --- /dev/null +++ b/services/supervision/Android.bp @@ -0,0 +1,22 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "services.supervision-sources", + srcs: ["java/**/*.java"], + path: "java", + visibility: ["//frameworks/base/services"], +} + +java_library_static { + name: "services.supervision", + defaults: ["platform_service_defaults"], + srcs: [":services.supervision-sources"], + libs: ["services.core"], +} diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java new file mode 100644 index 000000000000..a4ef629492e7 --- /dev/null +++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java @@ -0,0 +1,67 @@ +/* + * 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.annotation.NonNull; +import android.annotation.Nullable; +import android.app.supervision.ISupervisionManager; +import android.content.Context; + + +import com.android.internal.util.DumpUtils; +import com.android.server.SystemService; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** Service for handling system supervision. */ +public class SupervisionService extends ISupervisionManager.Stub { + private static final String LOG_TAG = "SupervisionService"; + + private final Context mContext; + + public SupervisionService(Context context) { + mContext = context.createAttributionContext("SupervisionService"); + } + + @Override + public boolean isSupervisionEnabled() { + return false; + } + + @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()); + } + + public static class Lifecycle extends SystemService { + private final SupervisionService mSupervisionService; + + public Lifecycle(@NonNull Context context) { + super(context); + mSupervisionService = new SupervisionService(context); + } + + @Override + public void onStart() { + publishBinderService(Context.SUPERVISION_SERVICE, mSupervisionService); + } + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index ab0f0c1fe5ff..d91f154c1b87 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -3556,12 +3556,16 @@ public class DisplayModeDirectorTest { new RefreshRateRange(refreshRate, refreshRate); displayListener.onDisplayChanged(DISPLAY_ID); - Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE); + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE); assertVoteForPhysicalRefreshRate(vote, /* refreshRate= */ refreshRate); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, refreshRate, refreshRate); mInjector.mDisplayInfo.layoutLimitedRefreshRate = null; displayListener.onDisplayChanged(DISPLAY_ID); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE); + assertNull(vote); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE); assertNull(vote); } @@ -3585,6 +3589,8 @@ public class DisplayModeDirectorTest { Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE); assertNull(vote); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE); + assertNull(vote); } private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) { diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java index 54f46078d30b..698ce13aa6db 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java @@ -18,7 +18,9 @@ package com.android.server.dreams; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -27,7 +29,9 @@ import android.content.ComponentName; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; +import android.platform.test.annotations.EnableFlags; import android.service.dreams.DreamOverlayService; +import android.service.dreams.Flags; import android.service.dreams.IDreamOverlay; import android.service.dreams.IDreamOverlayCallback; import android.service.dreams.IDreamOverlayClient; @@ -221,6 +225,47 @@ public class DreamOverlayServiceTest { verify(monitor, never()).onWakeUp(); } + /** + * Verifies that only the currently started dream is able to affect the overlay. + */ + @Test + @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT) + public void testRedirectToWakeAcrossClients() throws RemoteException { + doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mExecutor).execute(any()); + + final TestDreamOverlayService.Monitor monitor = Mockito.mock( + TestDreamOverlayService.Monitor.class); + final TestDreamOverlayService service = new TestDreamOverlayService(monitor, mExecutor); + final IBinder binder = service.onBind(new Intent()); + final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder); + + service.redirectWake(true); + + final IDreamOverlayClient client = getClient(overlay); + + // Start the dream. + client.startDream(mLayoutParams, mOverlayCallback, + FIRST_DREAM_COMPONENT.flattenToString(), false); + // Make sure redirect state is set on dream. + verify(mOverlayCallback).onRedirectWake(eq(true)); + + // Make sure new changes are propagated. + clearInvocations(mOverlayCallback); + service.redirectWake(false); + verify(mOverlayCallback).onRedirectWake(eq(false)); + + + // Start another dream, make sure new dream is informed of current state. + service.redirectWake(true); + clearInvocations(mOverlayCallback); + client.startDream(mLayoutParams, mOverlayCallback, + FIRST_DREAM_COMPONENT.flattenToString(), false); + verify(mOverlayCallback).onRedirectWake(eq(true)); + } + private static IDreamOverlayClient getClient(IDreamOverlay overlay) throws RemoteException { final OverlayClientCallback callback = new OverlayClientCallback(); overlay.getClient(callback); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java index f47768280cc1..6ac95c829f56 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java @@ -109,6 +109,8 @@ public class FaceEnrollClientTest { private AidlResponseHandler mAidlResponseHandler; @Mock private AuthenticationStateListeners mAuthenticationStateListeners; + @Mock + private BiometricUtils<Face> mBiometricUtils; @Captor private ArgumentCaptor<OperationContextExt> mOperationContextCaptor; @Captor @@ -213,7 +215,7 @@ public class FaceEnrollClientTest { mBiometricLogger, mBiometricContext, 5 /* maxTemplatesPerUser */, true /* debugConsent */, (new FaceEnrollOptions.Builder()).setEnrollReason(ENROLL_SOURCE).build(), - mAuthenticationStateListeners); + mAuthenticationStateListeners, mBiometricUtils); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index 6780e60a22b0..bf970867f149 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -50,6 +50,7 @@ import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.UserSwitchProvider; +import com.android.server.biometrics.sensors.face.FaceUtils; import org.junit.Before; import org.junit.Test; @@ -90,6 +91,8 @@ public class SensorTest { private AidlSession mCurrentSession; @Mock private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback; + @Mock + private FaceUtils mBiometricUtils; private final TestLooper mLooper = new TestLooper(); private final LockoutCache mLockoutCache = new LockoutCache(); @@ -114,7 +117,7 @@ public class SensorTest { mUserSwitchProvider); mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - mAidlResponseHandlerCallback); + mAidlResponseHandlerCallback, mBiometricUtils); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java index 4248e5e37238..24ce569f644e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java @@ -206,7 +206,7 @@ public class HidlToAidlSensorAdapterTest { new int[]{} /* disabledFeatures */, ENROLL_TIMEOUT_SEC, null /* previewSurface */, SENSOR_ID, mLogger, mBiometricContext, 1 /* maxTemplatesPerUser */, false /* debugConsent */, (new FaceEnrollOptions.Builder()).build(), - mAuthenticationStateListeners)); + mAuthenticationStateListeners, mBiometricUtils)); mLooper.dispatchAll(); verify(mAidlResponseHandlerCallback).onEnrollSuccess(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index 698db2e19661..4ef8782386d5 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -51,6 +51,7 @@ import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.UserSwitchProvider; +import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import org.junit.Before; @@ -96,6 +97,8 @@ public class SensorTest { private HandlerThread mThread; @Mock AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback; + @Mock + private FingerprintUtils mBiometricUtils; private final TestLooper mLooper = new TestLooper(); private final LockoutCache mLockoutCache = new LockoutCache(); @@ -121,7 +124,7 @@ public class SensorTest { mUserSwitchProvider); mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, - mAidlResponseHandlerCallback); + mAidlResponseHandlerCallback, mBiometricUtils); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 5a8de58c14ae..0a52238671cd 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -3047,6 +3047,41 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) + public void testMultipleCancelOfLifetimeExtendedSendsOneUpdate() throws Exception { + final NotificationRecord notif = generateNotificationRecord(null); + notif.getSbn().getNotification().flags = + Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + mService.addNotification(notif); + final StatusBarNotification sbn = notif.getSbn(); + + assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(1); + assertThat(mService.getNotificationRecordCount()).isEqualTo(1); + + // Send two cancelations. + mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(), + sbn.getUserId()); + waitForIdle(); + mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(), + sbn.getUserId()); + waitForIdle(); + + assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(1); + assertThat(mService.getNotificationRecordCount()).isEqualTo(1); + + // Checks that only one post update is sent. + verify(mWorkerHandler, times(1)) + .post(any(NotificationManagerService.PostNotificationRunnable.class)); + ArgumentCaptor<NotificationRecord> captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(), + anyBoolean()); + assertThat(captor.getValue().getNotification().flags + & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo( + FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void testCancelAllClearsLifetimeExtended() throws Exception { final NotificationRecord notif = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); @@ -6419,12 +6454,31 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void testStats_DirectReplyLifetimeExtendedPostsUpdate() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + // Marks the notification as having already been lifetime extended and canceled. r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + r.setCanceledAfterLifetimeExtension(true); + r.setPostSilently(true); mService.addNotification(r); mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey()); waitForIdle(); + // At the moment prepareNotifyPostedLocked is called on the listeners, + // verify that FLAG_ONLY_ALERT_ONCE and shouldPostSilently are set, regardless of initial + // values. + doAnswer( + invocation -> { + int flags = ((NotificationRecord) invocation.getArgument(0)) + .getSbn().getNotification().flags; + assertThat(flags & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE); + boolean shouldPostSilently = ((NotificationRecord) invocation.getArgument(0)) + .shouldPostSilently(); + assertThat(shouldPostSilently).isTrue(); + return null; + } + ).when(mListeners).prepareNotifyPostedLocked(any(), any(), anyBoolean()); + + // Checks that the record gets marked as a direct reply having occurred. assertThat(mService.getNotificationRecord(r.getKey()).getStats().hasDirectReplied()) .isTrue(); // Checks that a post update is sent. @@ -6437,9 +6491,65 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(captor.getValue().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo( FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY); + // FLAG_ONLY_ALERT_ONCE was not present on the original notification, so it's not here. assertThat(captor.getValue().getNotification().flags - & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE); + & FLAG_ONLY_ALERT_ONCE).isEqualTo(0); assertThat(captor.getValue().shouldPostSilently()).isTrue(); + assertThat(captor.getValue().isCanceledAfterLifetimeExtension()).isTrue(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) + public void testStats_DirectReplyLifetimeExtendedPostsUpdate_RestorePostSilently() + throws Exception { + final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + // Marks the notification as having already been lifetime extended and canceled. + r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + r.setPostSilently(false); + mService.addNotification(r); + + mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey()); + waitForIdle(); + + // Checks that a post update is sent with shouldPostSilently set to true. + doAnswer( + invocation -> { + boolean shouldPostSilently = ((NotificationRecord) invocation.getArgument(0)) + .shouldPostSilently(); + assertThat(shouldPostSilently).isTrue(); + return null; + } + ).when(mListeners).prepareNotifyPostedLocked(any(), any(), anyBoolean()); + + // Checks that shouldPostSilently is restored to its false state afterward. + assertThat(mService.getNotificationRecord(r.getKey()).shouldPostSilently()).isFalse(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) + public void testStats_DirectReplyLifetimeExtendedPostsUpdate_RestoreOnlyAlertOnceFlag() + throws Exception { + final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + // Marks the notification as having already been lifetime extended and canceled. + r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + mService.addNotification(r); + + mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey()); + waitForIdle(); + + // Checks that a post update is sent with FLAG_ONLY_ALERT_ONCE set to true. + doAnswer( + invocation -> { + int flags = ((NotificationRecord) invocation.getArgument(0)) + .getSbn().getNotification().flags; + assertThat(flags & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE); + return null; + } + ).when(mListeners).prepareNotifyPostedLocked(any(), any(), anyBoolean()); + + // Checks that the flag is removed afterward. + assertThat(mService.getNotificationRecord(r.getKey()).getSbn().getNotification().flags + & FLAG_ONLY_ALERT_ONCE).isEqualTo(0); } @Test @@ -6476,6 +6586,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { anyBoolean()); assertThat(captor.getValue().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0); + assertThat(captor.getValue().isCanceledAfterLifetimeExtension()).isFalse(); assertThat(captor.getValue() .getNotification().extras.getCharSequence(Notification.EXTRA_TITLE).toString()) .isEqualTo("new title"); @@ -9143,11 +9254,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { final int replyIndex = 2; final String reply = "Hello"; final boolean modifiedBeforeSending = true; - final boolean generatedByAssistant = true; NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; - r.setSuggestionsGeneratedByAssistant(generatedByAssistant); + r.getSbn().getNotification().flags |= FLAG_ONLY_ALERT_ONCE; + r.setSuggestionsGeneratedByAssistant(true); + r.setCanceledAfterLifetimeExtension(true); + r.setPostSilently(true); mService.addNotification(r); mService.mNotificationDelegate.onNotificationSmartReplySent( @@ -9155,6 +9268,21 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { modifiedBeforeSending); waitForIdle(); + // At the moment prepareNotifyPostedLocked is called on the listeners, + // verify that FLAG_ONLY_ALERT_ONCE and shouldPostSilently are set, regardless of initial + // values. + doAnswer( + invocation -> { + int flags = ((NotificationRecord) invocation.getArgument(0)) + .getSbn().getNotification().flags; + assertThat(flags & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE); + boolean shouldPostSilently = ((NotificationRecord) invocation.getArgument(0)) + .shouldPostSilently(); + assertThat(shouldPostSilently).isTrue(); + return null; + } + ).when(mListeners).prepareNotifyPostedLocked(any(), any(), anyBoolean()); + // Checks that a post update is sent. verify(mWorkerHandler, times(1)) .post(any(NotificationManagerService.PostNotificationRunnable.class)); @@ -9165,8 +9293,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(captor.getValue().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo( FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY); + // Flag was present before, so it's set afterward assertThat(captor.getValue().getNotification().flags & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE); + // Should post silently was set before, so it's set afterward. assertThat(captor.getValue().shouldPostSilently()).isTrue(); } |